From bce060a28eebd9068a928a1d308136fb8ee3e6b4 Mon Sep 17 00:00:00 2001 From: jaysunxiao Date: Thu, 20 May 2021 14:17:21 +0800 Subject: [PATCH] init project --- .gitignore | 61 + README.md | 163 + deploy.sh | 260 + event/pom.xml | 244 + .../java/com/zfoo/event/EventContext.java | 97 + .../java/com/zfoo/event/manager/EventBus.java | 188 + .../zfoo/event/model/anno/EventReceiver.java | 29 + .../zfoo/event/model/event/AppStartEvent.java | 31 + .../com/zfoo/event/model/event/IEvent.java | 44 + .../com/zfoo/event/model/vo/EnhanceUtils.java | 87 + .../model/vo/EventReceiverDefinition.java | 71 + .../zfoo/event/model/vo/IEventReceiver.java | 26 + .../event/schema/EventDefinitionParser.java | 51 + .../event/schema/EventRegisterProcessor.java | 35 + .../zfoo/event/schema/NamespaceHandler.java | 30 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 1 + event/src/main/resources/event-1.0.xsd | 20 + .../java/com/zfoo/event/ApplicationTest.java | 48 + .../java/com/zfoo/event/MyController1.java | 35 + .../java/com/zfoo/event/MyController2.java | 38 + .../java/com/zfoo/event/MyNoticeEvent.java | 39 + event/src/test/resources/application.xml | 23 + event/src/test/resources/logback-test.xml | 44 + event/tooltip/general-game-architect.jpg | Bin 0 -> 1016290 bytes hotswap/pom.xml | 252 + .../java/com/zfoo/hotswap/HotSwapContext.java | 26 + .../com/zfoo/hotswap/agent/HotSwapAgent.java | 67 + .../zfoo/hotswap/manager/HotSwapManager.java | 34 + .../zfoo/hotswap/manager/IHotSwapManager.java | 15 + .../com/zfoo/hotswap/model/ClassFileDef.java | 64 + .../hotswap/service/HotSwapServiceMBean.java | 224 + .../hotswap/service/IHotSwapServiceMBean.java | 28 + .../com/zfoo/hotswap/util/HotSwapUtils.java | 96 + .../src/main/resources/META-INF/MANIFEST.MF | 5 + .../com/zfoo/hotswap/ApplicationTest.java | 32 + .../java/com/zfoo/hotswap/HotswapClass.java | 17 + hotswap/src/test/resources/logback-test.xml | 43 + monitor/pom.xml | 234 + .../zfoo/monitor/model/DiskFileSystemVO.java | 111 + .../java/com/zfoo/monitor/model/MemoryVO.java | 97 + .../com/zfoo/monitor/model/MonitorVO.java | 156 + .../java/com/zfoo/monitor/model/SarVO.java | 172 + .../java/com/zfoo/monitor/model/UptimeVO.java | 124 + .../java/com/zfoo/monitor/util/OSUtils.java | 280 + .../com/zfoo/monitor/ApplicationTest.java | 120 + net/README.md | 35 + net/pom.xml | 330 + .../main/java/com/zfoo/net/NetContext.java | 161 + .../net/config/manager/ConfigManager.java | 100 + .../net/config/manager/IConfigManager.java | 33 + .../zfoo/net/config/model/ConsumerConfig.java | 77 + .../com/zfoo/net/config/model/HostConfig.java | 81 + .../zfoo/net/config/model/MonitorConfig.java | 90 + .../com/zfoo/net/config/model/NetConfig.java | 171 + .../zfoo/net/config/model/ProviderConfig.java | 107 + .../zfoo/net/config/model/RegistryConfig.java | 100 + .../AbstractConsumerLoadBalancer.java | 93 + .../ConsistentHashConsumerLoadBalancer.java | 113 + .../balancer/IConsumerLoadBalancer.java | 42 + .../balancer/RandomConsumerLoadBalancer.java | 51 + .../ShortestTimeConsumerLoadBalancer.java | 92 + .../consumer/event/ConsumerStartEvent.java | 51 + .../zfoo/net/consumer/registry/IRegistry.java | 57 + .../net/consumer/registry/RegisterVO.java | 181 + .../consumer/registry/ZookeeperRegistry.java | 622 + .../zfoo/net/consumer/service/Consumer.java | 117 + .../zfoo/net/consumer/service/IConsumer.java | 39 + .../com/zfoo/net/core/AbstractClient.java | 94 + .../com/zfoo/net/core/AbstractServer.java | 140 + .../main/java/com/zfoo/net/core/IClient.java | 26 + .../main/java/com/zfoo/net/core/IServer.java | 26 + .../zfoo/net/core/gateway/GatewayServer.java | 68 + .../core/gateway/IGatewayLoadBalancer.java | 26 + .../core/gateway/WebsocketGatewayServer.java | 77 + .../gateway/WebsocketSslGatewayServer.java | 92 + .../net/core/gateway/model/AuthUidAsk.java | 67 + .../gateway/model/AuthUidToGatewayCheck.java | 52 + .../model/AuthUidToGatewayConfirm.java | 48 + .../gateway/model/AuthUidToGatewayEvent.java | 49 + .../model/GatewaySessionInactiveAsk.java | 67 + .../model/GatewaySessionInactiveEvent.java | 49 + .../model/GatewaySynchronizeSidAsk.java | 65 + .../java/com/zfoo/net/core/tcp/TcpClient.java | 57 + .../java/com/zfoo/net/core/tcp/TcpServer.java | 54 + .../tcp/model/ServerSessionInactiveEvent.java | 40 + .../net/core/websocket/WebsocketServer.java | 69 + .../dispatcher/manager/IPacketDispatcher.java | 66 + .../dispatcher/manager/PacketDispatcher.java | 414 + .../dispatcher/model/anno/PacketReceiver.java | 26 + .../dispatcher/model/answer/AsyncAnswer.java | 75 + .../dispatcher/model/answer/IAsyncAnswer.java | 33 + .../dispatcher/model/answer/ISyncAnswer.java | 35 + .../dispatcher/model/answer/SyncAnswer.java | 45 + .../exception/ErrorResponseException.java | 28 + .../model/exception/NetTimeOutException.java | 26 + .../UnexpectedProtocolException.java | 26 + .../net/dispatcher/model/vo/EnhanceUtils.java | 96 + .../dispatcher/model/vo/IPacketReceiver.java | 28 + .../model/vo/PacketReceiverDefinition.java | 98 + .../net/handler/BaseDispatcherHandler.java | 73 + .../net/handler/ClientDispatcherHandler.java | 61 + .../net/handler/GatewayDispatcherHandler.java | 132 + .../net/handler/ServerDispatcherHandler.java | 54 + .../codec/tcp/TcpPacketCodecHandler.java | 99 + .../websocket/WebSocketCodecHandler.java | 111 + .../net/handler/idle/ClientIdleHandler.java | 49 + .../net/handler/idle/ServerIdleHandler.java | 44 + .../com/zfoo/net/packet/common/Error.java | 102 + .../com/zfoo/net/packet/common/Heartbeat.java | 40 + .../com/zfoo/net/packet/common/Message.java | 83 + .../com/zfoo/net/packet/common/PairLS.java | 62 + .../com/zfoo/net/packet/common/PairLong.java | 65 + .../zfoo/net/packet/common/PairString.java | 57 + .../java/com/zfoo/net/packet/common/Ping.java | 35 + .../java/com/zfoo/net/packet/common/Pong.java | 49 + .../com/zfoo/net/packet/common/TripleLSS.java | 71 + .../zfoo/net/packet/common/TripleLong.java | 71 + .../zfoo/net/packet/common/TripleString.java | 66 + .../net/packet/model/DecodedPacketInfo.java | 59 + .../net/packet/model/EncodedPacketInfo.java | 84 + .../packet/model/GatewayPacketAttachment.java | 151 + .../net/packet/model/IPacketAttachment.java | 33 + .../net/packet/model/NoAnswerAttachment.java | 60 + .../packet/model/PacketAttachmentType.java | 72 + .../packet/model/SignalPacketAttachment.java | 135 + .../net/packet/service/IPacketService.java | 34 + .../net/packet/service/PacketService.java | 151 + .../com/zfoo/net/schema/NamespaceHandler.java | 31 + .../zfoo/net/schema/NetDefinitionParser.java | 237 + .../com/zfoo/net/schema/NetProcessor.java | 35 + .../net/session/manager/ISessionManager.java | 47 + .../net/session/manager/SessionManager.java | 133 + .../zfoo/net/session/model/AttributeType.java | 45 + .../com/zfoo/net/session/model/Session.java | 127 + .../java/com/zfoo/net/task/ITaskManager.java | 29 + .../java/com/zfoo/net/task/TaskManager.java | 112 + .../net/task/model/AbstractTaskDispatch.java | 37 + .../model/ConsistentHashTaskDispatch.java | 43 + .../zfoo/net/task/model/ITaskDispatch.java | 26 + .../net/task/model/RandomTaskDispatch.java | 37 + .../com/zfoo/net/task/model/ReceiveTask.java | 65 + .../com/zfoo/net/task/model/SafeRunnable.java | 27 + .../net/task/model/SessionIdTaskDispatch.java | 41 + .../java/com/zfoo/net/util/SessionUtils.java | 62 + .../java/com/zfoo/net/util/SimpleCache.java | 197 + .../java/com/zfoo/net/util/SingleCache.java | 78 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 1 + net/src/main/resources/net-1.0.xsd | 82 + .../com/zfoo/net/config/RegistryTest.java | 107 + .../core/csharp/ServerPacketController.java | 38 + .../com/zfoo/net/core/csharp/ServerTest.java | 45 + .../zfoo/net/core/gateway/GatewayTest.java | 97 + .../controller/GatewayProviderController.java | 43 + .../zfoo/net/core/provider/ProviderTest.java | 148 + .../controller/ProviderController.java | 42 + .../net/core/tcp/client/TcpClientTest.java | 133 + .../controller/TcpClientPacketController.java | 36 + .../net/core/tcp/server/TcpServerTest.java | 80 + .../controller/TcpServerPacketController.java | 123 + .../net/core/websocket/client/client.html | 24 + .../websocket/server/WebsocketServerTest.java | 46 + .../controller/WebsocketPacketController.java | 39 + .../java/com/zfoo/net/packet/CM_Array.java | 73 + .../com/zfoo/net/packet/CM_AsyncMess0.java | 50 + .../com/zfoo/net/packet/CM_AsyncMess1.java | 50 + .../java/com/zfoo/net/packet/CM_Float.java | 94 + .../test/java/com/zfoo/net/packet/CM_Int.java | 122 + .../java/com/zfoo/net/packet/CM_List.java | 117 + .../test/java/com/zfoo/net/packet/CM_Map.java | 110 + .../java/com/zfoo/net/packet/CM_Object.java | 72 + .../test/java/com/zfoo/net/packet/CM_Set.java | 109 + .../java/com/zfoo/net/packet/CM_SyncMess.java | 50 + .../java/com/zfoo/net/packet/ObjectA.java | 68 + .../java/com/zfoo/net/packet/ObjectB.java | 56 + .../com/zfoo/net/packet/SM_AsyncMess0.java | 52 + .../com/zfoo/net/packet/SM_AsyncMess1.java | 52 + .../java/com/zfoo/net/packet/SM_Float.java | 34 + .../test/java/com/zfoo/net/packet/SM_Int.java | 123 + .../java/com/zfoo/net/packet/SM_Object.java | 32 + .../java/com/zfoo/net/packet/SM_SyncMess.java | 52 + .../net/packet/csharp/CM_CSharpRequest.java | 108 + .../zfoo/net/packet/csharp/CSharpObjectA.java | 33 + .../zfoo/net/packet/csharp/CSharpObjectB.java | 32 + .../packet/gateway/CM_GatewayProvider.java | 50 + .../packet/gateway/SM_GatewayProvider.java | 49 + .../zfoo/net/packet/provider/CM_Provider.java | 50 + .../zfoo/net/packet/provider/SM_Provider.java | 49 + .../packet/websocket/CM_WebSocketPacket.java | 433 + .../packet/websocket/WebSocketObjectA.java | 67 + .../packet/websocket/WebSocketObjectB.java | 62 + .../com/zfoo/net/protocol/ProtocolTest.java | 254 + .../com/zfoo/net/session/SessionUtils.java | 57 + .../com/zfoo/net/util/SimpleCacheTest.java | 57 + net/src/test/resources/client_config.xml | 31 + net/src/test/resources/deploy-dev.properties | 5 + .../gateway/gateway_client_config.xml | 33 + .../gateway_consistent_session_config.xml | 41 + net/src/test/resources/logback-test.xml | 43 + net/src/test/resources/protocol.xml | 75 + .../consumer_consistent_session_config.xml | 36 + .../provider/consumer_random_config.xml | 35 + .../consumer_shortest_time_config.xml | 35 + .../resources/provider/provider_config.xml | 46 + net/src/test/resources/server_config.xml | 42 + orm/README.md | 87 + orm/pom.xml | 278 + .../main/java/com/zfoo/orm/OrmContext.java | 142 + .../com/zfoo/orm/manager/IOrmManager.java | 54 + .../java/com/zfoo/orm/manager/OrmManager.java | 254 + .../zfoo/orm/model/accessor/IAccessor.java | 48 + .../orm/model/accessor/MongodbAccessor.java | 147 + .../com/zfoo/orm/model/anno/EntityCache.java | 31 + .../orm/model/anno/EntityCachesInjection.java | 26 + .../main/java/com/zfoo/orm/model/anno/Id.java | 26 + .../java/com/zfoo/orm/model/anno/Index.java | 29 + .../com/zfoo/orm/model/anno/IndexText.java | 29 + .../com/zfoo/orm/model/anno/Persister.java | 29 + .../zfoo/orm/model/cache/EntityCaches.java | 324 + .../zfoo/orm/model/cache/IEntityCaches.java | 67 + .../zfoo/orm/model/config/CacheStrategy.java | 56 + .../zfoo/orm/model/config/CachesConfig.java | 33 + .../com/zfoo/orm/model/config/HostConfig.java | 69 + .../com/zfoo/orm/model/config/OrmConfig.java | 71 + .../orm/model/config/PersisterStrategy.java | 58 + .../orm/model/config/PersisterTypeEnum.java | 60 + .../orm/model/config/PersistersConfig.java | 33 + .../com/zfoo/orm/model/entity/IEntity.java | 38 + .../model/persister/AbstractOrmPersister.java | 35 + .../orm/model/persister/CronOrmPersister.java | 94 + .../orm/model/persister/IOrmPersister.java | 24 + .../com/zfoo/orm/model/persister/PNode.java | 68 + .../orm/model/persister/TimeOrmPersister.java | 61 + .../java/com/zfoo/orm/model/query/IQuery.java | 46 + .../zfoo/orm/model/query/MongodbQuery.java | 113 + .../java/com/zfoo/orm/model/query/Page.java | 113 + .../java/com/zfoo/orm/model/vo/EntityDef.java | 155 + .../java/com/zfoo/orm/model/vo/IndexDef.java | 57 + .../com/zfoo/orm/model/vo/IndexTextDef.java | 55 + .../com/zfoo/orm/schema/NamespaceHandler.java | 31 + .../zfoo/orm/schema/OrmDefinitionParser.java | 195 + .../com/zfoo/orm/schema/OrmProcessor.java | 81 + .../java/com/zfoo/orm/util/MongoIdUtils.java | 105 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 1 + orm/src/main/resources/orm-1.0.xsd | 84 + orm/src/test/java/com/zfoo/orm/TestUnit.java | 27 + .../com/zfoo/orm/accessor/AccessorTest.java | 80 + .../com/zfoo/orm/accessor/DeleteTest.java | 54 + .../com/zfoo/orm/accessor/IndexTextTest.java | 64 + .../com/zfoo/orm/accessor/InsertTest.java | 47 + .../zfoo/orm/accessor/TransactionTest.java | 81 + .../com/zfoo/orm/accessor/UpdateTest.java | 63 + .../zfoo/orm/converter/MysqlToMongoDB.java | 85 + .../java/com/zfoo/orm/entity/MailEnt.java | 84 + .../java/com/zfoo/orm/entity/UserEntity.java | 145 + .../java/com/zfoo/orm/query/PageTest.java | 42 + .../java/com/zfoo/orm/query/QueryTest.java | 47 + .../test/java/com/zfoo/orm/test/OrmTest.java | 50 + .../java/com/zfoo/orm/test/UserManager.java | 35 + .../com/zfoo/orm/util/MongoIdUtilsTest.java | 124 + orm/src/test/resources/application.xml | 48 + orm/src/test/resources/deploy-dev.properties | 5 + orm/src/test/resources/logback-test.xml | 44 + pom.xml | 249 + protocol/README.md | 61 + protocol/pom.xml | 275 + .../main/java/com/zfoo/protocol/IPacket.java | 31 + .../com/zfoo/protocol/ProtocolManager.java | 574 + .../zfoo/protocol/buffer/ByteBufUtils.java | 1067 + .../zfoo/protocol/collection/ArrayUtils.java | 180 + .../protocol/collection/CollectionUtils.java | 312 + .../collection/ConcurrentArrayList.java | 285 + .../collection/ConcurrentHashSet.java | 65 + .../collection/model/NaturalComparator.java | 72 + .../protocol/collection/tree/GeneralTree.java | 90 + .../protocol/collection/tree/TreeNode.java | 224 + .../protocol/exception/AssertException.java | 31 + .../protocol/exception/ExceptionUtils.java | 65 + .../protocol/exception/POJOException.java | 39 + .../zfoo/protocol/exception/RunException.java | 43 + .../protocol/exception/UnknownException.java | 43 + .../protocol/generate/GenerateOperation.java | 94 + .../generate/GenerateProtocolDocument.java | 184 + .../generate/GenerateProtocolFile.java | 113 + .../generate/GenerateProtocolPath.java | 120 + .../java/com/zfoo/protocol/model/Pair.java | 85 + .../com/zfoo/protocol/model/Quaternion.java | 54 + .../java/com/zfoo/protocol/model/Triple.java | 49 + .../protocol/registration/EnhanceUtils.java | 259 + .../registration/IProtocolRegistration.java | 37 + .../protocol/registration/ProtocolModule.java | 127 + .../registration/ProtocolRegistration.java | 144 + .../registration/field/ArrayField.java | 52 + .../registration/field/BaseField.java | 40 + .../field/IFieldRegistration.java | 28 + .../registration/field/ListField.java | 50 + .../protocol/registration/field/MapField.java | 57 + .../field/ObjectProtocolField.java | 44 + .../protocol/registration/field/SetField.java | 50 + .../protocol/serializer/ArraySerializer.java | 81 + .../serializer/BooleanSerializer.java | 49 + .../protocol/serializer/ByteSerializer.java | 51 + .../protocol/serializer/CharSerializer.java | 50 + .../protocol/serializer/DoubleSerializer.java | 49 + .../protocol/serializer/FloatSerializer.java | 50 + .../protocol/serializer/GenerateUtils.java | 37 + .../zfoo/protocol/serializer/ISerializer.java | 29 + .../protocol/serializer/IntSerializer.java | 50 + .../protocol/serializer/ListSerializer.java | 80 + .../protocol/serializer/LongSerializer.java | 49 + .../protocol/serializer/MapSerializer.java | 87 + .../serializer/ObjectProtocolSerializer.java | 58 + .../protocol/serializer/SetSerializer.java | 83 + .../protocol/serializer/ShortSerializer.java | 50 + .../protocol/serializer/StringSerializer.java | 46 + .../serializer/cs/CsArraySerializer.java | 110 + .../serializer/cs/CsBooleanSerializer.java | 44 + .../serializer/cs/CsByteSerializer.java | 45 + .../serializer/cs/CsCharSerializer.java | 45 + .../serializer/cs/CsDoubleSerializer.java | 45 + .../serializer/cs/CsFloatSerializer.java | 45 + .../serializer/cs/CsIntSerializer.java | 44 + .../serializer/cs/CsListSerializer.java | 112 + .../serializer/cs/CsLongSerializer.java | 45 + .../serializer/cs/CsMapSerializer.java | 122 + .../cs/CsObjectProtocolSerializer.java | 56 + .../serializer/cs/CsSetSerializer.java | 105 + .../serializer/cs/CsShortSerializer.java | 45 + .../serializer/cs/CsStringSerializer.java | 46 + .../serializer/cs/GenerateCsUtils.java | 384 + .../protocol/serializer/cs/ICsSerializer.java | 30 + .../enhance/EnhanceArraySerializer.java | 189 + .../enhance/EnhanceBooleanSerializer.java | 51 + .../enhance/EnhanceByteSerializer.java | 50 + .../enhance/EnhanceCharSerializer.java | 50 + .../enhance/EnhanceDoubleSerializer.java | 50 + .../enhance/EnhanceFloatSerializer.java | 50 + .../enhance/EnhanceIntSerializer.java | 50 + .../enhance/EnhanceListSerializer.java | 109 + .../enhance/EnhanceLongSerializer.java | 50 + .../enhance/EnhanceMapSerializer.java | 150 + .../EnhanceObjectProtocolSerializer.java | 52 + .../enhance/EnhanceSetSerializer.java | 108 + .../enhance/EnhanceShortSerializer.java | 50 + .../enhance/EnhanceStringSerializer.java | 41 + .../enhance/IEnhanceSerializer.java | 39 + .../serializer/js/GenerateJsUtils.java | 277 + .../protocol/serializer/js/IJsSerializer.java | 30 + .../serializer/js/JsArraySerializer.java | 87 + .../serializer/js/JsBooleanSerializer.java | 45 + .../serializer/js/JsByteSerializer.java | 45 + .../serializer/js/JsCharSerializer.java | 43 + .../serializer/js/JsDoubleSerializer.java | 45 + .../serializer/js/JsFloatSerializer.java | 45 + .../serializer/js/JsIntSerializer.java | 44 + .../serializer/js/JsListSerializer.java | 86 + .../serializer/js/JsLongSerializer.java | 44 + .../serializer/js/JsMapSerializer.java | 96 + .../js/JsObjectProtocolSerializer.java | 46 + .../serializer/js/JsSetSerializer.java | 86 + .../serializer/js/JsShortSerializer.java | 45 + .../serializer/js/JsStringSerializer.java | 43 + .../serializer/lua/GenerateLuaUtils.java | 301 + .../serializer/lua/ILuaSerializer.java | 30 + .../serializer/lua/LuaArraySerializer.java | 87 + .../serializer/lua/LuaBooleanSerializer.java | 45 + .../serializer/lua/LuaByteSerializer.java | 45 + .../serializer/lua/LuaCharSerializer.java | 43 + .../serializer/lua/LuaDoubleSerializer.java | 45 + .../serializer/lua/LuaFloatSerializer.java | 45 + .../serializer/lua/LuaIntSerializer.java | 44 + .../serializer/lua/LuaListSerializer.java | 86 + .../serializer/lua/LuaLongSerializer.java | 44 + .../serializer/lua/LuaMapSerializer.java | 96 + .../lua/LuaObjectProtocolSerializer.java | 45 + .../serializer/lua/LuaSetSerializer.java | 87 + .../serializer/lua/LuaShortSerializer.java | 45 + .../serializer/lua/LuaStringSerializer.java | 43 + .../zfoo/protocol/util/AssertionUtils.java | 239 + .../com/zfoo/protocol/util/ClassUtils.java | 239 + .../com/zfoo/protocol/util/FileUtils.java | 554 + .../java/com/zfoo/protocol/util/IOUtils.java | 101 + .../com/zfoo/protocol/util/JsonUtils.java | 217 + .../zfoo/protocol/util/ReflectionUtils.java | 317 + .../com/zfoo/protocol/util/StringUtils.java | 478 + .../protocol/xml/XmlModuleDefinition.java | 71 + .../protocol/xml/XmlProtocolDefinition.java | 55 + .../com/zfoo/protocol/xml/XmlProtocols.java | 41 + .../cs/Buffer/BigEndianByteBuffer.cs | 74 + .../main/resources/cs/Buffer/ByteBuffer.cs | 561 + .../cs/Buffer/LittleEndianByteBuffer.cs | 56 + protocol/src/main/resources/cs/IPacket.cs | 7 + .../resources/cs/IProtocolRegistration.cs | 12 + .../src/main/resources/cs/ProtocolManager.cs | 69 + protocol/src/main/resources/go/ByteBuffer.go | 407 + .../src/main/resources/js/ProtocolManager.js | 26 + .../main/resources/js/buffer/ByteBuffer.js | 340 + protocol/src/main/resources/js/buffer/long.js | 1325 + .../src/main/resources/js/buffer/longbits.js | 184 + .../main/resources/lua/Buffer/ByteBuffer.lua | 453 + .../src/main/resources/lua/Buffer/Long.lua | 542 + .../main/resources/lua/ProtocolManager.lua | 53 + .../java/com/zfoo/protocol/SpeedTest.java | 619 + .../java/com/zfoo/protocol/javassist/A.java | 28 + .../java/com/zfoo/protocol/javassist/B.java | 51 + .../java/com/zfoo/protocol/javassist/C.java | 51 + .../java/com/zfoo/protocol/javassist/D.java | 28 + .../com/zfoo/protocol/javassist/Hello.java | 24 + .../com/zfoo/protocol/javassist/IDGet.java | 32 + .../protocol/javassist/JavassistTest.java | 219 + .../zfoo/protocol/packet/ComplexObject.java | 596 + .../zfoo/protocol/packet/NormalObject.java | 209 + .../com/zfoo/protocol/packet/ObjectA.java | 78 + .../com/zfoo/protocol/packet/ObjectB.java | 56 + .../zfoo/protocol/packet/ProtobufObject.java | 23534 ++++++++++++++++ .../zfoo/protocol/packet/SimpleObject.java | 50 + .../zfoo/protocol/util/ByteBufUtilsTest.java | 166 + .../src/test/resources/ComplexObject.bytes | Bin 0 -> 2214 bytes .../CsProtocol/Buffer/BigEndianByteBuffer.cs | 74 + .../csTest/CsProtocol/Buffer/ByteBuffer.cs | 559 + .../Buffer/LittleEndianByteBuffer.cs | 56 + .../resources/csTest/CsProtocol/IPacket.cs | 7 + .../CsProtocol/IProtocolRegistration.cs | 12 + .../csTest/CsProtocol/Packet/ComplexObject.cs | 1441 + .../csTest/CsProtocol/Packet/NormalObject.cs | 537 + .../csTest/CsProtocol/Packet/ObjectA.cs | 94 + .../csTest/CsProtocol/Packet/ObjectB.cs | 60 + .../csTest/CsProtocol/Packet/SimpleObject.cs | 65 + .../csTest/CsProtocol/ProtocolManager.cs | 69 + .../test/resources/csTest/CsProtocolTest.cs | 193 + .../src/test/resources/jsTest/.eslintrc.js | 5 + .../jsTest/jsProtocol/ProtocolManager.js | 42 + .../jsTest/jsProtocol/buffer/ByteBuffer.js | 340 + .../jsTest/jsProtocol/buffer/long.js | 1325 + .../jsTest/jsProtocol/buffer/longbits.js | 184 + .../jsTest/jsProtocol/packet/ComplexObject.js | 971 + .../jsTest/jsProtocol/packet/NormalObject.js | 360 + .../jsTest/jsProtocol/packet/ObjectA.js | 56 + .../jsTest/jsProtocol/packet/ObjectB.js | 31 + .../jsTest/jsProtocol/packet/SimpleObject.js | 35 + .../resources/jsTest/jsProtocolTest.spec.js | 107 + protocol/src/test/resources/logback-test.xml | 43 + .../luaTest/LuaProtocol/Buffer/ByteBuffer.lua | 448 + .../luaTest/LuaProtocol/Buffer/Long.lua | 542 + .../LuaProtocol/Packet/ComplexObject.lua | 980 + .../LuaProtocol/Packet/NormalObject.lua | 369 + .../luaTest/LuaProtocol/Packet/ObjectA.lua | 65 + .../luaTest/LuaProtocol/Packet/ObjectB.lua | 39 + .../LuaProtocol/Packet/SimpleObject.lua | 43 + .../luaTest/LuaProtocol/ProtocolManager.lua | 68 + .../test/resources/luaTest/LuaProtocolTest.cs | 47 + .../resources/luaTest/LuaProtocolTest.lua | 137 + protocol/src/test/resources/speed.proto | 169 + scheduler/README.md | 35 + scheduler/pom.xml | 243 + .../com/zfoo/scheduler/SchedulerContext.java | 119 + .../scheduler/manager/ISchedulerManager.java | 40 + .../scheduler/manager/SchedulerManager.java | 240 + .../zfoo/scheduler/model/anno/Scheduler.java | 29 + .../zfoo/scheduler/model/vo/EnhanceUtils.java | 83 + .../zfoo/scheduler/model/vo/IScheduler.java | 25 + .../scheduler/model/vo/ReflectScheduler.java | 49 + .../scheduler/model/vo/RunnableScheduler.java | 34 + .../model/vo/SchedulerDefinition.java | 83 + .../scheduler/schema/NamespaceHandler.java | 30 + .../schema/SchedulerDefinitionParser.java | 55 + .../schema/SchedulerRegisterProcessor.java | 38 + .../com/zfoo/scheduler/util/TimeUtils.java | 373 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 1 + .../src/main/resources/scheduler-1.0.xsd | 19 + .../com/zfoo/scheduler/ApplicationTest.java | 40 + .../zfoo/scheduler/SchedulerController.java | 40 + .../zfoo/scheduler/util/TimeUtilsTest.java | 96 + scheduler/src/test/resources/application.xml | 23 + scheduler/src/test/resources/logback-test.xml | 44 + storage/pom.xml | 256 + .../java/com/zfoo/storage/StorageContext.java | 110 + .../interpreter/ExcelResourceReader.java | 168 + .../storage/interpreter/IResourceReader.java | 32 + .../zfoo/storage/manager/IStorageManager.java | 47 + .../zfoo/storage/manager/StorageManager.java | 192 + .../java/com/zfoo/storage/model/anno/Id.java | 30 + .../com/zfoo/storage/model/anno/Index.java | 32 + .../zfoo/storage/model/anno/ResInjection.java | 34 + .../com/zfoo/storage/model/anno/Resource.java | 28 + .../storage/model/config/StorageConfig.java | 61 + .../java/com/zfoo/storage/model/vo/IdDef.java | 56 + .../com/zfoo/storage/model/vo/IndexDef.java | 79 + .../zfoo/storage/model/vo/ResourceDef.java | 41 + .../com/zfoo/storage/model/vo/Storage.java | 150 + .../zfoo/storage/schema/NamespaceHandler.java | 28 + .../storage/schema/ResInjectionProcessor.java | 94 + .../schema/StorageDefinitionParser.java | 128 + .../strategy/JsonToArrayConverter.java | 64 + .../storage/strategy/JsonToMapConverter.java | 46 + .../strategy/JsonToObjectConverter.java | 64 + .../strategy/StringToClassConverter.java | 50 + .../strategy/StringToDateConverter.java | 40 + .../strategy/StringToMapConverter.java | 30 + .../java/com/zfoo/storage/util/CellUtils.java | 105 + .../main/resources/META-INF/spring.handlers | 1 + .../main/resources/META-INF/spring.schemas | 1 + storage/src/main/resources/storage-1.0.xsd | 64 + .../com/zfoo/storage/ApplicationTest.java | 64 + .../java/com/zfoo/storage/StudentManager.java | 31 + .../storage/conversion/ConversionTest.java | 93 + .../com/zfoo/storage/excel/ExcelTest.java | 98 + .../zfoo/storage/pathmatch/PathMatchTest.java | 40 + .../storage/resource/StudentResource.java | 73 + .../java/com/zfoo/storage/resource/User.java | 42 + storage/src/test/resources/application.xml | 54 + .../test/resources/excel/StudentResource.xls | Bin 0 -> 22016 bytes storage/src/test/resources/logback-test.xml | 44 + util/pom.xml | 211 + .../src/main/java/com/zfoo/util/DomUtils.java | 174 + .../main/java/com/zfoo/util/EnumUtils.java | 45 + .../main/java/com/zfoo/util/ThreadUtils.java | 75 + .../zfoo/util/captcha/ArithmeticCaptcha.java | 136 + .../com/zfoo/util/captcha/GifCaptcha.java | 148 + .../com/zfoo/util/captcha/PngCaptcha.java | 82 + .../com/zfoo/util/captcha/gif/Encoder.java | 344 + .../com/zfoo/util/captcha/gif/GifEncoder.java | 513 + .../java/com/zfoo/util/captcha/gif/Quant.java | 474 + .../util/captcha/model/AbstractCaptcha.java | 285 + .../util/captcha/model/CaptchaCharEnum.java | 39 + .../util/captcha/model/CaptchaFontEnum.java | 73 + .../com/zfoo/util/math/Combinatorics.java | 55 + .../com/zfoo/util/math/ConsistentHash.java | 76 + .../java/com/zfoo/util/math/HashUtils.java | 63 + .../java/com/zfoo/util/math/NumberUtils.java | 998 + .../com/zfoo/util/math/RandomSelector.java | 70 + .../java/com/zfoo/util/math/RandomUtils.java | 421 + .../java/com/zfoo/util/math/dfa/WordTree.java | 220 + .../zfoo/util/math/lexer/LexicalAnalysis.java | 173 + .../java/com/zfoo/util/net/HostAndPort.java | 120 + .../main/java/com/zfoo/util/net/NetUtils.java | 440 + .../java/com/zfoo/util/security/AesUtils.java | 93 + .../java/com/zfoo/util/security/IdUtils.java | 58 + .../java/com/zfoo/util/security/MD5Utils.java | 88 + .../java/com/zfoo/util/security/ZipUtils.java | 72 + util/src/main/resources/captcha/actionj.ttf | Bin 0 -> 34944 bytes util/src/main/resources/captcha/epilog.ttf | Bin 0 -> 30136 bytes util/src/main/resources/captcha/fresnel.ttf | Bin 0 -> 75560 bytes util/src/main/resources/captcha/headache.ttf | Bin 0 -> 45292 bytes util/src/main/resources/captcha/lexo.ttf | Bin 0 -> 77600 bytes util/src/main/resources/captcha/prefix.ttf | Bin 0 -> 99016 bytes util/src/main/resources/captcha/progbot.ttf | Bin 0 -> 86956 bytes util/src/main/resources/captcha/ransom.ttf | Bin 0 -> 35812 bytes util/src/main/resources/captcha/robot.ttf | Bin 0 -> 45072 bytes util/src/main/resources/captcha/scandal.ttf | Bin 0 -> 41320 bytes util/src/test/java/MainTest.java | 33 + .../com/zfoo/util/AssertionUtilsTest.java | 29 + .../java/com/zfoo/util/ClassUtilTest.java | 62 + .../test/java/com/zfoo/util/DomUtilsTest.java | 103 + .../java/com/zfoo/util/EnumUtilsTest.java | 37 + .../test/java/com/zfoo/util/FileUtilTest.java | 84 + .../test/java/com/zfoo/util/JsonUtilTest.java | 190 + .../test/java/com/zfoo/util/NetUtilsTest.java | 82 + .../java/com/zfoo/util/ReflectUtilTest.java | 57 + .../java/com/zfoo/util/StringUtilTest.java | 49 + .../com/zfoo/util/captcha/CaptchaTest.java | 84 + .../util/collection/CollectionUtilsTest.java | 76 + .../zfoo/util/collection/GeneralTreeTest.java | 39 + .../zfoo/util/math/ConsistentHashTest.java | 92 + .../com/zfoo/util/math/dfa/WordTreeTest.java | 44 + .../util/math/lexer/LexicalAnalysisTest.java | 37 + .../com/zfoo/util/security/AesUtilsTest.java | 31 + .../com/zfoo/util/security/IdUtilsTest.java | 34 + .../com/zfoo/util/security/MD5UtilsTest.java | 32 + .../java/com/zfoo/util/security/ZipTest.java | 48 + 573 files changed, 84513 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 deploy.sh create mode 100644 event/pom.xml create mode 100644 event/src/main/java/com/zfoo/event/EventContext.java create mode 100644 event/src/main/java/com/zfoo/event/manager/EventBus.java create mode 100644 event/src/main/java/com/zfoo/event/model/anno/EventReceiver.java create mode 100644 event/src/main/java/com/zfoo/event/model/event/AppStartEvent.java create mode 100644 event/src/main/java/com/zfoo/event/model/event/IEvent.java create mode 100644 event/src/main/java/com/zfoo/event/model/vo/EnhanceUtils.java create mode 100644 event/src/main/java/com/zfoo/event/model/vo/EventReceiverDefinition.java create mode 100644 event/src/main/java/com/zfoo/event/model/vo/IEventReceiver.java create mode 100644 event/src/main/java/com/zfoo/event/schema/EventDefinitionParser.java create mode 100644 event/src/main/java/com/zfoo/event/schema/EventRegisterProcessor.java create mode 100644 event/src/main/java/com/zfoo/event/schema/NamespaceHandler.java create mode 100644 event/src/main/resources/META-INF/spring.handlers create mode 100644 event/src/main/resources/META-INF/spring.schemas create mode 100644 event/src/main/resources/event-1.0.xsd create mode 100644 event/src/test/java/com/zfoo/event/ApplicationTest.java create mode 100644 event/src/test/java/com/zfoo/event/MyController1.java create mode 100644 event/src/test/java/com/zfoo/event/MyController2.java create mode 100644 event/src/test/java/com/zfoo/event/MyNoticeEvent.java create mode 100644 event/src/test/resources/application.xml create mode 100644 event/src/test/resources/logback-test.xml create mode 100644 event/tooltip/general-game-architect.jpg create mode 100644 hotswap/pom.xml create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/HotSwapContext.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/agent/HotSwapAgent.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/manager/HotSwapManager.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/manager/IHotSwapManager.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/model/ClassFileDef.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/service/HotSwapServiceMBean.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/service/IHotSwapServiceMBean.java create mode 100644 hotswap/src/main/java/com/zfoo/hotswap/util/HotSwapUtils.java create mode 100644 hotswap/src/main/resources/META-INF/MANIFEST.MF create mode 100644 hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java create mode 100644 hotswap/src/test/java/com/zfoo/hotswap/HotswapClass.java create mode 100644 hotswap/src/test/resources/logback-test.xml create mode 100644 monitor/pom.xml create mode 100644 monitor/src/main/java/com/zfoo/monitor/model/DiskFileSystemVO.java create mode 100644 monitor/src/main/java/com/zfoo/monitor/model/MemoryVO.java create mode 100644 monitor/src/main/java/com/zfoo/monitor/model/MonitorVO.java create mode 100644 monitor/src/main/java/com/zfoo/monitor/model/SarVO.java create mode 100644 monitor/src/main/java/com/zfoo/monitor/model/UptimeVO.java create mode 100644 monitor/src/main/java/com/zfoo/monitor/util/OSUtils.java create mode 100644 monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java create mode 100644 net/README.md create mode 100644 net/pom.xml create mode 100644 net/src/main/java/com/zfoo/net/NetContext.java create mode 100644 net/src/main/java/com/zfoo/net/config/manager/ConfigManager.java create mode 100644 net/src/main/java/com/zfoo/net/config/manager/IConfigManager.java create mode 100644 net/src/main/java/com/zfoo/net/config/model/ConsumerConfig.java create mode 100644 net/src/main/java/com/zfoo/net/config/model/HostConfig.java create mode 100644 net/src/main/java/com/zfoo/net/config/model/MonitorConfig.java create mode 100644 net/src/main/java/com/zfoo/net/config/model/NetConfig.java create mode 100644 net/src/main/java/com/zfoo/net/config/model/ProviderConfig.java create mode 100644 net/src/main/java/com/zfoo/net/config/model/RegistryConfig.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/balancer/AbstractConsumerLoadBalancer.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/balancer/ConsistentHashConsumerLoadBalancer.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/balancer/IConsumerLoadBalancer.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/balancer/RandomConsumerLoadBalancer.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/balancer/ShortestTimeConsumerLoadBalancer.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/event/ConsumerStartEvent.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/registry/IRegistry.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/registry/RegisterVO.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/registry/ZookeeperRegistry.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/service/Consumer.java create mode 100644 net/src/main/java/com/zfoo/net/consumer/service/IConsumer.java create mode 100644 net/src/main/java/com/zfoo/net/core/AbstractClient.java create mode 100644 net/src/main/java/com/zfoo/net/core/AbstractServer.java create mode 100644 net/src/main/java/com/zfoo/net/core/IClient.java create mode 100644 net/src/main/java/com/zfoo/net/core/IServer.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/GatewayServer.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/IGatewayLoadBalancer.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/WebsocketGatewayServer.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/WebsocketSslGatewayServer.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidAsk.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayCheck.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayConfirm.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayEvent.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveAsk.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveEvent.java create mode 100644 net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySynchronizeSidAsk.java create mode 100644 net/src/main/java/com/zfoo/net/core/tcp/TcpClient.java create mode 100644 net/src/main/java/com/zfoo/net/core/tcp/TcpServer.java create mode 100644 net/src/main/java/com/zfoo/net/core/tcp/model/ServerSessionInactiveEvent.java create mode 100644 net/src/main/java/com/zfoo/net/core/websocket/WebsocketServer.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/manager/IPacketDispatcher.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/manager/PacketDispatcher.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/anno/PacketReceiver.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/answer/AsyncAnswer.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/answer/IAsyncAnswer.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/answer/ISyncAnswer.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/answer/SyncAnswer.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/exception/ErrorResponseException.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/exception/NetTimeOutException.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/exception/UnexpectedProtocolException.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/vo/EnhanceUtils.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/vo/IPacketReceiver.java create mode 100644 net/src/main/java/com/zfoo/net/dispatcher/model/vo/PacketReceiverDefinition.java create mode 100644 net/src/main/java/com/zfoo/net/handler/BaseDispatcherHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/ClientDispatcherHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/GatewayDispatcherHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/ServerDispatcherHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/codec/tcp/TcpPacketCodecHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/codec/websocket/WebSocketCodecHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/idle/ClientIdleHandler.java create mode 100644 net/src/main/java/com/zfoo/net/handler/idle/ServerIdleHandler.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/Error.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/Heartbeat.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/Message.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/PairLS.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/PairLong.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/PairString.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/Ping.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/Pong.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/TripleLSS.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/TripleLong.java create mode 100644 net/src/main/java/com/zfoo/net/packet/common/TripleString.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/DecodedPacketInfo.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/EncodedPacketInfo.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/GatewayPacketAttachment.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/IPacketAttachment.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/NoAnswerAttachment.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/PacketAttachmentType.java create mode 100644 net/src/main/java/com/zfoo/net/packet/model/SignalPacketAttachment.java create mode 100644 net/src/main/java/com/zfoo/net/packet/service/IPacketService.java create mode 100644 net/src/main/java/com/zfoo/net/packet/service/PacketService.java create mode 100644 net/src/main/java/com/zfoo/net/schema/NamespaceHandler.java create mode 100644 net/src/main/java/com/zfoo/net/schema/NetDefinitionParser.java create mode 100644 net/src/main/java/com/zfoo/net/schema/NetProcessor.java create mode 100644 net/src/main/java/com/zfoo/net/session/manager/ISessionManager.java create mode 100644 net/src/main/java/com/zfoo/net/session/manager/SessionManager.java create mode 100644 net/src/main/java/com/zfoo/net/session/model/AttributeType.java create mode 100644 net/src/main/java/com/zfoo/net/session/model/Session.java create mode 100644 net/src/main/java/com/zfoo/net/task/ITaskManager.java create mode 100644 net/src/main/java/com/zfoo/net/task/TaskManager.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/AbstractTaskDispatch.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/ConsistentHashTaskDispatch.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/ITaskDispatch.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/RandomTaskDispatch.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/ReceiveTask.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/SafeRunnable.java create mode 100644 net/src/main/java/com/zfoo/net/task/model/SessionIdTaskDispatch.java create mode 100644 net/src/main/java/com/zfoo/net/util/SessionUtils.java create mode 100644 net/src/main/java/com/zfoo/net/util/SimpleCache.java create mode 100644 net/src/main/java/com/zfoo/net/util/SingleCache.java create mode 100644 net/src/main/resources/META-INF/spring.handlers create mode 100644 net/src/main/resources/META-INF/spring.schemas create mode 100644 net/src/main/resources/net-1.0.xsd create mode 100644 net/src/test/java/com/zfoo/net/config/RegistryTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/csharp/ServerPacketController.java create mode 100644 net/src/test/java/com/zfoo/net/core/csharp/ServerTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/gateway/GatewayTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/gateway/controller/GatewayProviderController.java create mode 100644 net/src/test/java/com/zfoo/net/core/provider/ProviderTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/provider/controller/ProviderController.java create mode 100644 net/src/test/java/com/zfoo/net/core/tcp/client/TcpClientTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/tcp/client/controller/TcpClientPacketController.java create mode 100644 net/src/test/java/com/zfoo/net/core/tcp/server/TcpServerTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/tcp/server/controller/TcpServerPacketController.java create mode 100644 net/src/test/java/com/zfoo/net/core/websocket/client/client.html create mode 100644 net/src/test/java/com/zfoo/net/core/websocket/server/WebsocketServerTest.java create mode 100644 net/src/test/java/com/zfoo/net/core/websocket/server/controller/WebsocketPacketController.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_Array.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_AsyncMess0.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_AsyncMess1.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_Float.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_Int.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_List.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_Map.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_Object.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_Set.java create mode 100644 net/src/test/java/com/zfoo/net/packet/CM_SyncMess.java create mode 100644 net/src/test/java/com/zfoo/net/packet/ObjectA.java create mode 100644 net/src/test/java/com/zfoo/net/packet/ObjectB.java create mode 100644 net/src/test/java/com/zfoo/net/packet/SM_AsyncMess0.java create mode 100644 net/src/test/java/com/zfoo/net/packet/SM_AsyncMess1.java create mode 100644 net/src/test/java/com/zfoo/net/packet/SM_Float.java create mode 100644 net/src/test/java/com/zfoo/net/packet/SM_Int.java create mode 100644 net/src/test/java/com/zfoo/net/packet/SM_Object.java create mode 100644 net/src/test/java/com/zfoo/net/packet/SM_SyncMess.java create mode 100644 net/src/test/java/com/zfoo/net/packet/csharp/CM_CSharpRequest.java create mode 100644 net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectA.java create mode 100644 net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectB.java create mode 100644 net/src/test/java/com/zfoo/net/packet/gateway/CM_GatewayProvider.java create mode 100644 net/src/test/java/com/zfoo/net/packet/gateway/SM_GatewayProvider.java create mode 100644 net/src/test/java/com/zfoo/net/packet/provider/CM_Provider.java create mode 100644 net/src/test/java/com/zfoo/net/packet/provider/SM_Provider.java create mode 100644 net/src/test/java/com/zfoo/net/packet/websocket/CM_WebSocketPacket.java create mode 100644 net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectA.java create mode 100644 net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectB.java create mode 100644 net/src/test/java/com/zfoo/net/protocol/ProtocolTest.java create mode 100644 net/src/test/java/com/zfoo/net/session/SessionUtils.java create mode 100644 net/src/test/java/com/zfoo/net/util/SimpleCacheTest.java create mode 100644 net/src/test/resources/client_config.xml create mode 100644 net/src/test/resources/deploy-dev.properties create mode 100644 net/src/test/resources/gateway/gateway_client_config.xml create mode 100644 net/src/test/resources/gateway/gateway_consistent_session_config.xml create mode 100644 net/src/test/resources/logback-test.xml create mode 100644 net/src/test/resources/protocol.xml create mode 100644 net/src/test/resources/provider/consumer_consistent_session_config.xml create mode 100644 net/src/test/resources/provider/consumer_random_config.xml create mode 100644 net/src/test/resources/provider/consumer_shortest_time_config.xml create mode 100644 net/src/test/resources/provider/provider_config.xml create mode 100644 net/src/test/resources/server_config.xml create mode 100644 orm/README.md create mode 100644 orm/pom.xml create mode 100644 orm/src/main/java/com/zfoo/orm/OrmContext.java create mode 100644 orm/src/main/java/com/zfoo/orm/manager/IOrmManager.java create mode 100644 orm/src/main/java/com/zfoo/orm/manager/OrmManager.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/accessor/IAccessor.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/accessor/MongodbAccessor.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/anno/EntityCache.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/anno/EntityCachesInjection.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/anno/Id.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/anno/Index.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/anno/IndexText.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/anno/Persister.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/cache/EntityCaches.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/cache/IEntityCaches.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/CacheStrategy.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/CachesConfig.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/HostConfig.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/OrmConfig.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/PersisterStrategy.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/PersisterTypeEnum.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/config/PersistersConfig.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/entity/IEntity.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/persister/AbstractOrmPersister.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/persister/CronOrmPersister.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/persister/IOrmPersister.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/persister/PNode.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/persister/TimeOrmPersister.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/query/IQuery.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/query/MongodbQuery.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/query/Page.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/vo/EntityDef.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/vo/IndexDef.java create mode 100644 orm/src/main/java/com/zfoo/orm/model/vo/IndexTextDef.java create mode 100644 orm/src/main/java/com/zfoo/orm/schema/NamespaceHandler.java create mode 100644 orm/src/main/java/com/zfoo/orm/schema/OrmDefinitionParser.java create mode 100644 orm/src/main/java/com/zfoo/orm/schema/OrmProcessor.java create mode 100644 orm/src/main/java/com/zfoo/orm/util/MongoIdUtils.java create mode 100644 orm/src/main/resources/META-INF/spring.handlers create mode 100644 orm/src/main/resources/META-INF/spring.schemas create mode 100644 orm/src/main/resources/orm-1.0.xsd create mode 100644 orm/src/test/java/com/zfoo/orm/TestUnit.java create mode 100644 orm/src/test/java/com/zfoo/orm/accessor/AccessorTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/accessor/DeleteTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/accessor/IndexTextTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/accessor/InsertTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/accessor/TransactionTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/accessor/UpdateTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/converter/MysqlToMongoDB.java create mode 100644 orm/src/test/java/com/zfoo/orm/entity/MailEnt.java create mode 100644 orm/src/test/java/com/zfoo/orm/entity/UserEntity.java create mode 100644 orm/src/test/java/com/zfoo/orm/query/PageTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/query/QueryTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/test/OrmTest.java create mode 100644 orm/src/test/java/com/zfoo/orm/test/UserManager.java create mode 100644 orm/src/test/java/com/zfoo/orm/util/MongoIdUtilsTest.java create mode 100644 orm/src/test/resources/application.xml create mode 100644 orm/src/test/resources/deploy-dev.properties create mode 100644 orm/src/test/resources/logback-test.xml create mode 100644 pom.xml create mode 100644 protocol/README.md create mode 100644 protocol/pom.xml create mode 100644 protocol/src/main/java/com/zfoo/protocol/IPacket.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/ProtocolManager.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/buffer/ByteBufUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/ArrayUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/CollectionUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentArrayList.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentHashSet.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/model/NaturalComparator.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/tree/GeneralTree.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/collection/tree/TreeNode.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/exception/AssertException.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/exception/ExceptionUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/exception/POJOException.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/exception/RunException.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/exception/UnknownException.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/generate/GenerateOperation.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolDocument.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolPath.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/model/Pair.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/model/Quaternion.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/model/Triple.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/EnhanceUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/IProtocolRegistration.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/ProtocolModule.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/ProtocolRegistration.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/ArrayField.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/BaseField.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/IFieldRegistration.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/ListField.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/MapField.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/ObjectProtocolField.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/registration/field/SetField.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/ArraySerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/BooleanSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/ByteSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/CharSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/DoubleSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/FloatSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/GenerateUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/ISerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/IntSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/ListSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/LongSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/MapSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/ObjectProtocolSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/SetSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/ShortSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/StringSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsArraySerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsBooleanSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsByteSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsCharSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsDoubleSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsFloatSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsIntSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsListSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsLongSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsMapSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsObjectProtocolSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsSetSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsShortSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsStringSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/GenerateCsUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/cs/ICsSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceArraySerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceBooleanSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceByteSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceCharSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceDoubleSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceFloatSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceIntSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceListSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceLongSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceMapSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceObjectProtocolSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceSetSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceShortSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceStringSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/enhance/IEnhanceSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/GenerateJsUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/IJsSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsArraySerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsBooleanSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsByteSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsCharSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsDoubleSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsFloatSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsIntSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsListSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsLongSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsMapSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsObjectProtocolSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsSetSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsShortSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/js/JsStringSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/GenerateLuaUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/ILuaSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaArraySerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaBooleanSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaByteSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaCharSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaDoubleSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaFloatSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaIntSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaListSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaLongSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaMapSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaObjectProtocolSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaSetSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaShortSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaStringSerializer.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/AssertionUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/IOUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/JsonUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/util/StringUtils.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/xml/XmlModuleDefinition.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocolDefinition.java create mode 100644 protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocols.java create mode 100644 protocol/src/main/resources/cs/Buffer/BigEndianByteBuffer.cs create mode 100644 protocol/src/main/resources/cs/Buffer/ByteBuffer.cs create mode 100644 protocol/src/main/resources/cs/Buffer/LittleEndianByteBuffer.cs create mode 100644 protocol/src/main/resources/cs/IPacket.cs create mode 100644 protocol/src/main/resources/cs/IProtocolRegistration.cs create mode 100644 protocol/src/main/resources/cs/ProtocolManager.cs create mode 100644 protocol/src/main/resources/go/ByteBuffer.go create mode 100644 protocol/src/main/resources/js/ProtocolManager.js create mode 100644 protocol/src/main/resources/js/buffer/ByteBuffer.js create mode 100644 protocol/src/main/resources/js/buffer/long.js create mode 100644 protocol/src/main/resources/js/buffer/longbits.js create mode 100644 protocol/src/main/resources/lua/Buffer/ByteBuffer.lua create mode 100644 protocol/src/main/resources/lua/Buffer/Long.lua create mode 100644 protocol/src/main/resources/lua/ProtocolManager.lua create mode 100644 protocol/src/test/java/com/zfoo/protocol/SpeedTest.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/A.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/B.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/C.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/D.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/Hello.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/IDGet.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/javassist/JavassistTest.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/packet/ComplexObject.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/packet/NormalObject.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/packet/ObjectA.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/packet/ObjectB.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/packet/ProtobufObject.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/packet/SimpleObject.java create mode 100644 protocol/src/test/java/com/zfoo/protocol/util/ByteBufUtilsTest.java create mode 100644 protocol/src/test/resources/ComplexObject.bytes create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Buffer/BigEndianByteBuffer.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Buffer/ByteBuffer.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Buffer/LittleEndianByteBuffer.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/IPacket.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/IProtocolRegistration.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Packet/ComplexObject.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Packet/NormalObject.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectA.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectB.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/Packet/SimpleObject.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocol/ProtocolManager.cs create mode 100644 protocol/src/test/resources/csTest/CsProtocolTest.cs create mode 100644 protocol/src/test/resources/jsTest/.eslintrc.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/ProtocolManager.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/buffer/ByteBuffer.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/buffer/long.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/buffer/longbits.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/packet/ComplexObject.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/packet/NormalObject.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectA.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectB.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocol/packet/SimpleObject.js create mode 100644 protocol/src/test/resources/jsTest/jsProtocolTest.spec.js create mode 100644 protocol/src/test/resources/logback-test.xml create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Buffer/ByteBuffer.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Buffer/Long.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Packet/ComplexObject.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Packet/NormalObject.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectA.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectB.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/Packet/SimpleObject.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocol/ProtocolManager.lua create mode 100644 protocol/src/test/resources/luaTest/LuaProtocolTest.cs create mode 100644 protocol/src/test/resources/luaTest/LuaProtocolTest.lua create mode 100644 protocol/src/test/resources/speed.proto create mode 100644 scheduler/README.md create mode 100644 scheduler/pom.xml create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/SchedulerContext.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/manager/ISchedulerManager.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/manager/SchedulerManager.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/model/anno/Scheduler.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/model/vo/EnhanceUtils.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/model/vo/IScheduler.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/model/vo/ReflectScheduler.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/model/vo/RunnableScheduler.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/model/vo/SchedulerDefinition.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/schema/NamespaceHandler.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerDefinitionParser.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerRegisterProcessor.java create mode 100644 scheduler/src/main/java/com/zfoo/scheduler/util/TimeUtils.java create mode 100644 scheduler/src/main/resources/META-INF/spring.handlers create mode 100644 scheduler/src/main/resources/META-INF/spring.schemas create mode 100644 scheduler/src/main/resources/scheduler-1.0.xsd create mode 100644 scheduler/src/test/java/com/zfoo/scheduler/ApplicationTest.java create mode 100644 scheduler/src/test/java/com/zfoo/scheduler/SchedulerController.java create mode 100644 scheduler/src/test/java/com/zfoo/scheduler/util/TimeUtilsTest.java create mode 100644 scheduler/src/test/resources/application.xml create mode 100644 scheduler/src/test/resources/logback-test.xml create mode 100644 storage/pom.xml create mode 100644 storage/src/main/java/com/zfoo/storage/StorageContext.java create mode 100644 storage/src/main/java/com/zfoo/storage/interpreter/ExcelResourceReader.java create mode 100644 storage/src/main/java/com/zfoo/storage/interpreter/IResourceReader.java create mode 100644 storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java create mode 100644 storage/src/main/java/com/zfoo/storage/manager/StorageManager.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/anno/Id.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/anno/Index.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/anno/Resource.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/config/StorageConfig.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/vo/IdDef.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/vo/IndexDef.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/vo/ResourceDef.java create mode 100644 storage/src/main/java/com/zfoo/storage/model/vo/Storage.java create mode 100644 storage/src/main/java/com/zfoo/storage/schema/NamespaceHandler.java create mode 100644 storage/src/main/java/com/zfoo/storage/schema/ResInjectionProcessor.java create mode 100644 storage/src/main/java/com/zfoo/storage/schema/StorageDefinitionParser.java create mode 100644 storage/src/main/java/com/zfoo/storage/strategy/JsonToArrayConverter.java create mode 100644 storage/src/main/java/com/zfoo/storage/strategy/JsonToMapConverter.java create mode 100644 storage/src/main/java/com/zfoo/storage/strategy/JsonToObjectConverter.java create mode 100644 storage/src/main/java/com/zfoo/storage/strategy/StringToClassConverter.java create mode 100644 storage/src/main/java/com/zfoo/storage/strategy/StringToDateConverter.java create mode 100644 storage/src/main/java/com/zfoo/storage/strategy/StringToMapConverter.java create mode 100644 storage/src/main/java/com/zfoo/storage/util/CellUtils.java create mode 100644 storage/src/main/resources/META-INF/spring.handlers create mode 100644 storage/src/main/resources/META-INF/spring.schemas create mode 100644 storage/src/main/resources/storage-1.0.xsd create mode 100644 storage/src/test/java/com/zfoo/storage/ApplicationTest.java create mode 100644 storage/src/test/java/com/zfoo/storage/StudentManager.java create mode 100644 storage/src/test/java/com/zfoo/storage/conversion/ConversionTest.java create mode 100644 storage/src/test/java/com/zfoo/storage/excel/ExcelTest.java create mode 100644 storage/src/test/java/com/zfoo/storage/pathmatch/PathMatchTest.java create mode 100644 storage/src/test/java/com/zfoo/storage/resource/StudentResource.java create mode 100644 storage/src/test/java/com/zfoo/storage/resource/User.java create mode 100644 storage/src/test/resources/application.xml create mode 100644 storage/src/test/resources/excel/StudentResource.xls create mode 100644 storage/src/test/resources/logback-test.xml create mode 100644 util/pom.xml create mode 100644 util/src/main/java/com/zfoo/util/DomUtils.java create mode 100644 util/src/main/java/com/zfoo/util/EnumUtils.java create mode 100644 util/src/main/java/com/zfoo/util/ThreadUtils.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/ArithmeticCaptcha.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/GifCaptcha.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/PngCaptcha.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/gif/Encoder.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/gif/GifEncoder.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/gif/Quant.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/model/AbstractCaptcha.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/model/CaptchaCharEnum.java create mode 100644 util/src/main/java/com/zfoo/util/captcha/model/CaptchaFontEnum.java create mode 100644 util/src/main/java/com/zfoo/util/math/Combinatorics.java create mode 100644 util/src/main/java/com/zfoo/util/math/ConsistentHash.java create mode 100644 util/src/main/java/com/zfoo/util/math/HashUtils.java create mode 100644 util/src/main/java/com/zfoo/util/math/NumberUtils.java create mode 100644 util/src/main/java/com/zfoo/util/math/RandomSelector.java create mode 100644 util/src/main/java/com/zfoo/util/math/RandomUtils.java create mode 100644 util/src/main/java/com/zfoo/util/math/dfa/WordTree.java create mode 100644 util/src/main/java/com/zfoo/util/math/lexer/LexicalAnalysis.java create mode 100644 util/src/main/java/com/zfoo/util/net/HostAndPort.java create mode 100644 util/src/main/java/com/zfoo/util/net/NetUtils.java create mode 100644 util/src/main/java/com/zfoo/util/security/AesUtils.java create mode 100644 util/src/main/java/com/zfoo/util/security/IdUtils.java create mode 100644 util/src/main/java/com/zfoo/util/security/MD5Utils.java create mode 100644 util/src/main/java/com/zfoo/util/security/ZipUtils.java create mode 100644 util/src/main/resources/captcha/actionj.ttf create mode 100644 util/src/main/resources/captcha/epilog.ttf create mode 100644 util/src/main/resources/captcha/fresnel.ttf create mode 100644 util/src/main/resources/captcha/headache.ttf create mode 100644 util/src/main/resources/captcha/lexo.ttf create mode 100644 util/src/main/resources/captcha/prefix.ttf create mode 100644 util/src/main/resources/captcha/progbot.ttf create mode 100644 util/src/main/resources/captcha/ransom.ttf create mode 100644 util/src/main/resources/captcha/robot.ttf create mode 100644 util/src/main/resources/captcha/scandal.ttf create mode 100644 util/src/test/java/MainTest.java create mode 100644 util/src/test/java/com/zfoo/util/AssertionUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/ClassUtilTest.java create mode 100644 util/src/test/java/com/zfoo/util/DomUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/EnumUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/FileUtilTest.java create mode 100644 util/src/test/java/com/zfoo/util/JsonUtilTest.java create mode 100644 util/src/test/java/com/zfoo/util/NetUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/ReflectUtilTest.java create mode 100644 util/src/test/java/com/zfoo/util/StringUtilTest.java create mode 100644 util/src/test/java/com/zfoo/util/captcha/CaptchaTest.java create mode 100644 util/src/test/java/com/zfoo/util/collection/CollectionUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/collection/GeneralTreeTest.java create mode 100644 util/src/test/java/com/zfoo/util/math/ConsistentHashTest.java create mode 100644 util/src/test/java/com/zfoo/util/math/dfa/WordTreeTest.java create mode 100644 util/src/test/java/com/zfoo/util/math/lexer/LexicalAnalysisTest.java create mode 100644 util/src/test/java/com/zfoo/util/security/AesUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/security/IdUtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/security/MD5UtilsTest.java create mode 100644 util/src/test/java/com/zfoo/util/security/ZipTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..7086620e --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# Compiled class file +*.class + +# Log file +*.log +*.log* +**/logs/ + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + +# 忽略IntelliJ IDEA生成的项目描述文件 +.idea +*.iml +**/target/ + +# 忽略lucene生成的索引文件 +**/lucene-data/ + +# 忽略生成的协议文件 +**/jsProtocol/ +**/CsProtocol/ +**/LuaProtocol/ +**/zapp-user/protocol/ +**/protobuf/ + +# 以下是web前端需要忽略的文件 +# 忽略npm生成的package描述文件 +package-lock.json + + +# 忽略web前端前端package.json,bower.json所有依赖的包 +**/node_modules/ +**/bower_components/ + +# 忽略npm编译后的文件 +**/dist/ + +# 忽略webapp中部署的文件 +**/resources/static/ +**/resources/templates/index.html + +# test文件 +tests/**/coverage/ +tests/e2e/reports diff --git a/README.md b/README.md new file mode 100644 index 00000000..a7446dec --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +### Ⅰ. zfoo简介 + +- **性能炸裂,天生异步,Actor设计思想,无锁化设计,基于Spring的MVC式用法的万能RPC框架** +- **极致序列化**,原生集成的目前二进制序列化和反序列化速度最快的 [zfoo protocol](protocol/README.md) 作为网络通讯协议 +- **高可拓展性**,单台服务器部署,集群部署,注册中心加集群部署,网关加集群部署,随意搭配 + +周边生态较为完善的RPC框架 + +- **普通java项目,spring项目,spring boot项目,一行代码无差别热更新** [hotswap](hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java) +- **Excel配置自动映射和Excel热更新方案** [storage](storage/src/test/java/com/zfoo/storage/ApplicationTest.java) +- **轻量级cpu,内存,硬盘,网络监控,** 拒绝复杂的监控部署 [monitor](monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java) +- mongodb的自动映射框架 [orm](orm/README.md) +- 事件总线 [event](event/src/test/java/com/zfoo/event/ApplicationTest.java) +- 时间任务调度 [scheduler](scheduler/README.md) + +### Ⅱ. 适用项目 + +- 性能需求极高的项目,如直播,游戏等 +- 节省研发成本的项目,如想节省,开发,部署,运维成本 +- 喜欢 [KISS法则](https://baike.baidu.com/item/KISS原则/3242383) 的项目 ,简单的配置,优雅的代码。 + +### Ⅲ. 安装和使用 + +#### 1. 环境要求 + +**JDK 11+**,可以在 **OpenJDK** 和 **Oracle JDK** 无缝切换 + +``` +如果你的机器没有安装JDK 11,最快速的安装方法是在Idea中的Project Structure,Platform Settings,SDKs中直接下载 +``` + +#### 2. [protocol](protocol/README.md) 目前性能最好的Java序列化和反序列化库 + +``` +// zfoo协议注册,只能初始化一次 +ProtocolManager.initProtocol(Set.of(ComplexObject.class, ObjectA.class, ObjectB.class)); + +// 序列化 +ProtocolManager.write(byteBuf, complexObject); + +// 反序列化 +var packet = ProtocolManager.read(byteBuf); +``` + +#### 3. [net](net/README.md) 目前速度最快的RPC框架 + +``` +// 服务提供者,只需要在方法上加个注解,则自动注册接口 +@PacketReceiver +public void atUserInfoAsk(Session session, UserInfoAsk ask) { +} + +// 消费者,同步请求远程用户信息,会阻塞当前的线程,慎重考虑使用同步请求 +var userInfoAsk = UserInfoAsk.valueOf(userId); +var answer = NetContext.getCosumer().syncAsk(userInfoAsk, UserInfoAnswer.class, userId).packet(); + +// 消费者,异步请求远程用户信息,不会柱塞当前的线程,异步请求成功过后依然会在userId指定的线程执行逻辑 +NetContext.getCosumer() + .asyncAsk(userInfoAsk, UserInfoAnswer.class, userId) + .whenComplete(sm -> { + // do something + ); +``` + +#### 4. [hotswap](hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java) 热更新代码,不需要停止服务器,不需要额外的任何配置,一行代码开启热更新 + +``` +// 传入需要更新的class文件 +ConfigUtils.startHotSwapConfig(bytes); +``` + +#### 5. [orm](orm/README.md) 基于mongodb的自动映射框架 + +``` +// 无需自己写sql和任何配置,直接通过注解定义在数据库中定义一张表 +@EntityCache(cacheStrategy = "tenThousand", persister = @Persister("time30s")) +public class UserEntity implements IEntity { + @Id + private long id; + private String name; +} + +// 更新数据库的数据 +entityCaches.update(userEntity); +``` + +#### 6. [event](event/src/test/java/com/zfoo/event/ApplicationTest.java) 事件总线解耦不同模块,提高代码的质量 + +``` +// 接收一个事件,只需要在需要接收事件的方法上加一个注解就会自动监听这个事件 +@EventReceiver +public void onNoticeEvent(NoticeEvent event) { + // do something +} + +// 抛出一个事件 +EventBus.syncSubmit(MyNoticeEvent.valueOf("同步事件")); +EventBus.asyncSubmit(MyNoticeEvent.valueOf("异步事件")); +``` + +#### 7. [scheduler](scheduler/README.md) 基于cron表达式的定时任务调度框架 + +```` +@Scheduler(cron = "0/1 * * * * ?") +public void cronSchedulerPerSecond() { + // do something +} +```` + +#### 8. [storage](storage/src/test/java/com/zfoo/storage/ApplicationTest.java) excel和Java类自动映射框架,无需代码和任何工具只需要定义一个和excel对应的类,直接解析excel + +``` +@Resource +public class StudentResource { + @Id + private int id; + @Index + private String name; + private int age; +} +``` + +### Ⅳ. 为什么快 + +- 使用目前性能最好的 [zfoo protocol](protocol/README.md) 作为网关和RPC消息的序列化和反序列化协议 +- 无锁化设计和优雅的线程池设计,用户的请求通过网关总能保证请求在同一台服务器的同一条线程去执行,所以就不需要用锁保证并发 +- rpc调用天生异步支持,并且保证rpc异步调用结束过后在同一条线程去执行,类似于actor的设计思想,特别适合对性能有极高需求的场景,如直播聊天,游戏等等。 +- 数据库使用高性能分布式数据库 [mongodb](https://github.com/mongodb/mongo) ,在其之上使用 [caffeine](https://github.com/ben-manes/caffeine) + 设计了 [zfoo orm](protocol/README.md) 二级缓存,充分释放数据库压力 +- 服务器热更新和监控无需代码和额外工具,直接内置在程序里,解放运维生产力 +- 使用MVC设计模式,规范开发,保证代码质量,高效执行 + +### Ⅴ. 提交规范 + +- Java项目格式化代码的方式采用IntelliJ Idea默认的格式化 +- 所有的接口的参数和方法返回的参数都默认是非空的,如果参数可空,需要加上org.springframework.lang.Nullable注解 +- 代码提交的时候需要固定格式,如给net增加了一个功能,feat[net]: 功能描述,下面给出了一些常用的格式 + +``` +feat[module]: 新增某一项功能 +perf[module]: 优化了模块代码或者优化了什么功能 +fix[module]: 修改了什么bug +test[module]: 测试了什么东西 +doc[module]: 增加了什么文档 +del[module]: 删除了某些功能或者无用代码 +``` + +### Ⅵ. License + +zfoo使用 [Apache License Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + +``` +Copyright (C) 2020 The zfoo Authors + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed +on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. +``` \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 00000000..0318541e --- /dev/null +++ b/deploy.sh @@ -0,0 +1,260 @@ +#!/bin/bash + +# 服务器部署脚本 +# doc: +# 1.在Idea中下载安装阿里云Alibaba Cloud插件 +# 2.使用Ecs的AccessKeyId和AccessSecurity登录,服务器信息里有具体密码 +# 3.在upload选项中选择需要上传的jar包,location默认为根路径/, +# command中输入脚本运行命令:sh /deploy.sh stopUpdateStart /usr/local/zapp/user/zapp-user-1.0.jar /usr/local/zapp/user notPrint +# 注: +# /deploy.sh脚本包括了,启动服务器,优雅停止java服务器,更新文件,重新启动服务器的脚本,对应四个命令:start|stop|update|stopUpdateStart。 +# 其中stopUpdateStart会按照顺序执行,stop,update,start命令。 + +# usage: +# sh deploy.sh start /usr/local/zapp/gateway/zapp-gateway-1.0.jar /usr/local/zapp/gateway +# sh deploy.sh stop gateway +# sh deploy.sh update /usr/local/zapp/gateway/zapp-gateway-1.0.jar +# sh deploy.sh stopUpdateStart /usr/local/zapp/gateway/zapp-gateway-1.0.jar /usr/local/zapp/gateway +# start启动服务器,/usr/local/zapp/gateway/zapp-gateway-1.0.jar是jar包的绝对路径,/usr/local/zapp/gateway是日志的输出路径 +# stop关闭服务器,会使用jps | grep gateway,抓取需要关闭的java进程 +# update更新jar包,默认会使用根路径/zapp-gateway-1.0.jar下的jar包去更新/usr/local/zapp/gateway/zapp-gateway-1.0.jar路径的jar包 +# stopUpdateStart,按顺序执行命令stop,update,start + +# 相关参数命令 +# java -XX:+PrintFlagsInitial,查看jvm全部参数的默认值 +# +# @author jaysunxiao +# @version 1.0 + */ + +if [ $# -lt 1 ]; then + echo "deploy.sh脚本使用错误,命令参数不合法" + echo "usage: sh deploy.sh start|stop|update|stopUpdateStart" + exit 1 +fi + +command=${1} + +function waitAllProcessesExit() { + while true; do + local runningProcesses + runningProcesses=$(jps -lvm | grep ${1}) + + if [ -n "${runningProcesses}" ]; then + echo "正在关闭以下Java进程${1}:" + echo "${runningProcesses}" + sleep 3 + else + echo "已正常关闭所有${1}进程" + return + fi + done +} + +# 停止所有进程,不包括login +function stop() { + echo "-------------------------------------------------------------------------------------------------------------------------------------->stop" + local pids + pids=$(jps | grep ${1} | awk '{print $1}' | paste -d " " -s) + + if [ -z "${pids}" ]; then + echo "没有找到任何包含${1}关键字的Java进程" + return + fi + + echo "----------------------------------------------------------------------------------------->cpu" + uptime + echo "注:" + echo "uptime查看cpu的负载情况,load avarage分别是1分钟,5分钟,15分钟内系统的load值" + echo "一般load值不大于3,我们就认为它的负载是正常的,大于了3就要想办法降低系统的负载" + + echo "----------------------------------------------------------------------------------------->内存" + free -m + echo "注:" + echo "free查看内存的使用情况,重点关注swap内存使用,swap大表示物理内存不够用,这时候容易导致OOM异常" + + echo "----------------------------------------------------------------------------------------->磁盘" + df -h + echo "注:" + echo "df查看磁盘的使用情况,应该特别关注日志和数据库的挂载路径的使用情况" + + echo "----------------------------------------------------------------------------------------->网络" + sar -n DEV 1 2 + echo "注:" + echo "sar查看网络的使用情况,可以通过网络设备的吞吐量,判断网络设备是否已经饱和。" + + echo "----------------------------------------------------------------------------------------->系统日志" + dmesg | tail -n 20 + echo "注:" + echo "df查看系统日志的最后20行,主要看有没有严重的系统问题" + + echo "----------------------------------------------------------------------------------------->jmap实例数量" + for pid in ${pids}; do + echo "${pid}->进程的class实例数量前20信息" + jmap -histo ${pid} | head -n 20 + echo -e "\n" + done + + echo "----------------------------------------------------------------------------------------->开始执行终止Java进程命令" + echo "kill -15 $pids" + kill -15 ${pids} + + waitAllProcessesExit ${1} +} + +# 启动一个java进程,必须指定jar路径和jar名称 +# -n为notEmpty,-z为empty +function start() { + echo "-------------------------------------------------------------------------------------------------------------------------------------->start" + local jarPath=${1} + local logPath=${2} + + if [ -z "${jarPath}" ]; then + echo "启动的路径不能为空" + echo "usage: sh deploy.sh start jarPath logPath" + exit 1 + fi + + if [ -z "${logPath}" ]; then + echo "启动的jar包名称不能为空" + echo "usage: sh deploy.sh start jarPath logPath" + exit 1 + fi + + mkdir -p "${logPath}/log" + cd ${logPath} + + # -XX:+AlwaysPreTouch,并置零内存页面,可能令得启动时慢上一点,但后面访问时会更流畅,比如页面会连续分配 + nohup java -XX:+UseG1GC -XX:InitialHeapSize=4g -XX:MaxHeapSize=4g -XX:MaxMetaspaceSize=256m -XX:AutoBoxCacheMax=20000 -XX:+UseStringDeduplication -XX:+DisableExplicitGC -XX:+HeapDumpOnOutOfMemoryError -Djdk.attach.allowAttachSelf=true -Dspring.profiles.active=pro -Dfile.encoding=UTF-8 -jar ${jarPath} >/dev/null 2>&1 & + + # 如果没有info的log,则等待一秒钟 + local infoLog + infoLog=$(ls ./log | grep info) + if [ -z "${infoLog}" ]; then + sleep 1 + fi + + if [ -z "${3}" ]; then + tailf log/info.log + else + sleep 4 + local jarName=${jarPath##*/} + local runningProcesses + runningProcesses=$(jps -lvm | grep ${jarName}) + echo "----------------------------------------------------------------------------------------->jps信息" + echo "${runningProcesses}" + + sleep 4 + echo "----------------------------------------------------------------------------------------->jstat编译统计信息" + local pids + pids=$(jps | grep ${jarName} | awk '{print $1}' | paste -d " " -s) + for pid in ${pids}; do + echo "${pid}->进程的编译统计信息" + jstat -compiler ${pid} + echo -e "\n" + done + + sleep 6 + echo "----------------------------------------------------------------------------------------->启动信息" + tail -n 40 log/info.log + + exit 0 + fi +} + +# 更新jar包 +function update() { + echo "-------------------------------------------------------------------------------------------------------------------------------------->update" + local jarPath=${1} + + if [ -z "${jarPath}" ]; then + echo "需要被更新的jar路径不能为空" + echo "usage: sh deploy.sh update jarPath" + exit 1 + fi + + local jarFilePath=${jarPath%/*} + local jarName=${jarPath##*/} + + # echo ${jarFilePath} + # echo ${jarName} + + echo "更新之前源文件和目标文件信息:" + local fileInfo=$(ls -lh "/${jarName}") + echo "${fileInfo}" + + if [ -f "${jarPath}" ]; then + fileInfo=$(ls -lh ${jarPath}) + echo "${fileInfo}" + fi + + echo -e "\n\n" + + rm -rf ${jarPath} + mkdir -p ${jarFilePath} + cp "/${jarName}" ${jarFilePath} + + echo "更新之后源文件和目标文件信息:" + fileInfo=$(ls -lh "/${jarName}") + echo "${fileInfo}" + fileInfo=$(ls -lh ${jarPath}) + echo "${fileInfo}" +} + +function stopUpdateStart() { + echo "-------------------------------------------------------------------------------------------------------------------------------------->stopUpdateStart" + local jarPath=${1} + local logPath=${2} + + if [ -z "${jarPath}" ]; then + echo "启动的路径不能为空" + echo "usage: sh deploy.sh start jarPath logPath" + exit 1 + fi + + if [ -z "${logPath}" ]; then + echo "启动的jar包名称不能为空" + echo "usage: sh deploy.sh start jarPath logPath" + exit 1 + fi + + echo "开始按顺序执行任务: stop -> update -> start" + echo -e "\n\n" + local jarFilePath=${jarPath%/*} + local jarName=${jarPath##*/} + + # 先停止 + stop ${jarName} + # 再更新 + update ${jarPath} + # 最后启动 + start ${jarPath} ${logPath} ${3} +} + +# Linux性能调优 +function optimizeLinux() { + echo "-------------------------------------------------------------------------------------------------------------------------------------->性能调优" +} +# 立即执行调优 +optimizeLinux + +case ${command} in +"start") + start ${2} ${3} + ;; +"stop") + stop ${2} + ;; +"update") + update ${2} + ;; +"stopUpdateStart") + stopUpdateStart ${2} ${3} ${4} + ;; +*) + echo "命令无法识别: ${1}" + echo "usage: sh deploy.sh start|stop|udpate|stopUpdateStart" + ;; +esac + +exit 0 diff --git a/event/pom.xml b/event/pom.xml new file mode 100644 index 00000000..6dbd9c19 --- /dev/null +++ b/event/pom.xml @@ -0,0 +1,244 @@ + + + 4.0.0 + + com.zfoo + event + 3.0 + + jar + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + com.zfoo + util + ${zfoo.util.version} + + + + io.netty + netty-all + ${netty.version} + + + + + org.javassist + javassist + ${javassist.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + + junit + junit + ${junit.version} + test + + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + + diff --git a/event/src/main/java/com/zfoo/event/EventContext.java b/event/src/main/java/com/zfoo/event/EventContext.java new file mode 100644 index 00000000..f5fdeff1 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/EventContext.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.event.schema.EventRegisterProcessor; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.util.ThreadUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; + +import java.lang.reflect.Field; +import java.util.concurrent.ExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EventContext implements ApplicationListener, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(EventContext.class); + + private static EventContext instance; + + private ApplicationContext applicationContext; + + public static EventContext getEventContext() { + return instance; + } + + public static ApplicationContext getApplicationContext() { + return instance.applicationContext; + } + + public synchronized static void shutdown() { + try { + Field field = EventBus.class.getDeclaredField("executors"); + ReflectionUtils.makeAccessible(field); + + var executors = (ExecutorService[]) ReflectionUtils.getField(field, null); + for (ExecutorService executor : executors) { + ThreadUtils.shutdown(executor); + } + } catch (Throwable e) { + logger.error("Event thread pool failed shutdown: " + ExceptionUtils.getMessage(e)); + return; + } + + logger.info("Event shutdown gracefully."); + } + + @Override + public void onApplicationEvent(ApplicationContextEvent event) { + if (event instanceof ContextRefreshedEvent) { + if (instance != null) { + return; + } + // 初始化上下文 + EventContext.instance = this; + instance.applicationContext = event.getApplicationContext(); + + var beanNames = applicationContext.getBeanDefinitionNames(); + var processor = applicationContext.getBean(EventRegisterProcessor.class); + + for (var beanName : beanNames) { + processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName); + } + } else if (event instanceof ContextClosedEvent) { + shutdown(); + ThreadUtils.shutdownForkJoinPool(); + } + } + + @Override + public int getOrder() { + return 30; + } + +} diff --git a/event/src/main/java/com/zfoo/event/manager/EventBus.java b/event/src/main/java/com/zfoo/event/manager/EventBus.java new file mode 100644 index 00000000..cb463143 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/manager/EventBus.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.manager; + +import com.zfoo.event.model.anno.EventReceiver; +import com.zfoo.event.model.event.IEvent; +import com.zfoo.event.model.vo.EnhanceUtils; +import com.zfoo.event.model.vo.EventReceiverDefinition; +import com.zfoo.event.model.vo.IEventReceiver; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.math.RandomUtils; +import io.netty.util.concurrent.FastThreadLocalThread; +import javassist.CannotCompileException; +import javassist.NotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class EventBus { + + private static final Logger logger = LoggerFactory.getLogger(EventBus.class); + + // 线程池的大小 + private static final int EXECUTORS_SIZE = Runtime.getRuntime().availableProcessors() * 2; + + private static final ExecutorService[] executors; + + private static final Map, List> receiverMap; + + + static { + receiverMap = new HashMap<>(); + executors = new ExecutorService[EXECUTORS_SIZE]; + + for (int i = 0; i < executors.length; i++) { + var namedThreadFactory = new EventThreadFactory(); + executors[i] = Executors.newSingleThreadExecutor(namedThreadFactory); + } + } + + private static class EventThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + EventThreadFactory() { + var s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = "event-p" + poolNumber.getAndIncrement() + "-t"; + } + + @Override + public Thread newThread(Runnable runnable) { + var t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(false); + t.setPriority(Thread.NORM_PRIORITY); + t.setUncaughtExceptionHandler((thread, e) -> logger.error(thread.toString(), e)); + return t; + } + } + + /** + * 同步抛出一个事件,会在当前线程中运行 + * + * @param event 需要抛出的事件 + */ + public static void syncSubmit(IEvent event) { + var list = receiverMap.get(event.getClass()); + if (CollectionUtils.isEmpty(list)) { + return; + } + doSubmit(event, list); + } + + + /** + * 异步抛出一个事件,事件不在同一个线程中处理 + * + * @param event 需要抛出的事件 + */ + public static void asyncSubmit(IEvent event) { + var list = receiverMap.get(event.getClass()); + if (CollectionUtils.isEmpty(list)) { + return; + } + + executors[Math.abs(event.threadId() % EXECUTORS_SIZE)].execute(new Runnable() { + @Override + public void run() { + doSubmit(event, list); + } + }); + } + + /** + * 随机获取一个线程池 + */ + public static Executor asyncExecute() { + return executors[RandomUtils.randomInt(EXECUTORS_SIZE)]; + } + + private static void doSubmit(IEvent event, List listReceiver) { + for (var receiver : listReceiver) { + try { + receiver.invoke(event); + } catch (Exception e) { + logger.error("eventBus未知exception异常", e); + } catch (Throwable t) { + logger.error("eventBus未知error异常", t); + } + } + } + + public static void registerEventReceiver(Object bean) { + try { + var clazz = bean.getClass(); + var methods = ReflectionUtils.getMethodsByAnnoInPOJOClass(clazz, EventReceiver.class); + for (var method : methods) { + var paramClazzs = method.getParameterTypes(); + if (paramClazzs.length != 1) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must have one parameter!", bean.getClass().getName(), method.getName())); + } + if (!IEvent.class.isAssignableFrom(paramClazzs[0])) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must have one [IEvent] type parameter!", bean.getClass().getName(), method.getName())); + } + + var eventClazz = (Class) paramClazzs[0]; + var eventName = eventClazz.getCanonicalName(); + var methodName = method.getName(); + + if (!Modifier.isPublic(method.getModifiers())) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] [event:{}] must use 'public' as modifier!", bean.getClass().getName(), methodName, eventName)); + } + + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] [event:{}] can not use 'static' as modifier!", bean.getClass().getName(), methodName, eventName)); + } + + var expectedMethodName = StringUtils.format("on{}", eventClazz.getSimpleName()); + if (!methodName.equals(expectedMethodName)) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] [event:{}] expects '{}' as method name!" + , bean.getClass().getName(), methodName, eventName, expectedMethodName)); + } + + var receiverDefinition = new EventReceiverDefinition(bean, method, eventClazz); + if (!receiverMap.containsKey(eventClazz)) { + receiverMap.put(eventClazz, new LinkedList<>()); + } + + var enhanceReceiverDefinition = EnhanceUtils.createEventReceiver(receiverDefinition); + receiverMap.get(eventClazz).add(enhanceReceiverDefinition); + } + } catch (NotFoundException | CannotCompileException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } +} + + diff --git a/event/src/main/java/com/zfoo/event/model/anno/EventReceiver.java b/event/src/main/java/com/zfoo/event/model/anno/EventReceiver.java new file mode 100644 index 00000000..360a79ee --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/anno/EventReceiver.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.model.anno; + +import java.lang.annotation.*; + +/** + * 接受时间注解 + * + * @author jaysunxiao + * @version 3.0 + */ + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface EventReceiver { +} diff --git a/event/src/main/java/com/zfoo/event/model/event/AppStartEvent.java b/event/src/main/java/com/zfoo/event/model/event/AppStartEvent.java new file mode 100644 index 00000000..6f741cbe --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/event/AppStartEvent.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.model.event; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ApplicationContextEvent; + +/** + * 应用启动事件,这个使用spring自带的事件机制,自研的event事件仅用在业务逻辑 + * + * @author jaysunxiao + * @version 3.0 + */ +public class AppStartEvent extends ApplicationContextEvent { + + public AppStartEvent(ApplicationContext context) { + super(context); + } + +} diff --git a/event/src/main/java/com/zfoo/event/model/event/IEvent.java b/event/src/main/java/com/zfoo/event/model/event/IEvent.java new file mode 100644 index 00000000..da11c6a1 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/event/IEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.model.event; + +import com.zfoo.util.math.RandomUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IEvent { + + /** + * 这个返回的是一个用于确定事件在EventBus中的哪个线程池的执行的一个参数,只有异步事件才会有作用 + *

+ * 比如cpu是四核,那么EventBus中的executors线程池的数量为8个,通过取余可以确定异步事件在哪个线程池中执行。 + * 如果参数是0,0 % 8 = 0,那么异步事件最终会在executors[0]表示的线程池中执行 + * 如果参数是1,1 % 8 = 1,那么异步事件最终会在executors[1]表示的线程池中执行 + * 如果参数是8,8 % 8 = 0,那么异步事件最终会在executors[0]表示的线程池中执行 + * 如果参数是9,9 % 8 = 1,那么异步事件最终会在executors[1]表示的线程池中执行 + *

+ * 通过返回的参数,可以轻松控制异步事件在哪个线程池中去执行。 + * 因为EventBus中的线程池都是单线程线程池,如果将一些异步事件放在同一个线程池中执行,可以不用加锁,提高程序运行的效率。 + *

+ * 默认返回一个随机数,这就导致如果不重写这个方法,那么异步事件有可能会在EventBus中的任何一条线程池中去执行。 + * + * @return 线程池的执行的一个参数 + */ + default int threadId() { + return RandomUtils.randomInt(); + } + +} diff --git a/event/src/main/java/com/zfoo/event/model/vo/EnhanceUtils.java b/event/src/main/java/com/zfoo/event/model/vo/EnhanceUtils.java new file mode 100644 index 00000000..d339dcc7 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/vo/EnhanceUtils.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.model.vo; + +import com.zfoo.event.model.event.IEvent; +import com.zfoo.event.schema.NamespaceHandler; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.security.IdUtils; +import javassist.*; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class EnhanceUtils { + + static { + // 适配Tomcat,因为Tomcat不是用的默认的类加载器,而Javaassist用的是默认的加载器 + var classArray = new Class[]{ + IEventReceiver.class, + IEvent.class + }; + + var classPool = ClassPool.getDefault(); + + for (var clazz : classArray) { + if (classPool.find(clazz.getCanonicalName()) == null) { + ClassClassPath classPath = new ClassClassPath(clazz); + classPool.insertClassPath(classPath); + } + } + } + + public static IEventReceiver createEventReceiver(EventReceiverDefinition definition) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + var classPool = ClassPool.getDefault(); + + Object bean = definition.getBean(); + Method method = definition.getMethod(); + Class clazz = definition.getEventClazz(); + + // 定义类名称 + CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getCanonicalName() + StringUtils.capitalize(NamespaceHandler.EVENT) + IdUtils.getLocalIntId()); + enhanceClazz.addInterface(classPool.get(IEventReceiver.class.getCanonicalName())); + + // 定义类中的一个成员 + CtField field = new CtField(classPool.get(bean.getClass().getCanonicalName()), "bean", enhanceClazz); + field.setModifiers(Modifier.PRIVATE); + enhanceClazz.addField(field); + + // 定义类的构造器 + CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getCanonicalName()}), enhanceClazz); + constructor.setBody("{this.bean=$1;}"); + constructor.setModifiers(Modifier.PUBLIC); + enhanceClazz.addConstructor(constructor); + + // 定义类实现的接口方法 + CtMethod invokeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "invoke", classPool.get(new String[]{IEvent.class.getCanonicalName()}), enhanceClazz); + invokeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + String invokeMethodBody = "{this.bean." + method.getName() + "((" + clazz.getCanonicalName() + ")$1);}";// 强制类型转换,转换为具体的Event类型的类型 + invokeMethod.setBody(invokeMethodBody); + enhanceClazz.addMethod(invokeMethod); + + // 释放缓存 + enhanceClazz.detach(); + + Class resultClazz = enhanceClazz.toClass(IEventReceiver.class); + Constructor resultConstructor = resultClazz.getConstructor(bean.getClass()); + IEventReceiver receiver = (IEventReceiver) resultConstructor.newInstance(bean); + return receiver; + } +} diff --git a/event/src/main/java/com/zfoo/event/model/vo/EventReceiverDefinition.java b/event/src/main/java/com/zfoo/event/model/vo/EventReceiverDefinition.java new file mode 100644 index 00000000..17c830ed --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/vo/EventReceiverDefinition.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.model.vo; + +import com.zfoo.event.model.event.IEvent; +import com.zfoo.protocol.util.ReflectionUtils; + +import java.lang.reflect.Method; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EventReceiverDefinition implements IEventReceiver { + + + private Object bean; + + // 被ReceiveEvent注解的方法 + private Method method; + + // 接收的参数Class + private Class eventClazz; + + public EventReceiverDefinition(Object bean, Method method, Class eventClazz) { + this.bean = bean; + this.method = method; + this.eventClazz = eventClazz; + ReflectionUtils.makeAccessible(this.method); + } + + @Override + public void invoke(IEvent event) { + ReflectionUtils.invokeMethod(bean, method, event); + } + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + + public Method getMethod() { + return method; + } + + public void setMethod(Method method) { + this.method = method; + } + + public Class getEventClazz() { + return eventClazz; + } + + public void setEventClazz(Class eventClazz) { + this.eventClazz = eventClazz; + } +} diff --git a/event/src/main/java/com/zfoo/event/model/vo/IEventReceiver.java b/event/src/main/java/com/zfoo/event/model/vo/IEventReceiver.java new file mode 100644 index 00000000..adbe60cd --- /dev/null +++ b/event/src/main/java/com/zfoo/event/model/vo/IEventReceiver.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.model.vo; + +import com.zfoo.event.model.event.IEvent; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IEventReceiver { + + void invoke(IEvent event); + +} diff --git a/event/src/main/java/com/zfoo/event/schema/EventDefinitionParser.java b/event/src/main/java/com/zfoo/event/schema/EventDefinitionParser.java new file mode 100644 index 00000000..38213c4c --- /dev/null +++ b/event/src/main/java/com/zfoo/event/schema/EventDefinitionParser.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.schema; + +import com.zfoo.event.EventContext; +import com.zfoo.protocol.util.StringUtils; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EventDefinitionParser implements BeanDefinitionParser { + + @Override + public AbstractBeanDefinition parse(Element element, ParserContext parserContext) { + Class clazz; + String name; + BeanDefinitionBuilder builder; + + // 注册EventSpringContext + clazz = EventContext.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册EventRegisterProcessor,event事件处理 + clazz = EventRegisterProcessor.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + return builder.getBeanDefinition(); + } + +} diff --git a/event/src/main/java/com/zfoo/event/schema/EventRegisterProcessor.java b/event/src/main/java/com/zfoo/event/schema/EventRegisterProcessor.java new file mode 100644 index 00000000..c88178d3 --- /dev/null +++ b/event/src/main/java/com/zfoo/event/schema/EventRegisterProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.schema; + +import com.zfoo.event.EventContext; +import com.zfoo.event.manager.EventBus; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EventRegisterProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (EventContext.getEventContext() == null) { + return bean; + } + EventBus.registerEventReceiver(bean); + return bean; + } +} diff --git a/event/src/main/java/com/zfoo/event/schema/NamespaceHandler.java b/event/src/main/java/com/zfoo/event/schema/NamespaceHandler.java new file mode 100644 index 00000000..97a8683e --- /dev/null +++ b/event/src/main/java/com/zfoo/event/schema/NamespaceHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event.schema; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NamespaceHandler extends NamespaceHandlerSupport { + + public static final String EVENT = "event"; + + @Override + public void init() { + registerBeanDefinitionParser(EVENT, new EventDefinitionParser()); + } +} diff --git a/event/src/main/resources/META-INF/spring.handlers b/event/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..229d592f --- /dev/null +++ b/event/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/event=com.zfoo.event.schema.NamespaceHandler \ No newline at end of file diff --git a/event/src/main/resources/META-INF/spring.schemas b/event/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..7a7fe2e7 --- /dev/null +++ b/event/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/event-1.0.xsd=event-1.0.xsd diff --git a/event/src/main/resources/event-1.0.xsd b/event/src/main/resources/event-1.0.xsd new file mode 100644 index 00000000..20ba1d47 --- /dev/null +++ b/event/src/main/resources/event-1.0.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/event/src/test/java/com/zfoo/event/ApplicationTest.java b/event/src/test/java/com/zfoo/event/ApplicationTest.java new file mode 100644 index 00000000..219a16d1 --- /dev/null +++ b/event/src/test/java/com/zfoo/event/ApplicationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class ApplicationTest { + + // 事件总线教程 + @Test + public void startEventTest() { + // 加载配置文件,配置文件中必须引入event + var context = new ClassPathXmlApplicationContext("application.xml"); + + // 事件的接受需要在被Spring管理的bean的方法上加上@EventReceiver注解,即可自动注册事件的监听 + // 参考MyController1中的标准注册方法 + + // 抛出同步事件,事件会被当前线程立刻执行,注意日志打印的线程号 + EventBus.syncSubmit(MyNoticeEvent.valueOf("同步事件")); + + // 抛出异步事件,事件会被不会立刻执行,注意日志打印的线程号 + EventBus.asyncSubmit(MyNoticeEvent.valueOf("异步事件")); + + // 睡眠3秒,等待异步事件执行完 + ThreadUtils.sleep(3000); + } + +} diff --git a/event/src/test/java/com/zfoo/event/MyController1.java b/event/src/test/java/com/zfoo/event/MyController1.java new file mode 100644 index 00000000..fbbf604f --- /dev/null +++ b/event/src/test/java/com/zfoo/event/MyController1.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event; + +import com.zfoo.event.model.anno.EventReceiver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class MyController1 { + + private static final Logger logger = LoggerFactory.getLogger(MyController1.class); + + @EventReceiver + public void onMyNoticeEvent(MyNoticeEvent event) { + logger.info("方法1收到事件:" + event.getMessage()); + } + +} diff --git a/event/src/test/java/com/zfoo/event/MyController2.java b/event/src/test/java/com/zfoo/event/MyController2.java new file mode 100644 index 00000000..b4992a72 --- /dev/null +++ b/event/src/test/java/com/zfoo/event/MyController2.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event; + +import com.zfoo.event.model.anno.EventReceiver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class MyController2 { + + private static final Logger logger = LoggerFactory.getLogger(MyController2.class); + + /** + * 同一个事件可以被重复注册和接受 + */ + @EventReceiver + public void onMyNoticeEvent(MyNoticeEvent event) { + logger.info("方法2收到事件:" + event.getMessage()); + } + +} diff --git a/event/src/test/java/com/zfoo/event/MyNoticeEvent.java b/event/src/test/java/com/zfoo/event/MyNoticeEvent.java new file mode 100644 index 00000000..a6de3894 --- /dev/null +++ b/event/src/test/java/com/zfoo/event/MyNoticeEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.event; + +import com.zfoo.event.model.event.IEvent; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MyNoticeEvent implements IEvent { + + private String message; + + public static MyNoticeEvent valueOf(String message) { + var event = new MyNoticeEvent(); + event.setMessage(message); + return event; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/event/src/test/resources/application.xml b/event/src/test/resources/application.xml new file mode 100644 index 00000000..7fc4d6d4 --- /dev/null +++ b/event/src/test/resources/application.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/event/src/test/resources/logback-test.xml b/event/src/test/resources/logback-test.xml new file mode 100644 index 00000000..64b56b26 --- /dev/null +++ b/event/src/test/resources/logback-test.xml @@ -0,0 +1,44 @@ + + + + + com.zfoo.event + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + + diff --git a/event/tooltip/general-game-architect.jpg b/event/tooltip/general-game-architect.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d6bcbb9cb115e305bba17f009257dca2f87fbf9b GIT binary patch literal 1016290 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%0D{hTFb&&AWKUJIG|RyogWeXyqkoajEa^x^M- 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�SbSTOIW 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

xakf@^t|bnv(b5 z7Bzt{dn&ETX$A{-2@?@H53{FijWvY8z1+au{MXu~V}Ewj0RD6cL$_=moY}6k0VdH< zKhoISZH9u1TsdN$arVm)tM7gL--d&HJ>EaMdtUo+vWmI{h{-d zo?&gPm3+o$*JRDa#l|MyjeI`)H;^blAj*>B?=_#8aaHEN&wu?DVTt$H5cET{t%x#P z+I;JW=E~y3Fp1Nz-_hYO}81U`~$l%94e3Pz6PZSO!Rx z-u-6#JB%db33$UHMns{8(Q}nvrwmJCdvFnP?_GDFC?p~}%lx)B-|H|s2{XL&=eQJ* zRA&@V3nDF#hxJ@v-`b;9U=w!_P+R;@MqYAEyz^WuqZI!;3iK;LNXr{>HYi^t`gc zaWtl>OpR6J^0;ECd5A&O0~rE6S+(T%ei!@$aJwf431nh$ z@$s9~Bepoys*fcE22vZ=;>Q4XnIGvl{YULtKVYHWl}bXb{@&-SLo6_$Au{EWT)JGs zLX4$$?)56v&$ne?D7`|DR}K!uD`927uL0sZY_Ellfs|9)*-7Gw2OvRLCwXHud6cYA zoM>W?%sDbP)#ey8wKmYMPRDI-bYDA5{iiJcEA-`a8z1T0M^O0DSZ}Wt2S}sLL=6;< zl2bk5FJMLi-*l)|Udlp3E=%bEEYdUnT$9*HWKP=9f;;dBd82o1ulRnIm?H2u;W95g z)*q%&{RH~z!%to`RDDEscR*YAaabx#Wa~t$Pn1x;$>zeF0O-_cJVzhypZ@N3nl%g| z!c0*NdSt+dB7*_B)Zt8H0}EF-VUXQlc9m@LVd+jkg0u;=yj`M<4gZh0?~aQqOWKyU zQBVms2$C~MRzQ#(C1(^wl9G`|B#UGb6bX_M$&y4-q97R+kqnZNsFD#-qU5ixVRmO{ z-}(04dH1*T$4n1>yZhccbt*jdRGr`OpwDHsz{ZIJ8rF>Sjq3&NK)V!~N_1ND{EC+g zhIBOi|7ID#7w`fh2C=-p4{L7-KJFXd8L1XnCR4#{&$dJm7y8fXlE>A?Vhygo3l$nU zvu#37PCn(c^&STPQd0>)pz+uG^?wVOAnf?wZ}Oe*?(qq*@RFctn*0+{q`Vv3;kY z!W2AETeg6-+FnNnRsE4ybIHRG_=QRcpaug{CARmxV7lVee!)dL_25B3Otx476#n-k z&YyXr17q4d`rTh_OmrPCw0IEC z)p?Y~;)at_8N$0X4M4YsNYz1VBk*%OeeyY7b*If5dZ-pX9(vL#*;|E zVGq;DoJLo9Xgod_I9T1;`98h}HK>4nK5uZk^r1G}`EwIyC25jsNe0fgdeF#B#q`@4 z(y{zn4WCUNBaiv#qC?l zB!1Y=R=SDBCt<+?94yYGl zUq*O-`x~d&fPe1VS|oBhs%xO<)3Rh4wBM!gc$H_F6&dqzp7-#mXg{`b1cx6U4@#!T zU@$Eif*Vm$xhN{dOBnD=(>;DnA@LH(F5L6heGvFP5ZkeX#JoLlp7`XCnMpTbX3|-E z%fvGmz%*YH*C+7%74D9>fE4@KJ}-JBQ)97v(0>B#4T)d%hf7na5y@}B!iwCkF*lvo zFfuZdxa9GgYGhvuGt?}LtEJg=&jBQ!uEM#*qmi-c>;nUc=jMAA+P_Z_?SrNN*AbA4 zYKO8CiJjuGX}S)W9l3`|;)Xa!jc8hL*l0HQ1h6pvkwpE^TdEQZ5srO8k%a|X9>^J= zjDD{Qzo#P7C!>%tm){FYF!$#5zU(B|2i4mXyl+X&qYa=+_-1ug8m9Np0I3AmwUGKza%Wnh>WkC;xHV4;Gj16!}m9>@tmi7-#uoHf>e$m6&>$ z^=aRaFKc!?;+iL91sC%q9~J&7QHhdw_o1$U&>YNEQR8Gkf0i+)#95isqdY^Of2k(E zproWX_LC#wYj|!X39JYIA9Bxke;v}ihzumlL$(Br3WFNW<%H7djJq5?WO3F`tm!pG zD>Eev>d#>y@R)v4Z6V?emv9`!ep3KaKN!w43)2^dKGf{IBCk%>fM$XFJ5eyEsWnB? zC!6O73RF$~zY)XVbD3UTb8$KSXL*z7#!bLSoOE{SZsjVmp3k^-Lncsp_7@kxNgv_E ze$i5Zj$pnMD$v6Z#HTZXr(ikER4sK<2}(G z(fEivs!ajgH8=ZD9v+Ru+qBu{R?WXXdhXmg>kqF2L}13vSIRfQN`96NKLx3s+6xf+ zhZ{u+a&`5b|MEme(3~GF$S)p!2SDbLY&*QE`ke3J^48| z!*?0AfB*jP*cMdU2u581+uHDL4QBktU%0O(sI@xo|E6a)^+V4cy9~4Tcz&SA{vCu> z0?U9zd{f|r5^PB(B+S4jQa!n!Fba$&=%r;c?g;L;go3H0Cu}E$I-FmOLchw|qIL-= z^ul@Kr@21vOg;y&BB!#FIx`7Ltk=c<%aP#_**D;aq;fucAtNp>{+fKJJ6j^tXK&rd zZR;b>aM&Gg`i6aR&;~*rmB|QjCs|p<+1*+>Yy=$xdA}^^s5m;L@g5AeGw(2Tl7s&r zqdfq7;va?p|E4q$Y4z70fdIVq%f9}R7a+^8N3Z$bhsf{^cFb_y1vF9&8Ab62j_?c4 zFUY7{I09~mkbD~%c8CnlT`(3I1Cjvl5z1OenASyKC}$1^gi*A-fj03DcVYUcF&rde zK+k;Rg9$-I_vhvj{?Utre@2)3U8zk7@()W-G&Q{?+}SCrGksn#tc>a&GUy3Lq5dB2 zxO&JFMrQTRmySw!A}7CT946=KZi(j~uDs@V_4IFGj!Q~zM#y@Kq53KT0rVx?_^H{37-HepOahX2vKhHceFS1xRG4Pqk?M=&H0k!(o*VOp4Z<4A;=eKqdK& z0kJ^jsi{TuMqoHf=)n*8&}D5q_&)laOwg_I9ku(0JaKymrJ)DyGz@@+xu~$!J*9r@ zO)+K9hf~P7jVmv}BtetFMIQ)8Y=PPRK)^*tcmf~Qz_g-MxEL>h~=a-KQu7X3SHZO>{w@X=EOk-3+Ez&DN3d>N&57 zb;W7FOv7A4P45s~fJi4)ckpF+e{pDFy&zfwLo@hnTe?8OV!upqh26Qr?jI{5D8*#>Yd!oMmXno`@+M6C;4x5p<`Y<+4f zlkYU94=2Uexqi;oukB2PUz@~vM~nU`B0t720LV5W28My3_d>pu#Clmf?p^=k*Ng=N zYR=np3Uns6OLC>6K50{aQIo7eAslAGEmu5Pdzj{H z0Kynq$`(P>k73~NaZ)-5P!den_FkRs?C3uc%&HnIj2oa$M%Y{~XZS$GwYg-bE4<$` zHoI${u|Qbu{;CU`Z(Wv$%!(Bye7Xx`k^BxLWlF_;ihr?P5j$1l)@YGQVt$30!$4cf zy}DFk!Ftq239HbOXfyfcfLcJErRMz*)Q|P|^@XwVFo=AqLzc+G>I5%u92}1>JYU@9 zDIQ1I%2Y5D{;GpEynm(nR@e{|tSGH52LZ8^sTmtOX&=U*!%gbyGE>1seNaUUGdwSx z+lXB&FlVvHr!R1CPMIb4^;H42Fhr?Q;C%}L0kxx+Ul=h9-rJvAIR$Ts-Sa(w0!8M& zBGb=bp9FD%5Cmy5X1WX2WBswN1k!O*t}IJtR! z@e-v=SY4LZ@x|vi>#|H(oyTcGChHe;Y<%}&s6-j2;ahK3?{`d~3*r`1AF^OY*1_?t zY0*6vPIndf9fRF-xA&ocs%w)}trdL0lkEOm5Vbq?z6o&<^`tvmfcPY=8P4HEhjn`2 zy80|lxT>Z9pd!|Cyn*!RUE+O9QD#67M)-3vk?-x9_RCf&D$%mrGaN#1M0MF z>$p2l71UZY@UBc8dHaL)CWj(6i><|T3(@V-U8(fpw0@g!ike#|_BCJqlvd zhW2QKxZQMza*+L3wVWo3CsoS|auM`1r@%Xo*wB0_8rp+Ft(R-L^2Fhqd8UWbui>jG z$WD9;d9V*N_$Y#6V;{)S^sFgAlS0-Hg@kXBL__EQL{4^clb~naQH2YrL43Bvu@nZ+ zUUp1^w;w~nOdLKp@2I|!JTxdP{h(6lyYxJ_7yD-0UGv_K`qK^NF1&#U?+vE z_}9R%Pzdwk{<2$}B>lxUQ78OhWZ{-U$v~I7sM^))4a;~K=(xN8+Y4FH##gfcZEaj( z?HC%u;$th^7TW|j;kZL%==55>IuyPDHs>%VNUcV7xkb00jWt4s&T0N3l_ zxS%A);A-NFW>t81ZQ?F?4Hey`SlBtG+c|_dcrWBYwk#dde!lG>qYbW;_Xf|JE}z-g zg+R#%%y#Eq7&Yh-T^p4V&q_$HVj!En?8o}x9d zEfKLHVrCpiU@zB){SR8ek1^QWByjZR;Esz^D<&I_RBcMxGA(@Jm5t@Nqik#Y#Otg+ zK8g@_wiEEfW%i*q!5kpQoZ&@2=`8v5 zUf#>e_gtEj<}1u6nk?<;vw4MCklg>Qsnb!x%wljZ zSw_tDLbo8ot~+uD1#4ZR$OB8{m{upA1$(22S05sh3T*sJNp4&hhJE9lL)r6hVdu1U z**3=|d$x=Z9|j{O+onv2m`OaYW)RU0L`0lK<$k_A=!Gipv>}JZs-?U)nS3mmm{*#L zB^i-RSo=~r%T_tlE|wE&n{YKV-uo|zr?Q%6r`813!XZd5D1zJQKXO+}-c+>gxltQ0 zPYe^2)?D1lytUOPAD5EmyOS||F1=j;L^hcIDv#2k8*@(!Vb%|J56;dmMwiGE3fIN) zZ;Bgu7o8-zvW*i`@t;qA3!C?Y|Lc3v+oavy(5bLz(*Y(OrH!12;OshKe z3_+~_b5!+Z*fuYQB{bAgm6;>z)#b*x@YS2UbV0YM-Iu7D1&wZYW)Lw8s@+T_K(>;5 zVDc**P-L=>F$z&|prNg)c;?I{J0*+ZHW8=+US5sz&8>-fa~b2AxtL?Et&Z9jEcpUR zz~U}deDGJ)v4~f>yE&(=r^l8cXxFs1+o<8Ky}L8IH|$n)zcW9wA7+xS39AF7f=LMW zV=5@aOVECgEG7@;6O|0q^i2;?`+skdbXJQTxOG%NVX2}Dg-{y4*zx{~Vzxt#v?b~Qnv*0)h zIDe0Ts}+9NhL(6<8iHxWH;i$9uWxnt^%2c%K+jn$GQfDPhdS$YFoY)$6|tKZG71Zb zirzaNa{$?NBsXD8aZiiVmu^J#JASG&viz>?da$Y&3L{HF_bMkeT)^Y7z@zf`WgS6# zB7V!t&wYpolkgTtbHoYWNLG;Aj{}>CEWeh*jR^jLJ#-eX3h@G0EgzMdsYDK7_Vpm7sa^CZ<12 zJ!#g+>|7fNrpulmmhUgP))PVVlCgpf1@9htK6id-ctQKY)9rh5GL#}0Cww+qOJ_=llM zS<~jws0BBI*nUD=yQq6)F%1J}S+r>htqf|i)6dg>#>RDaYTMY^^>{IF`hO2>k*Mt1 zQO&%}+1Xk1`S;HU9>ApZESCIZ$Bvct9t>Dh17ll5^Vb5~ROXVbOqGc{+jk7lY4cbe zBeUYPS7w>8GMZnK7h^rf4|!8rG`V9}o8tpF{M2gtm#=GiC4B)kn=;;R3jv#6( z^ixeQUGhevjhB~#xSxC7rjAYFJ+^T)Ncn{5*xXkM{e)>*2$LND7ACQxc&k<-A>7o{ z({p{p>oudY1QE$#)*&u4a`?Rp#rLIzKawIJ(Dd!N_)$@L(`ApFE0V^b#GhD>hrDGf zz-6+^qE1GlV$uCUtrD@hO)Qff@2+}kYH6j( zK3Pe{fv+ZzNyXKGg?7K2@(~9z8?xFC3U@qsL+}=3ctpQz)z&l8fLD>^m>Kej>muC4 zJ3F02LtI6c?FS&kKA?gH5~{Mp39hpr!fzc^!p`roYe}YmVis(Dogw}!5I1B>^_Fz# zGjb!yaYMQXaLEnFE)tP8C4rl1CW*MFSk+7OqI;cpXQp_yeWRzR@cP$}L*x@}4q!W| z$R%h$BfDOLsN?oLQ^Cebfr}+EAfKmaLXD0Dwf0+6a2kp!cy#;r-Z`4U7^7@l zr6&cI+MpuXAo=?e#vDuqjlEBo{3<=!@o#7HCdKxiQx;?Z;1OEHFMa{QINq&1tG!7^IsjX%C6G>bEnOOwQZ}u)y6Y*- zpiTc)A>Gm0yZg$@@HrV>wvUCZBWxy5zdKQh|Fsi!G76^dp^6g5J=^<{W`}(sQMoC& zfOdwPL&mg-NSIR?2`w4&xenvtO@ob9%M+VEfGQ_`v9%fCsAraLs4MbvGxOo5z_-xG zTiZUvNthI2njp2fRDqfOL26O-)~}KDEjcawniOaluE6zr#IBj~pFmLr+3DI=cve1= zBfKYPZpFd!WYcs>Q?EimC@(tQsulGI^rj=Q7I2%4XV26DPmY4(L7=CAwO58E;Glh6q_JH@GlBzerSzn?zKXe{KG<) znCK}FkMXdzBxLUax`F=(XPk=bR|7S$*xC}i!4j-w%4T=AIbe<#R}@ILSivq*J$W+m z>LXuSe#W^rF0KOFR0-ctzu{ZQFbN4;ctNdd@4Gug;qk8q-5&*6ZBX!ksD_0rko|*j zo;nQhDdmmQPi1#Ho*j_0{ray1Ti|+CFuT)W-ROkhWjew3Ni=J2B0e4lLCzNAPY~k( zcl+7C{tRjppHS|ngU3C$)OP~I9d)YAQF5bX59AdrP{ebhnh z1wq?eJFpjL#4eTwz%y|l4>dstwSK!pag7ds^Yb9>e+Iy@!W)7UB9@9$cDO2D+%VHq zQwvS>-kfP8H9xNaiY9#1#W0w$AoMUD(V8&mVambj*J}~|p2sP<2a!ZYH?f{4u+9=& zd?Xf}6u^xW*AHV1!ct$TdB8)xSCI1uq=^Q3fYb&`;*$uv;vO#|d=!%q<*YwQ1aEAI zW5gdI`!+)$-hvDMP%qP3`4*8RWb1#QHm_l(XJ#T0s0pZvMTb+ay~2;JbsdjTcyAEW zLyaVIXFh6?g9$#(M|+7F?$!U*O5+KTEdE_V8U!IbeleX)-jtt1J&*z-hY~H+;M@8C zj8M1mat%t`F#RKN?BnN$KrjkJPW6q9i?cFe&~-#q*7lJT&#k&xevYdG2w3fxc0~g7 z9oxaX#oR30VEV64d=4;3c2f;7m%Q2Ean&j82dqQi3jaPl%N1_CDs0v4fa2z7iQw z|F;2&<8O)LB zp69b;>*`13H7yH{>GG>v1>8;PKXd)R7S+N|!&rZZYoqJdUf0WSxDGJK>ua81Nz1Q{ z4{7#Kn(vhOB&QF3(@^sMhUIwgf*{K}=K6iSh&hNP^?CpabZazoyjd<{4wTz+--a?5 zR8nxe(8c@DB zn8=)rafF(OC82%CIYsP&fW{NwJqnbD##P566lf!$SIDVh2g)eP6(ZOQ zJB2d;N5J9t$nr^n7GYy^T;#P2OU%p#&NWz-%yEAw4M+p@lhsRu>m~&*_x3@#Az=ak zU5X7Xma0m$yF5S-cExoqm6AwgY3DfYFgyVxU5j$vgMh8({=wWKnqT& z%KQCHx9HhWz?hGLt#ig>5zg{rL*Od=k;+8ccC}q52nhm~r&KJY(^~G5HGPdh+1~Mi zAa(EKsSO{mQybdfqj1wbf~l@B@J^J}c7uoxfpg_M1b!&p_svszFr@r^x`VeNRHR`) zyor>tye_i`%_j$1boO>KzJ2@AQT}ql^Xmpa$|E-xzXY~Dl!1)!dEN<5=~^845<)1| zLP<~(ULr^VafhFji_3F?)29gr?`~QwElo6MK71&1TX#l!KU_k2OX_ek{NysJ8(2J~5@-j!hzs=4w894^*+0TlgLuWIr7B)tRh2*)8aQp$a-RVx%`h?d zcE9fJfE7d>fWvqRW_hy()J+<@eurTryldiZ4P*M@_?`PzU$>Xr<6sORg)X^1k&R%pTpw*lI6_{hT#8+jm(6Xg1jr9+ki zZi7O(yRR<`GxM4dwlufvE~u?H4>o2vuVkCQ&($x4Bdp^2Ebgi#X&pcje~E;(iCfvx zgQ|{C{JVl5MW4ni#K(i6&Y^hgn@1P?Qhf5YtIG^f(^FcnfJ`<()V0as9ZFaNK}^pK zZZ201cMkP&!is_&e7vQ&@(a@2hc5gZw6wEsEx>{35=@_zWtT!U<}lS?a-LF3*mWKw zj)M~u%{BpV_&6_yKdoeni5y*bZI+D0kLPnbfJsn^*F1m{K?KWyySD-!A|-dan*w%2 z+ck^xc%Q*AQh6}_K9)Wb+n=>?HSVx-)A-Gg+VRKRnhCGl4yuZ}cU>r5xUfpmq$)grC6ZyBE@AtXF7Y&qs)oLt&lg+a|Pl|l5*Hc(2LM$E~9qr5E z`+b!lQ~e7y7g#09E7sve@`uLfEJ!9IBMY$;LL?6QLn9n{E2~X@T^ayuvy(JQNgYI$ z8ah248tgv7CV$w%@r(vzS~#M$c8ol?TjK4+>A-DIcW{VXLy1()pV;~(lD;H=%)Y)4 zbQJfbvpg-B-cW)x1bcpHH#jl9{YVw>b|%{}FBH8uJSsIFBqUf|{3p{2=m=Suh3i-3`*h8^)dB7$j47)YsW}#~{B!)a?>F2fywZC?<@>(5Vxpyc(-+z8 z)No<|a8#U;rzD1kC{iY+*kUU3l$6FU!Zy?gE1nwUofRELla$RnRr9 z?^;@mqBlmld(TlD>Epi?PPo0-AYek0ZB&Kdiwq)S(_7pun4k5Ero6X0feXi zUfm=kR@nxOwpxW^h&?3CNH&Q^d{BfT$Y(+($U_zC1(qg-fYwx`NxMF?jYz*?#+RMi zaM6%vC|@`xn%qph)8O2n)+qk%1o@`=SeWPn@v3}Ui%W;}K_sp}=g4=hqcrezO=Le= zYYHzLIcek2O-5n%iHPQeeDc+S3CMxIp&5z>g#o%H5zn?m1MbNZ2== zm3~yHM((UPYKRP)d;B@~2dp!X(zkS|wfKz7#kCm{(R(=#L^`d_kEFWK2U9cWxNjSv#r$ErZ z>+b3Apu5{|%>h&n!zTy6@>YK{E&C8ib@PsY{5lkI)oSMb{*n>?TTbzBQb7Dtk6wpX zkO`P~O2_AV{(Ow#jhReo*_V~V;FOwiBAZJ=#77bxFT&b(afC?;_}7u$xlaM7-owVF zEkiB}z^va#AdhPE{{$_7UJ(slyyGtq(zS#cUcC6-MQ_ezAH?5Z6m}DWfD@O>oFULq zNo7w*${yTir^?xx1=BxSyr4$5^1dJgG}Ow8%|RpAGVlE-umWj%7~9ARY1R_Na#nA} zp{+L~D@z{wdNF8p%Ncq2Wi#~D*lRGIx0*ZOBT0`0<0ncvQ+^)p1C&1ym&4P5xFCgF zZsb1g(Eo`_+Q}p-ph|Z4^fYd+&K0F6JGBYN){+IusnVrv)I&sIm$;AS`WZ~Zeog&O zAy{5dl9*GWj0F@VEdC)8AnD^TaX(-F!6>>7>t19pi_lm8ulYW{jp$^L1>re#$px-h znq}zSjzf;GWWCpl6@;{*=xCOj3PZsFI?}=u;iAM9Y?RwD3!1!DSQbQX3deL`ELa_> zhL9I}-$hCI9}#6tEui`*pb#rtualhzC#Sn-g|!&|pgPQ+iGxA}n-uMWE!;tMSbH3> z(l+=6AC!O;6GBfC7#|919lJ(hCkJmcF*a=+U@l42a!CI>qN4|>?!fDa^YoW~(B)iQ zX_*DBHVVskVO>_*?+~^kd?wvwRsI|B9qCqPyzzOr5c~F zhiz@ZzggRWZfa__fUYB^(FPhO1bsVtROs6+xK`ulLp409OP=sG^>6Pf$kv`i z5%d}Hs!&o*ki5xsJm)QUNS0yXfpQ~6;@X zgfpwo2(!X~abzVbqF=rLxj`ETN5tLjJD-&^$L+t~-+o*nYkc);EK%Eo2{os4t&qMM zv^{!p^e5;6R3z*`;aLR9dERH-tSh$Ktury1U>~a##ah;lYoQ8~%QAWJ=V_KtfoEB+ zNsUBlLfK(d;L$PAA=IA|@UC9G7CJO=eKFstJwb>FO5N@v!%*t(x}|U81}jv~Cg+m{ z7L=b`Ymx#AfhF%S{L8x`b+P@0=OGD-`lxwflbL%yCmIW7&deB2i?z=Kf5bTU;~~yc z+r)%d)NP?feR-hWTlX0hy7p(8q4Pjv+|PP1xTRq4akvomXIOq!aTC*IFJK4GdGYjTvzbl1jtsdxz7lq#$`k;CY&m3;2 zfc&>S7#_9c)3946J!zyEzEVvFO9a^rq;%}>YKHSi% zNXs`5OURkB(PD*?@jaEq2+`fy<*DWxQnKdCn~d=k+{MaIRn{62z6d0EaYkONv|H^t z-WT-D^=175C|G8_EGRpM_ekgvXV5&H^-M7I`N(IaYP8s|qmM8SZj7gTkW21uv|GGR zuwU9dx1KuPu8UGedV70w)@REiZMV_Ub*>t(`D|=$B0iZp2&d=wUo3tq$zsn!vSh9i z=S+4Ch)#3gf(uY+YN)zS(i_GG?nAFY5h_cA%o&A~gm+F)PlW`NpdMj8d>E6^`?VwS zFi6SJR}~-c2o0bd-v&Qs5JqnY;&z7AO$LYFgn!+)#{AIhOJaT&&9RM0?Eg7& zmf#$KKuf%POT6{%ZN6u&+P9`a#Op~q za0p_gq{T*1X%A7z0^Z|6Ki|`pvP}56^%x z07~KiaQ6;8r@UMp0b~pK@KRMVf`b0i4^HHRN=8gV;|!m{FTNCUK2icf;DQvmbUQv_ z*oX{U~`_;JQB@9`{Q}yg zeB&Bx@zjg>#d4cy=94GEQ9S)DevBRD_f)vZqDVBvw5pe4^3YdY16N~fm!VrwJcyf= zlDMpl{*nj|OMXrlZ_o4TK&lKli+d#^-aZk%P_yUlzV)<}850!Z#6nAmvqwBVKOd7+ z;o5ll*fB?b$>0K(Ga%uUg?A3X(h1=CkpUTT3LaCq{r(~1FHErCB(o-sfag6@kX?vy zrdJyLcy0m$&3#6ne+IEl{S^O%L_H>is@WP|qj^Lr4O~gfc zGwh5Vlnft9rd2QR;H4CC442Z-xW{7O_TaP%PtC_e2RRD_(194FJ&2ySX1maZkR3x+ z!E^%My-x8}?v=>uYCV;`oatfZFQ4w~c(vy{e3ECsBk%r<`k(^sEH7&I`OGA@!8pv* zh}_(~;d46Arc@F8;DD?_I#31#ub7KLHS_1g$+r_ z?HH3{B~J^fOne^rN1lJJ6iRZVQA5tl;+eI;m89ymclW%zYi%f$5E_L@#>wK_gZ34z zA`y3^cvhMN!xEV~1_s!lR=738D4cOyldT6%0T(Dxi%eg(M)!=3jSF5yx+5XIG()&@ zW=3{tA7-*%-^aIKDtob5ti>|JL5KZW=HuqpK9g$fO({a;t9Z`xY*BL^f16t$~f71WxY8e=vF1^T*M@BG@d!?w#_-DBa!Ui42Tpl{-ZA9?it%z z(vp_bz?f3GBnt8R%{#Z^jbH;#Vy3mC%T{|0Sb7I-m9UD(()Yc6TFsKWOZB4SshaEj zdlsZg)ezdttCIricNE5EvQuIwV>nrOB_+2Q&g~}L3}ZMcg4jryy2FAARUYOpRXl|m zoZfff%hgqeIs593td%#W+Q!B_0RaIx8MO`4jHzhBRk4BI5gw?C=MKRcWzv+R1^N;F z)UwU5c#w#Q;>-kWD0{@gzfO8g+>s>pY4xy$zFE5GQ|!aUDkS9tp3;9G%50HC$JUK=9B2*JnEqqrXffi>H-;JRV%5KkRL7crlc zQQ1izwIw>p6=c_x8}E1N!xTeA|8^p670~z*&%7;r35FiVLW?Vvy?MeeOgP~Jv^4SV zSjus)K^u8fooodw_3*YMQDjF~&l?#b{(Egc#M{WxO9Hi}~~)FB!gUzMc=nv}4Vld>vp#F}hJ=KBj|4;?x* z?lHPoO-fEKs&nUjCfoyYvNb#*OUut!jU!D9zYTHh~veSM*tBmyyU zmR)2-fA2GWCYt&ANOH<=>~bPcz1PBHy^pf9bH~7;MF|{*N6?AC$YcA^0!RR=PhY$r zM-6YN=30b+fMC-@{3r``>9pv8*M&Ss|nMnXV%*a%tA0b$BximsG z?H-R81q&Bi^_^tB(qPr#jo0JeQLOs*C#P=u=AgUTv9oHA=V@p#!Ed;N<`Ir(&L_UOKr|!= z_$sEs@{F+pxoA@!y!)zBjjv}nT$f|HV=J>H9;M!j>Q@aLiXDiqE3s^)RPjy}aCoSC z|0K@FJF-Hqhg5!YeC*J5**n*@uy9o??>qniDoRRNx6=X*6?+xxtS;*f(Te6{Q$#bx z#cSE-YLyzB!NG9R|IB#?Q7FBlgO0HcCbn{C;F2UbVf86cuaQ!X$lDtwcWd@b5)teLhWJu z4Uz%9CyyZ(YcW>B4wl--wY8}We@pex6^gz6`ZBp_Dk*x1A$5c_QIT0L$@yK+Ft>6p z?8n>U=XaIHzP_c4i_^=^-=(Jvy=Y=cd@|lQa=a%fZ_w>?I7C1|z{iIO$8S&|A+ldL z&)9B9$RLT-&{&t1giSqqd)w1E`+ePgi%Pbi)>ee!LJ)SgTiFG2+qh7m2kMuoiu68j z2&um1S^TK(q9H!kp7rCBwMKUFF*4}0xt$n8jTkKA3$XzZZ^FwgJzD4eI7VP5W+HD% z0x^79LEU+V8ocjRe<2?_5Olh}`p@DE8vI8jMBdN>YGoh_r^{rNl;}>j#HX88teBs~ z`Bz%&coyI?yW#Sl8_VMvN>S!A?!xvwZV{51LrkgasM+byEy-e!%+;XR=^OMq)xtP% zRycl$F0-^$Px)?eW^V2g=vjIIVjO(`K6h$#X~|K=RELj>9KCk-Qhr_50w)JobYm2o zL3RPdUZI;RLu%dgwKdm)p7&RhYGccQN~3Z?2^$K1j5l@;2usGqayGGE47LZ--+3`s zx8nIoHQb|+$b}b0%7Rs-Ml~0LPcnv=D^31a=~EAbq>LiJD9{0yv>}Km3FI|NZJ--i zdkrq>C;yqHo;)e}hiqKnbv{?eMV&X|JcMKkZbJLRN&_87yQw zc>pf1wyrTx6svl)BJ(RH^36AA70l>U)eUvn^7T<*$W~TYgG4qMd3lL~?2d@JH+;hb zM%){6lj0Gsr?--K)(7uRz5mKE@$Laev%N~WNz=m!pK{jyLQ+ORU6#$I2hUwbz1H>F z3UY`pYI-Pv%2lZ+LH7)cG#MG$mEKeHQuJpIUywVNHpG0Pwe|s3h7`Z1t~8M*@7PtB+;>8O+v!i#HWJ8)W5g@ zgjHpLY_<|KZT11uC7Et0BKI95{KftX=k$Z|nXOR&v>!?IY6u`VJ|Q%6-RzkP@VT&v zn-cAG;C;kg!fjA;C$hggQ~kyLZzEK;~WRvBvh&WKs90vxbDZSUSIu{)D6b z-L^WbJ>>TmwalSV4cw*Dem${TPKE;9GoG*If$PSlSr3K#KsK<{Tfo_lR+N)4;ggmy zHjJ$L3LOUC-2<$7(2^QX@f&Ab6Ii22?U&3q+>ZJv!ae1hP=OE*iG93OL6UDjyesF1 zWx&3i-Z~2bBwgC++Dk!Cu~R$#ucTKuxTUoL%kzcldh*_+--?TNwHVL>KVIRggtSfLf2~`b^E~*HJP;V zXi{>6_R`sN4A0php+MoV*sRo>7J95-odJs4H;+gtdE(EM^S?&2sJXtpI5_NUPOhdU zGg+u-?%ZnH;aQi*n^w}&exhnxxTsI`smA^sm(`T2hK-Cmhr9UYI;bn1Ei72*Bm@Y1 z{pp~CibOH?E7JZtb+iNNWH^A>sS4*D>clu+D^fBQiL%ssyM29G;qe9VU$NPNt6@Iz-WlnjF|*b`j(QEymYLb_U5)=sj6q+C9p9-$D=&gw zt_5)7SQQpX(e5Pv>bwdgwuayzX33qbD7j%~0yUdNhkA;zZZj>skU&6Bt*y(P7KyyS z>{l`MScjew+#af1F_l@#lF-neK~MR7I>X6Bz$Eqn!k+~c79tS`#cZzh+QOoDnTPFAD2lc8ijtK0 z!6#G3ELT2XwJl1%$4`z)h>frrh!(kU88Z5%&AB|Hi2id$v+mketmpI8(jyetPCwpU zCT7F4jkmg#pq!m4nN(uz|9DDfb<}G$b9ZlSs(yW0cp_S79QiDlpy)vb)1oL=TXzJ9 zRoUzY+yfZm%q5vBv5InDA2onX?k_Q;?`1GBkP-=$bAArah<-zr02$&h2r+)0;#&@M zdP-)V%~J>D*6AjPt<0rG#N5#Q-qwQcFR^PTgQSSdZ$4!zKZse5IbNNqApWTEFG?wv zC3Pg%1Skvb;_HU4!ysmVAa6lKF8>RDRAP+%~J?PK=k6oYU0Hrf24#o0ojxd7jcPx+td3-ovf< zddnROCPkP8ktAjD6ppmQ%3ReZHr0G}OIdS%Pm<$e$(Ym8wY?)FJV{1Iu^DN}3H*Hs zZAT@RtH4(!_GBA>QOS!?{JKtj z(jP9N5H3HBi%BrZ$Vul!99vWxFVb%m|A5PB0>r_sr)@cU8~SXj{aFNEFt4f+)CO2R zr_HYY)(CNF2;G}!!V86s!mGpOpD$Xac$ENoPf=R><%>Qm9K3#{TZbod?Wq&ubh7iG zf)l)Vwuj0uMm4QE+B{;TX*=G_C?A8ahiV=so}{0T&FGPjo{2y1-IwPdzI$YN{X@V~ ze7mQm=Noqcw7m3pJ0&0_)Lb7*#X=TrL6>8k$L^=rC?2q=^jf(|s^K`U;nme!zBj_o zI|fGb#Rv>NC-Q@EjS%C4;=&}bX>B~7h9TN^Nf&Z~{}3l$D_-j%h6$S|WqM~fnAVcV=mypa0@L9KU=Q~Yck9~1E-H@^N7 zO8up!LIzt;Om-e4+2*r#8EL!_AOqVm%Pj0wZPKKO%$W8(KEK74eDpn^Hm*IXt0X7K zLXxk`p+OHu19@Yrpt#7u>MsGj7qSZ+q-(iR$_<+Br7#P#H|V@SFAJWPdd)eR1Ppgi z%2&@hU6F1IZoii0XCga=*%b!m6&0C6WJTzYAD+D?6jiMT3`W_(;j6WZgHJ79u3?#W ziTx-Kk>qy1%R@SH^i3&xr$cKjafYwvbP`Zt5%8Itum5aVh0O69qKs70IXF0bM_(+% zD1HE^`AUri*rFYrvAYE0Z zNa3HmxaEc+G&SC^dgA#g{{mrSd)#nNbgueJqrFG@_&rX&Cj6KfO9Hzw0bmCPHYxsO z2j0ftUdfh>sjshRIFGax3&W9@G|yU=h>ugjQWZEjEomVm9$gqafb=_OPSG3atBZ~% zJ*TV7s=O%yvjvK0LoHiazqIhTH_YYLT=ioAVD;tnw-ptZBTs~io{*gE*(16w!#Ep! z7|1-DonJiI@-0BZ*%mo@RruJ)^Xc~_E<#f{w|>-N)E46xNTC~^8CA%6)jUb&JUY_&{;1(z?;YxkQP23 zAk!JAt^yQ2^+ZwfAho_RvDw2xtxR8{d%*c52x@gGB5^1#vvvItVh;pwBTYfC3`!d> zZ)|8&5~^?|#U|xMyWCy73)P3oKcY=i{HW4V?+UTqZ=VL95sI&9&Z(t!Woz{1>b&=> zl|+KTQYvHjiDUY38l~9gbo%CL1qI@3ccd;~PNTk7ivy6OXTaW75lDG%v!$wcoR{3w zFE9-NEcf%Vu1jg$PLxz^g#}NX8BPw(#Lc$Lmv^tT!}uA}Rq7U8Kd+)7TG?cFZ~1Dmm-G<|260j^#NGx zFYJDBTp&PnlqFm4%aaTR8v=US@8wOM?sCm-B4*Vj@}Y(fNGU#&*(n6tM+Pr6#;?AB z(uGY|*5!k?P;(jp>i%t*-QXoBfcK+4vEycK?^6S0fWq!lT#fmbmSnabTmF2_-tNT} zMH1qE&@aKAwLnZi3_(QtfkNRRPRGsw@1yS`_M?$QMujKun~fvXTx#!c9|+G~Qo=i; zA+-~7^=%l<(s+~xR2Mk!(*w#(!LF0Ug&gasBtS_}H*`eU73vf^AK$C+Rx$4_OYSjw zY*oGO=lr6GH?44~DVp7J#C=MikxFu~fQ^n>(Bn+u-E|N|WxFpS;0)yOlx6$kF~naO z;Qr_WCocn(M*AxJCn4L96$Q42Tk10ZY(9Lb|M)*cXPI$U4^%_f!9balRY^XIJwG%pg( zb~c3LB?c3|3IT*wFTeB}eX~|efrf=G#k0^84v~t1;vKH3Wni~|EgXHuIy^kwdmC*( zT5WVMYZ`?jBxz`Fj)HV?15hb!%-2eo13p*(6YVpi0qRk z-Zx)g5idJ9nA{Q064h^ym_@5R`%0oIsbJ9CL2sXD#EEM(?D)k)(q~#_T}98Q2CK}< zY^Z&+PSCqMckCo6*lnC5F*JSZL3kT5{V$$xw+3~ojhe7YNg$of5WUik`F;}lqdk@N%9rBYz@`fr8FWhMRk!G zX8fA<&GwbD_;y+pQe;5<<1QTTqQVhVXr-m=2YE(!WAvLhZ+hoC%bcc~jw(?5)moYf z0!(LZ9qwY=tC*Xvb7&-Le++9vbn(jCx{8(!^!J3e=r{dF1C;{f3#A8K8ByX6q~6Xd z{VfA9#}xG0gfLJ(rz6~lWiMTTORyn-Lb=M1lCrYOMu?Pjd*9xEE}SJPhdKWfFr&7p zh}2fP_83HyPaZ#hZ^gYTn)4=@yrPrn?5>hjknKIOr8}z!KC~tX`5!>lAJy!X)6$Aa zbZlbp)q?{n9QgUdchTkL<+)J-hyze*`UVAsXFgrdczzC;GXNxwN2>4~UG)mt+=K2| zG-dwn8W=A2aKwM5VsuAcL_~y{_TIseeV-nvUV3#4zqhfLZma+8_$?Cke097@Qo$_@ z6pn0rb0gN))-IFl(p`D?{{5el5%-EN`3VS~ZuE2C(1xa%A!g^nkDA6=uJSGzA z3h}2BgslD%vt7Vw+85?Lc@hPO4saTLBT4yo%LPScf5lPg@Z$l594-;-ng^n1d!qZa zFyk&{;XMlzP=QV+(L^Ogvhe^dEMCy=f$9T+#^q#d`s-*+(5>%K+WGHX82>}jaxF75 zF9#1x$6pZdqlMYAj(~4ArroB!(pcxoOA@^@AJ3np-VDT^2_)NxE-Q7{g&~{vSGg!0 z0z7K>@rc;kEM^mziznOftp&osDqaoUWdvPy`uAJ)$B2?QCnE>kvq{MAJu_InaOfNv#`why)#KtwlAM7+=4q54ayaeW>tleT8?GS~ zq&T)G?#QBwuAM`2UZm^~d{XIJ1vF)z$qBd9f=zu*Y>i40C}

pliIvwRk0Nfr1SJ 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

6n-* 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­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

thN>HtJd>3hw*f?eB-VoZ8;< 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%0Dg{RzK9U5W>rIxilQmy?^_k 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 QTLpfH5IzU0ypEs$9gdlPjsO4v literal 0 HcmV?d00001 diff --git a/hotswap/pom.xml b/hotswap/pom.xml new file mode 100644 index 00000000..961ddb5e --- /dev/null +++ b/hotswap/pom.xml @@ -0,0 +1,252 @@ + + + 4.0.0 + + com.zfoo + hotswap + 3.0 + + jar + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + + com.zfoo + util + ${zfoo.util.version} + + + + com.zfoo + scheduler + ${zfoo.scheduler.version} + + + + + org.javassist + javassist + ${javassist.version} + + + + net.bytebuddy + byte-buddy + ${bytebuddy.version} + + + + net.bytebuddy + byte-buddy-agent + ${bytebuddy.version} + + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + src/main/resources/META-INF/MANIFEST.MF + + + + + + + + diff --git a/hotswap/src/main/java/com/zfoo/hotswap/HotSwapContext.java b/hotswap/src/main/java/com/zfoo/hotswap/HotSwapContext.java new file mode 100644 index 00000000..e77e02d6 --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/HotSwapContext.java @@ -0,0 +1,26 @@ +package com.zfoo.hotswap; + +import com.zfoo.hotswap.manager.HotSwapManager; +import com.zfoo.hotswap.service.HotSwapServiceMBean; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class HotSwapContext { + + private static final HotSwapContext HOT_SWAP_CONTEXT = new HotSwapContext(); + + private HotSwapContext() { + + } + + public static HotSwapServiceMBean getHotSwapService() { + return HotSwapServiceMBean.getSingleInstance(); + } + + public static HotSwapManager getHotSwapManager() { + return HotSwapManager.getInstance(); + } + +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/agent/HotSwapAgent.java b/hotswap/src/main/java/com/zfoo/hotswap/agent/HotSwapAgent.java new file mode 100644 index 00000000..e7750ebc --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/agent/HotSwapAgent.java @@ -0,0 +1,67 @@ +package com.zfoo.hotswap.agent; + +import com.zfoo.protocol.util.IOUtils; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.Instrumentation; + +/** + * 使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序, + * 甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了, + * 这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。 + * + * @author jaysunxiao + */ +public class HotSwapAgent { + + private static final String HOT_SWAP_MANAGER = "com.zfoo.hotswap.manager.HotSwapManager"; + + private static final String UPDATE_BYTES_FIELD = "updateBytes"; + + private static final String EXCEPTION = "exception"; + + + /* + Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。 与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足: + 在manifest中指定Agent-Class属性,值为代理类全路径 + 代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。 + 不过如此设计的再运行时进行代理有个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。 + Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。 + */ + public static void agentmain(String args, Instrumentation inst) { + Class clazz = null; + DataInputStream dis = null; + try { + clazz = Class.forName(HOT_SWAP_MANAGER); + byte[] byteArray = (byte[]) clazz.getField(UPDATE_BYTES_FIELD).get(null); + dis = new DataInputStream(new ByteArrayInputStream(byteArray)); + int len = dis.readInt(); + ClassDefinition[] classDefs = new ClassDefinition[len]; + for (int i = 0; i < len; i++) { + //类名 + String className = dis.readUTF(); + //类字节码 + byte[] classBytes = new byte[dis.readInt()]; + dis.readFully(classBytes); + classDefs[i] = new ClassDefinition(Class.forName(className), classBytes); + } + //开始更新 + inst.redefineClasses(classDefs); + //更新成功 + } catch (Exception e) { + if (clazz != null) { + try { + clazz.getField(EXCEPTION).set(null, e); + } catch (Exception e1) { + throw new RuntimeException(e1); + } + } + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(dis); + } + } + +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/manager/HotSwapManager.java b/hotswap/src/main/java/com/zfoo/hotswap/manager/HotSwapManager.java new file mode 100644 index 00000000..1d368e6c --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/manager/HotSwapManager.java @@ -0,0 +1,34 @@ +package com.zfoo.hotswap.manager; + +import com.zfoo.hotswap.model.ClassFileDef; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class HotSwapManager implements IHotSwapManager { + + private static final HotSwapManager HOT_SWAP_MANAGER = new HotSwapManager(); + + private static Map classFileDefMap = new HashMap<>(); + + public static volatile byte[] updateBytes; + + public static volatile Exception exception; + + private HotSwapManager() { + } + + public static HotSwapManager getInstance() { + return HOT_SWAP_MANAGER; + } + + @Override + public Map getClassFileDefMap() { + return classFileDefMap; + } + +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/manager/IHotSwapManager.java b/hotswap/src/main/java/com/zfoo/hotswap/manager/IHotSwapManager.java new file mode 100644 index 00000000..6a4c0433 --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/manager/IHotSwapManager.java @@ -0,0 +1,15 @@ +package com.zfoo.hotswap.manager; + +import com.zfoo.hotswap.model.ClassFileDef; + +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IHotSwapManager { + + Map getClassFileDefMap(); + +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/model/ClassFileDef.java b/hotswap/src/main/java/com/zfoo/hotswap/model/ClassFileDef.java new file mode 100644 index 00000000..639bca4a --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/model/ClassFileDef.java @@ -0,0 +1,64 @@ +package com.zfoo.hotswap.model; + +import com.zfoo.util.security.MD5Utils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ClassFileDef { + + private String path; + private String className; + private byte[] data; + private long lastModifyTime; + private String md5; + + public ClassFileDef(String className, String path, long lastModifyTime, byte[] data) { + this.className = className; + this.path = path; + this.lastModifyTime = lastModifyTime; + this.data = data; + this.md5 = MD5Utils.bytesToMD5(data); + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public long getLastModifyTime() { + return lastModifyTime; + } + + public void setLastModifyTime(long lastModifyTime) { + this.lastModifyTime = lastModifyTime; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/service/HotSwapServiceMBean.java b/hotswap/src/main/java/com/zfoo/hotswap/service/HotSwapServiceMBean.java new file mode 100644 index 00000000..cc1cb448 --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/service/HotSwapServiceMBean.java @@ -0,0 +1,224 @@ +package com.zfoo.hotswap.service; + +import com.sun.tools.attach.AgentInitializationException; +import com.sun.tools.attach.AgentLoadException; +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; +import com.zfoo.hotswap.HotSwapContext; +import com.zfoo.hotswap.manager.HotSwapManager; +import com.zfoo.hotswap.model.ClassFileDef; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; +import javassist.bytecode.ClassFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.management.MBeanServer; +import javax.management.MXBean; +import javax.management.ObjectName; +import java.io.*; +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +//java11过后ClassFile对外不可见 +//import com.sun.tools.classfile.ClassFile; + +/** + * JMX(JAVA Management Extensions)技术是java5的新特性,它提供一种简单,标准的方式去管理应用程序,设备,服务等资源。 + * JMS定义了一些设计模式,api和一些服务来进行应用程序和网络的监控,这些都是基于java语言环境的。 + * 使用JMS技术,资源被一种叫做MBeans(Managed Beans)监控,这些MBean都在一个核心对象管理server上注册。 + * JMS给java开发者提供了自由的方式去监控java代码,创建智能java agents,实现分布式管理的中间件和管理者,并且能够快速整合这些方案到的管理和监控系统。 + *

+ * 根据JMX描述,MBean接口包括一些可读或者可写的属性,还有一些定义好的方法,这些方法能够被MBean管理应用程序调用。 + * 实现类,类名必须为接口sufixMBean的前缀。也就是Hello。如果不按这个命名注册MBean就会有问题。 + * + * @author jaysunxiao + * @version 3.0 + */ + +@MXBean +public class HotSwapServiceMBean implements IHotSwapServiceMBean { + + private static final Logger logger = LoggerFactory.getLogger(HotSwapServiceMBean.class); + + // 热更新的代理和热更新的文件要放在同一个目录 + private static final String HOT_SWAP_SCRIPT = "hotscript"; + private static final String HOT_SWAP_AGENT = HOT_SWAP_SCRIPT + "/hotswap-2.0.jar"; + + public static final String CLASS_SUFFIX = ".class"; + public static final String JAVA_SUFFIX = ".java"; + + private static final HotSwapServiceMBean HOT_SWAP_SERVICE = new HotSwapServiceMBean(); + + private HotSwapServiceMBean() { + registerMBean(); + } + + // 使用jconsole去连接,当然也可以使用RMI进行远程连接MBean server,来进行管理和执行操作。 + private void registerMBean() { + //注册监控 + try { + //获取MBeanServer 如果没有MBean server存在那么下面会自动调用ManagementFactory.createMBeanServer() + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + // 包名加 类名 创建一个ObjectName + ObjectName objectName = new ObjectName(this.getClass().getPackage().getName() + ":type=" + this.getClass().getSimpleName()); + // 在MBean server上注册MBean + mbs.registerMBean(this, objectName); + } catch (Exception e) { + logger.error("MBean error", e); + } + } + + public static HotSwapServiceMBean getSingleInstance() { + return HOT_SWAP_SERVICE; + } + + @Override + public void hotSwapByRelativePath(String relativePath) { + hotSwapByAbsolutePath(FileUtils.getProAbsPath() + File.separator + relativePath); + } + + @Override + public synchronized void hotSwapByAbsolutePath(String absolutePath) { + // 热更新class文件,上一步没有完成的操作本次继续执行 + try { + hotSwapClass(absolutePath); + } catch (Exception e) { + logger.error("热更新class文件异常:[exception:{}]", e); + } + } + + @Override + public void logAllUpdateClassFileInfo() { + int count = 0; + for (Map.Entry entry : HotSwapManager.getInstance().getClassFileDefMap().entrySet()) { + logger.info("[{}].更新的类名称:[{}],更改的时间:[{}]", ++count, entry.getKey(), entry.getValue().getLastModifyTime()); + } + } + + + private void hotSwapClass(String absolutePath) throws Exception { + long start = System.currentTimeMillis(); + + ByteArrayOutputStream baos = null; + DataOutputStream dos = null; + try { + // 本次需要更新的Class + Map updateClassMap = new HashMap<>(); + List fileList = FileUtils.getAllReadableFiles(new File(absolutePath)); + for (File file : fileList) { + if (!file.getName().endsWith(CLASS_SUFFIX)) { + continue; + } + String path = file.getAbsolutePath(); + long lastModifiedTime = file.lastModified(); + byte[] data = FileUtils.readFileToByteArray(file); + String className = readClassName(data); + ClassFileDef classFileDef = new ClassFileDef(className, path, lastModifiedTime, data); + updateClassMap.put(classFileDef.getClassName(), classFileDef); + } + + if (updateClassMap.isEmpty()) { + logger.debug("本次更新没有如何文件"); + return; + } + + // 验证本次更新的所有class文件是否合法 + Map classFileDefMap = HotSwapContext.getHotSwapManager().getClassFileDefMap(); + for (Map.Entry entry : updateClassMap.entrySet()) {// 读取所有本次需要热更新的字节码 + ClassFileDef classFileDef = entry.getValue(); + // 上一次热更新的文件 + ClassFileDef lastClassFileDef = classFileDefMap.get(classFileDef.getClassName()); + if (lastClassFileDef != null && !classFileDef.getClassName().equals(lastClassFileDef.getClassName())) { + logger.error("本次热更新失败,两次热更新文件的类名称不一致,转换失败:[{}]-->[{}]" + , classFileDef.getClassName(), lastClassFileDef.getClassName()); + return; + } + } + + + // 创建临时更新流 + baos = new ByteArrayOutputStream(1024); + dos = new DataOutputStream(baos); + // 先写需要热更新类的数量 + dos.writeInt(updateClassMap.size()); + for (ClassFileDef classFileDef : updateClassMap.values()) { + dos.writeUTF(classFileDef.getClassName());// 写类名称 + dos.writeInt(classFileDef.getData().length);// 字节码的长度 + dos.write(classFileDef.getData());// 字节码 + } + dos.flush(); + //设置状态,准备更新 + HotSwapManager.updateBytes = baos.toByteArray(); + HotSwapManager.exception = null; + + //更新 + loadHotSwapAgent(); + + if (HotSwapManager.exception != null) { + for (ClassFileDef classFileDef : updateClassMap.values()) { + logger.error("类[{}]热更新失败", classFileDef.getClassName()); + } + logger.error("热更新失败原因:", HotSwapManager.exception); + return; + } + + for (ClassFileDef classFileDef : updateClassMap.values()) { + logger.info("类[{}]热更新成功", classFileDef.getClassName()); + } + long end = TimeUtils.currentTimeMillis(); + logger.info("本次热更新总耗时:[{}]毫秒", end - start); + + // 更新成功,保存更新记录 + classFileDefMap.putAll(updateClassMap); + + // 删除所有被更新过的java和class文件 + for (ClassFileDef def : updateClassMap.values()) { + FileUtils.deleteFile(new File(def.getPath())); + } + } finally { + IOUtils.closeIO(dos, baos); + } + } + + private void loadHotSwapAgent() throws Exception { + VirtualMachine vm = null; + try { + // 获取运行当前这个类的jvm的pid + String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0]; + // attach到这个pid建立通信管道,让jvm加载agent + vm = VirtualMachine.attach(pid); + vm.loadAgent(HOT_SWAP_AGENT); + } catch (AgentInitializationException | AgentLoadException | AttachNotSupportedException | IOException e) { + logger.error("热更新开启VirtualMachine失败", e); + throw e; + } finally { + try { + if (vm != null) { + vm.detach(); + } + } catch (IOException e) { + logger.error("热更新关闭vm失败", e); + } + } + } + + + private String readClassName(byte[] bytes) { + ByteArrayInputStream byteArrayInputStream = null; + DataInputStream dataInputStream = null; + try { + dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes)); + var classFile = new ClassFile(dataInputStream); + return classFile.getName().replaceAll(StringUtils.SLASH, StringUtils.PERIOD); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(dataInputStream, byteArrayInputStream); + } + } +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/service/IHotSwapServiceMBean.java b/hotswap/src/main/java/com/zfoo/hotswap/service/IHotSwapServiceMBean.java new file mode 100644 index 00000000..4cbc975a --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/service/IHotSwapServiceMBean.java @@ -0,0 +1,28 @@ +package com.zfoo.hotswap.service; + +import javax.management.MXBean; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@MXBean +public interface IHotSwapServiceMBean { + + /** + * 热更新相对于项目路径的文件 + * + * @param relativePath 相对路径 + */ + void hotSwapByRelativePath(String relativePath); + + /** + * 热更新绝对路径的文件 + * + * @param absolutePath 热更新文件的绝对路径 + */ + void hotSwapByAbsolutePath(String absolutePath); + + void logAllUpdateClassFileInfo(); + +} diff --git a/hotswap/src/main/java/com/zfoo/hotswap/util/HotSwapUtils.java b/hotswap/src/main/java/com/zfoo/hotswap/util/HotSwapUtils.java new file mode 100644 index 00000000..055875ba --- /dev/null +++ b/hotswap/src/main/java/com/zfoo/hotswap/util/HotSwapUtils.java @@ -0,0 +1,96 @@ +package com.zfoo.hotswap.util; + +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.bytecode.ClassFile; +import javassist.util.HotSwapAgent; +import net.bytebuddy.agent.ByteBuddyAgent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.lang.instrument.ClassDefinition; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class HotSwapUtils { + + private static final Logger logger = LoggerFactory.getLogger(HotSwapUtils.class); + + private static String readClassName(byte[] bytes) { + ByteArrayInputStream byteArrayInputStream = null; + DataInputStream dataInputStream = null; + try { + dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes)); + var classFile = new ClassFile(dataInputStream); + return classFile.getName().replaceAll(StringUtils.SLASH, StringUtils.PERIOD); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(dataInputStream, byteArrayInputStream); + } + } + + /** + * 热更新java文件 + * jvm的启动参数,jdk11过后默认的全款更不允许连接自己,VM: -Djdk.attach.allowAttachSelf=true + *

+ * 优先使用简单的Javassist做热更新,因为Byte Buddy使用了更为复杂的ASM,spring boot中会使用Byte Buddy热更新 + * + * @param bytes .class结尾的字节码文件 + */ + public static synchronized void hotswapClass(byte[] bytes) { + if (bytes == null || bytes.length <= 0) { + return; + } + + Class clazz = null; + try { + clazz = Class.forName(readClassName(bytes)); + } catch (ClassNotFoundException e) { + logger.error(ExceptionUtils.getMessage(e)); + } + + hotswapClassByJavassist(clazz, bytes); + } + + private static void hotswapClassByJavassist(Class clazz, byte[] bytes) { + ByteArrayInputStream byteArrayInputStream = null; + CtClass ctClass = null; + try { + clazz = Class.forName(readClassName(bytes)); + byteArrayInputStream = new ByteArrayInputStream(bytes); + ctClass = ClassPool.getDefault().makeClass(byteArrayInputStream); + // Javassist热更新 + HotSwapAgent.redefine(clazz, ctClass); + logger.info("Javassist热更新[{}]成功", clazz); + } catch (Throwable t) { + logger.info("无法使用Javassist热更新,开始使用替补方案Byte Buddy做热更新", t); + hotswapClassByByteBuddy(clazz, bytes); + } finally { + IOUtils.closeIO(byteArrayInputStream); + if (ctClass != null) { + ctClass.defrost(); + } + } + } + + private static void hotswapClassByByteBuddy(Class clazz, byte[] bytes) { + try { + // Byte Buddy热更新 + var instrumentation = ByteBuddyAgent.install(); + instrumentation.redefineClasses(new ClassDefinition(clazz, bytes)); + logger.info("Byte Buddy热更新[{}]成功", clazz); + } catch (Throwable t) { + logger.error("Byte Buddy热更新未知异常,热更新[{}]失败", clazz); + } + } + +} diff --git a/hotswap/src/main/resources/META-INF/MANIFEST.MF b/hotswap/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 00000000..6ca9502c --- /dev/null +++ b/hotswap/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Agent-Class: com.zfoo.hotswap.agent.HotSwapAgent +Can-Redefine-Classes: true +Can-Retransform-Classes: true +Can-Set-Native-Method-Prefix: true diff --git a/hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java b/hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java new file mode 100644 index 00000000..4fb95df5 --- /dev/null +++ b/hotswap/src/test/java/com/zfoo/hotswap/ApplicationTest.java @@ -0,0 +1,32 @@ +package com.zfoo.hotswap; + +import com.zfoo.hotswap.util.HotSwapUtils; +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.IOUtils; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; + +/** + * @author jaysunxiao + */ +@Ignore +public class ApplicationTest { + + // 热更新教程,需要添加JVM参数,-Djdk.attach.allowAttachSelf=true,如果不加这个参数将使用Byte Buddy热更新替代Javassist热更新 + // 使用Javassist热更新更加的轻量,如果Javassist热更新失败,则会自动使用Byte Buddy做热更新 + @Test + public void startHotSwapTest() throws IOException { + // 热更新限制,不能为需要热更新的类添加或减少成员函数和成员变量,只能修改函数内部的逻辑 + var test = new HotswapClass(); + // 没有热更新的输出 + test.print(); + // 随便修改print方法,然后编译成为一个需要热更新的class文件 + HotSwapUtils.hotswapClass(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("HotswapClass.class"))); + // 热更新之后的输出 + test.print(); + } + +} + diff --git a/hotswap/src/test/java/com/zfoo/hotswap/HotswapClass.java b/hotswap/src/test/java/com/zfoo/hotswap/HotswapClass.java new file mode 100644 index 00000000..48cf6ae4 --- /dev/null +++ b/hotswap/src/test/java/com/zfoo/hotswap/HotswapClass.java @@ -0,0 +1,17 @@ +package com.zfoo.hotswap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + */ +public class HotswapClass { + + private static final Logger logger = LoggerFactory.getLogger(HotswapClass.class); + + public void print() { + logger.info("没有热更新的输出内容"); + } + +} diff --git a/hotswap/src/test/resources/logback-test.xml b/hotswap/src/test/resources/logback-test.xml new file mode 100644 index 00000000..5f49fcd4 --- /dev/null +++ b/hotswap/src/test/resources/logback-test.xml @@ -0,0 +1,43 @@ + + + + + com.zfoo.hotswap + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + diff --git a/monitor/pom.xml b/monitor/pom.xml new file mode 100644 index 00000000..3f3860a8 --- /dev/null +++ b/monitor/pom.xml @@ -0,0 +1,234 @@ + + + 4.0.0 + + com.zfoo + monitor + 3.0 + + jar + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + com.zfoo + util + ${zfoo.util.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + com.zfoo + scheduler + ${zfoo.scheduler.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + diff --git a/monitor/src/main/java/com/zfoo/monitor/model/DiskFileSystemVO.java b/monitor/src/main/java/com/zfoo/monitor/model/DiskFileSystemVO.java new file mode 100644 index 00000000..a7598c30 --- /dev/null +++ b/monitor/src/main/java/com/zfoo/monitor/model/DiskFileSystemVO.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor.model; + +import com.zfoo.monitor.util.OSUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class DiskFileSystemVO implements Comparable { + + private String name; + + private long size; + + private long available; + + private long timestamp; + + public static DiskFileSystemVO valueOf(String name, long size, long available, long timestamp) { + var vo = new DiskFileSystemVO(); + vo.name = name; + vo.size = size; + vo.available = available; + vo.timestamp = timestamp; + return vo; + } + + public String pressure() { + var usage = 1D * (size - available) / size; + if (usage >= 0.8) { + var tempVO = this.toGB(); + return StringUtils.format("df - 磁盘[name:{}]空间过高[size:{}GB][available:{}GB][usage:{}][{}]" + , name, tempVO.getSize(), tempVO.getAvailable(), OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp)); + } + return StringUtils.EMPTY; + } + + @Override + public int compareTo(DiskFileSystemVO target) { + if (target == null) { + return 1; + } + if (!this.name.equals(target.getName())) { + return 0; + } + + var a = 1D * (this.size - this.available) / this.size; + var b = 1D * (target.getSize() - target.getAvailable()) / target.getSize(); + return Double.compare(a, b); + } + + public DiskFileSystemVO toMB() { + var size = this.size / IOUtils.BYTES_PER_MB; + var available = this.available / IOUtils.BYTES_PER_MB; + return DiskFileSystemVO.valueOf(this.name, size, available, timestamp); + } + + public DiskFileSystemVO toGB() { + var size = (long) Math.ceil(1D * this.size / IOUtils.BYTES_PER_GB); + var available = (long) Math.ceil(1D * this.available / IOUtils.BYTES_PER_GB); + return DiskFileSystemVO.valueOf(this.name, size, available, timestamp); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public long getAvailable() { + return available; + } + + public void setAvailable(long available) { + this.available = available; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/monitor/src/main/java/com/zfoo/monitor/model/MemoryVO.java b/monitor/src/main/java/com/zfoo/monitor/model/MemoryVO.java new file mode 100644 index 00000000..c8faa384 --- /dev/null +++ b/monitor/src/main/java/com/zfoo/monitor/model/MemoryVO.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor.model; + +import com.zfoo.monitor.util.OSUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MemoryVO implements Comparable { + + private long total; + + private long available; + + private long timestamp; + + public static MemoryVO valueOf(long total, long available, long timestamp) { + var vo = new MemoryVO(); + vo.total = total; + vo.available = available; + vo.timestamp = timestamp; + return vo; + } + + public String pressure() { + var usage = 1D * (total - available) / total; + if (usage >= 0.8) { + var tempVO = this.toGB(); + return StringUtils.format("free - 内存占用过高[total:{}GB][available:{}GB][usage:{}][{}]" + , tempVO.getTotal(), tempVO.getAvailable(), OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp)); + } + return StringUtils.EMPTY; + } + + @Override + public int compareTo(MemoryVO target) { + if (target == null) { + return 1; + } + + var a = 1D * (this.total - this.available) / this.total; + var b = 1D * (target.getTotal() - target.getAvailable()) / target.getTotal(); + return Double.compare(a, b); + } + + public MemoryVO toMB() { + var total = this.total / IOUtils.BYTES_PER_MB; + var available = this.available / IOUtils.BYTES_PER_MB; + return MemoryVO.valueOf(total, available, timestamp); + } + + public MemoryVO toGB() { + var total = (long) Math.ceil(1D * this.total / IOUtils.BYTES_PER_GB); + var available = (long) Math.ceil(1D * this.available / IOUtils.BYTES_PER_GB); + return MemoryVO.valueOf(total, available, timestamp); + } + + public long getTotal() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public long getAvailable() { + return available; + } + + public void setAvailable(long available) { + this.available = available; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/monitor/src/main/java/com/zfoo/monitor/model/MonitorVO.java b/monitor/src/main/java/com/zfoo/monitor/model/MonitorVO.java new file mode 100644 index 00000000..a663ff1d --- /dev/null +++ b/monitor/src/main/java/com/zfoo/monitor/model/MonitorVO.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor.model; + +import com.zfoo.monitor.util.OSUtils; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; +import com.zfoo.util.net.NetUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MonitorVO { + + private String uuid; + + private UptimeVO uptime; + + private List df; + + private MemoryVO free; + + private List sar; + + public static MonitorVO valueOf(String uuid, UptimeVO uptime, List df, MemoryVO free, List sar) { + var vo = new MonitorVO(); + vo.uuid = uuid; + vo.uptime = uptime; + vo.df = df; + vo.free = free; + vo.sar = sar; + return vo; + } + + public List toPressures() { + var messages = new ArrayList(); + + var uptimeMessage = uptime.pressure(); + if (!StringUtils.isBlank(uptimeMessage)) { + messages.add(uptimeMessage); + } + + for (var fileSystem : df) { + var dfMessage = fileSystem.pressure(); + if (!StringUtils.isBlank(dfMessage)) { + messages.add(dfMessage); + } + } + + + var freeMessage = free.pressure(); + if (!StringUtils.isBlank(freeMessage)) { + messages.add(freeMessage); + } + + for (var networkIF : sar) { + var sarMessage = networkIF.pressure(); + if (!StringUtils.isBlank(sarMessage)) { + messages.add(sarMessage); + } + } + return messages; + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append(StringUtils.format("Server Monitor [ip:{}] [uuid:{}]", NetUtils.getLocalhostStr(), uuid)); + builder.append(FileUtils.LS); + builder.append(StringUtils.format("1.cpu: [{}] [{}] [{}] [usage:{}] [{}]" + , uptime.getOneMinute(), uptime.getFiveMinute(), uptime.getFiftyMinute(), OSUtils.toPercent(uptime.getUsage()), TimeUtils.timeToString(uptime.getTimestamp()))); + builder.append(FileUtils.LS); + builder.append(StringUtils.format("2.memory: [total:{}GB] [available:{}GB] [usage:{}] [{}]" + , free.toGB().getTotal(), free.toGB().getAvailable(), OSUtils.toPercent(1D * (free.getTotal() - free.getAvailable()) / free.getTotal()), TimeUtils.timeToString(free.getTimestamp()))); + builder.append(FileUtils.LS); + builder.append("3.disk: "); + builder.append(FileUtils.LS); + df.forEach(it -> { + builder.append(StringUtils.format(" [disk:{}] [size:{}GB] [available:{}GB] [usage:{}] [{}]" + , it.getName(), it.toGB().getSize(), it.toGB().getAvailable(), OSUtils.toPercent(1D * (it.getSize() - it.getAvailable()) / it.getSize()), TimeUtils.timeToString(it.getTimestamp()))); + builder.append(FileUtils.LS); + }); + builder.append("4.network:"); + builder.append(FileUtils.LS); + sar.forEach(it -> { + builder.append(StringUtils.format(" [interface:{}] [rxpck:{}] [txpck:{}] [rxBytes:{}] [txBytes:{}] [inErrors:{}] [outErrors:{}] [inDrops:{}] [collisions:{}] [{}]" + , it.getName(), it.getRxpck(), it.getTxpck(), it.getRxBytes(), it.getTxBytes(), it.getInErrors(), it.getOutErrors(), it.getInDrops(), it.getCollisions(), TimeUtils.timeToString(it.getTimestamp()))); + builder.append(FileUtils.LS); + }); + var pressures = toPressures(); + if (CollectionUtils.isNotEmpty(pressures)) { + builder.append("summary of errors:"); + builder.append(FileUtils.LS); + pressures.forEach(it -> builder.append(StringUtils.format(" {}", it)).append(FileUtils.LS)); + } + + return builder.toString(); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public UptimeVO getUptime() { + return uptime; + } + + public void setUptime(UptimeVO uptime) { + this.uptime = uptime; + } + + public List getDf() { + return df; + } + + public void setDf(List df) { + this.df = df; + } + + public MemoryVO getFree() { + return free; + } + + public void setFree(MemoryVO free) { + this.free = free; + } + + public List getSar() { + return sar; + } + + public void setSar(List sar) { + this.sar = sar; + } +} diff --git a/monitor/src/main/java/com/zfoo/monitor/model/SarVO.java b/monitor/src/main/java/com/zfoo/monitor/model/SarVO.java new file mode 100644 index 00000000..eeb96f23 --- /dev/null +++ b/monitor/src/main/java/com/zfoo/monitor/model/SarVO.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor.model; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SarVO implements Comparable { + + private String name; + + private long rxpck; + + private long txpck; + + private long rxBytes; + + private long txBytes; + + private long inErrors; + + private long outErrors; + + private long inDrops; + + private long collisions; + + private long timestamp; + + public static SarVO valueOf(String name, long rxpck, long txpck, long rxBytes, long txBytes + , long inErrors, long outErrors, long inDrops, long collisions, long timestamp) { + var vo = new SarVO(); + vo.name = name; + vo.rxpck = rxpck; + vo.txpck = txpck; + vo.rxBytes = rxBytes; + vo.txBytes = txBytes; + vo.inErrors = inErrors; + vo.outErrors = outErrors; + vo.inDrops = inDrops; + vo.collisions = collisions; + vo.timestamp = timestamp; + return vo; + } + + public String pressure() { + if (rxpck >= 5_0000) { + return StringUtils.format("sar - 网卡流量[interface:{}] [rxpck:{}] [txpck:{}] [rxBytes:{}] [txBytes:{}] [inErrors:{}] [outErrors:{}] [inDrops:{}] [collisions:{}] [{}],性能影响:危险" + , name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, TimeUtils.timeToString(timestamp)); + } + if (inErrors > 0 || outErrors > 0 || inDrops > 0 || collisions > 0) { + return StringUtils.format("sar - 网卡流量[interface:{}] [rxpck:{}] [txpck:{}] [rxBytes:{}] [txBytes:{}] [inErrors:{}] [outErrors:{}] [inDrops:{}] [collisions:{}] [{}],性能影响:低" + , name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, TimeUtils.timeToString(timestamp)); + } + return StringUtils.EMPTY; + } + + @Override + public int compareTo(SarVO target) { + if (target == null) { + return 1; + } + if (!this.name.equals(target.getName())) { + return 0; + } + + var a = this.rxpck + this.txpck; + var b = target.getRxpck() + target.getTxpck(); + return Long.compare(a, b); + } + + @Override + public String toString() { + return StringUtils.format("[name:{}][rxpck:{}][txpck:{}][rxBytes:{}][txBytes:{}][inErrors:{}][outErrors:{}][inDrops:{}][collisions:{}][time:{}]" + , name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, TimeUtils.timeToString(timestamp)); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getRxpck() { + return rxpck; + } + + public void setRxpck(long rxpck) { + this.rxpck = rxpck; + } + + public long getTxpck() { + return txpck; + } + + public void setTxpck(long txpck) { + this.txpck = txpck; + } + + public long getRxBytes() { + return rxBytes; + } + + public void setRxBytes(long rxBytes) { + this.rxBytes = rxBytes; + } + + public long getTxBytes() { + return txBytes; + } + + public void setTxBytes(long txBytes) { + this.txBytes = txBytes; + } + + public long getInErrors() { + return inErrors; + } + + public void setInErrors(long inErrors) { + this.inErrors = inErrors; + } + + public long getOutErrors() { + return outErrors; + } + + public void setOutErrors(long outErrors) { + this.outErrors = outErrors; + } + + public long getInDrops() { + return inDrops; + } + + public void setInDrops(long inDrops) { + this.inDrops = inDrops; + } + + public long getCollisions() { + return collisions; + } + + public void setCollisions(long collisions) { + this.collisions = collisions; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/monitor/src/main/java/com/zfoo/monitor/model/UptimeVO.java b/monitor/src/main/java/com/zfoo/monitor/model/UptimeVO.java new file mode 100644 index 00000000..6c3b070e --- /dev/null +++ b/monitor/src/main/java/com/zfoo/monitor/model/UptimeVO.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor.model; + +import com.zfoo.monitor.util.OSUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class UptimeVO implements Comparable { + + private double oneMinute; + + private double fiveMinute; + + private double fiftyMinute; + + private double usage; + + private long timestamp; + + public static UptimeVO valueOf(double oneMinute, double fiveMinute, double fiftyMinute, double usage, long timestamp) { + var vo = new UptimeVO(); + vo.oneMinute = oneMinute; + vo.fiveMinute = fiveMinute; + vo.fiftyMinute = fiftyMinute; + vo.usage = usage; + vo.timestamp = timestamp; + return vo; + } + + public String pressure() { + var processors = OSUtils.availableProcessors(); + var one = oneMinute / processors; + var five = fiveMinute / processors; + var fifty = fiftyMinute / processors; + + if (usage >= 0.8) { + return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}]过大,性能影响:危险" + , oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp)); + } + + if (one > 5 || five > 5 || fifty > 5) { + return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}]过大,性能影响:警告" + , oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp)); + } + + if (one > 4 || five > 4 || fifty > 4) { + return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}],性能影响:高" + , oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp)); + } + + if (one > 3 || five > 3 || fifty > 3) { + return StringUtils.format("uptime - cpu负载[{}][{}][{}][usage:{}][{}],性能影响:中,考虑优化" + , oneMinute, fiveMinute, fiftyMinute, OSUtils.toPercent(usage), TimeUtils.timeToString(timestamp)); + } + + + return StringUtils.EMPTY; + } + + @Override + public int compareTo(UptimeVO target) { + if (target == null) { + return 1; + } + + return Double.compare(this.usage, target.getUsage()); + } + + public double getOneMinute() { + return oneMinute; + } + + public void setOneMinute(double oneMinute) { + this.oneMinute = oneMinute; + } + + public double getFiveMinute() { + return fiveMinute; + } + + public void setFiveMinute(double fiveMinute) { + this.fiveMinute = fiveMinute; + } + + public double getFiftyMinute() { + return fiftyMinute; + } + + public void setFiftyMinute(double fiftyMinute) { + this.fiftyMinute = fiftyMinute; + } + + public double getUsage() { + return usage; + } + + public void setUsage(double usage) { + this.usage = usage; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/monitor/src/main/java/com/zfoo/monitor/util/OSUtils.java b/monitor/src/main/java/com/zfoo/monitor/util/OSUtils.java new file mode 100644 index 00000000..392b915f --- /dev/null +++ b/monitor/src/main/java/com/zfoo/monitor/util/OSUtils.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor.util; + +import com.zfoo.monitor.model.*; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; +import com.zfoo.util.security.IdUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import oshi.SystemInfo; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.hardware.NetworkIF; +import oshi.software.os.OperatingSystem; + +import java.io.InputStream; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Oshi库封装的工具类,通过此工具类,可获取系统、硬件相关信息 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class OSUtils { + + private static final Logger logger = LoggerFactory.getLogger(OSUtils.class); + + /** + * cpu的数量 + */ + private static final int processors = Runtime.getRuntime().availableProcessors(); + + /** + * 系统信息 + */ + private static final SystemInfo systemInfo = new SystemInfo(); + + /** + * 硬件信息 + */ + private static final HardwareAbstractionLayer hardware = systemInfo.getHardware(); + + /** + * 操作系统信息 + */ + private static final OperatingSystem os = systemInfo.getOperatingSystem(); + + /** + * 网络信息 + */ + private static final List networkIFs = hardware.getNetworkIFs(); + + /** + * cpu的tick次数信息 + */ + private static long[] ticks = hardware.getProcessor().getSystemCpuLoadTicks(); + + public static int availableProcessors() { + return processors; + } + + /** + * 将小于0的num转为百分比,舍弃的部分将会做四舍五入 + */ + public static String toPercent(double num) { + if (num > 1) { + throw new RuntimeException("转为百分比的num必须小于1"); + } + var percentFormat = NumberFormat.getPercentInstance(); + // 最大小数位数 + percentFormat.setMaximumFractionDigits(2); + // 最大整数位数 + percentFormat.setMaximumIntegerDigits(2); + // 最小小数位数 + percentFormat.setMinimumFractionDigits(2); + // 最小整数位数 + percentFormat.setMinimumIntegerDigits(2); + // 自动转换成百分比显示 + return percentFormat.format(num); + } + + /** + * 对应于Linux中的uptime命令,windows中无法统计,所以在windows返回的结果默认是-1 + */ + public static UptimeVO uptime() { + var processor = hardware.getProcessor(); + var loads = processor.getSystemLoadAverage(3); + var oneMinute = loads[0]; + var fiveMinute = loads[1]; + var fiftyMinute = loads[2]; + + var cpuTicks = processor.getSystemCpuLoadTicks(); + var usage = processor.getSystemCpuLoadBetweenTicks(ticks); + + ticks = cpuTicks; + return UptimeVO.valueOf(oneMinute, fiveMinute, fiftyMinute, usage, TimeUtils.now()); + } + + /** + * 对应于Linux中的df -h命令,兼容windows + */ + public static List df() { + var fileSystems = os.getFileSystem().getFileStores(); + var df = new ArrayList(); + var nameMap = new HashMap(); + for (var fs : fileSystems) { + var name = fs.getName(); + var size = fs.getTotalSpace(); + var available = fs.getFreeSpace(); + + var value = nameMap.get(name); + if (value == null) { + nameMap.put(name, 1); + } else { + name = name + value; + nameMap.put(name, ++value); + } + + df.add(DiskFileSystemVO.valueOf(name, size, available, TimeUtils.now())); + } + return df; + } + + /** + * 对应于Linux中的free命令,兼容windows + */ + public static MemoryVO free() { + var memory = hardware.getMemory(); + var total = memory.getTotal(); + var available = memory.getAvailable(); + return MemoryVO.valueOf(total, available, TimeUtils.now()); + } + + + /** + * 对应于Linux中的sar -n DEV 1命令,兼容windows + */ + public static List sar() { + var sar = new ArrayList(); + for (var networkIF : networkIFs) { + var name = networkIF.getDisplayName() + StringUtils.SPACE + networkIF.getName(); + var oldTimestamp = networkIF.getTimeStamp(); + var oldBytesRecv = networkIF.getBytesRecv(); + var oldBytesSent = networkIF.getBytesSent(); + var oldPacketsRecv = networkIF.getPacketsRecv(); + var oldPacketsSent = networkIF.getPacketsSent(); + var oldInErrors = networkIF.getInErrors(); + var oldOutErrors = networkIF.getOutErrors(); + var oldInDrops = networkIF.getInDrops(); + var oldCollisions = networkIF.getCollisions(); + + networkIF.updateAttributes(); + var timestamp = networkIF.getTimeStamp(); + var timeInterval = (timestamp - oldTimestamp) / 1000D; + var rxpck = (long) Math.ceil(((networkIF.getPacketsRecv() - oldPacketsRecv) / timeInterval)); + var txpck = (long) Math.ceil((networkIF.getPacketsSent() - oldPacketsSent) / timeInterval); + var rxBytes = (long) Math.ceil((networkIF.getBytesRecv() - oldBytesRecv) / timeInterval); + var txBytes = (long) Math.ceil((networkIF.getBytesSent() - oldBytesSent) / timeInterval); + var inErrors = networkIF.getInErrors() - oldInErrors; + var outErrors = networkIF.getOutErrors() - oldOutErrors; + var inDrops = networkIF.getInDrops() - oldInDrops; + var collisions = networkIF.getCollisions() - oldCollisions; + + sar.add(SarVO.valueOf(name, rxpck, txpck, rxBytes, txBytes, inErrors, outErrors, inDrops, collisions, timestamp)); + } + return sar; + } + + private static UptimeVO maxUptime; + private static Map maxDfMap; + private static MemoryVO maxFree; + private static Map maxSarMap; + + static { + initMonitor(); + } + + public static void initMonitor() { + maxUptime = uptime(); + maxDfMap = new ConcurrentHashMap<>(df().stream().collect(Collectors.toMap(key -> key.getName(), value -> value))); + maxFree = free(); + maxSarMap = new ConcurrentHashMap<>(sar().stream().collect(Collectors.toMap(key -> key.getName(), value -> value))); + } + + public static MonitorVO maxMonitor() { + var uuid = IdUtils.getUUID(); + var monitor = MonitorVO.valueOf(uuid, maxUptime, new ArrayList<>(maxDfMap.values()), maxFree, new ArrayList<>(maxSarMap.values())); + + initMonitor(); + return monitor; + } + + public static MonitorVO monitor() { + var uuid = IdUtils.getUUID(); + var uptime = uptime(); + var df = df(); + var free = free(); + var sar = sar(); + + if (uptime.compareTo(maxUptime) > 0) { + maxUptime = uptime; + } + + for (var fileSystem : df) { + var maxFileSystem = maxDfMap.get(fileSystem.getName()); + if (maxFileSystem != null && fileSystem.compareTo(maxFileSystem) > 0) { + maxDfMap.put(fileSystem.getName(), fileSystem); + } + } + + if (free.compareTo(maxFree) > 0) { + maxFree = free; + } + + for (var networkIF : sar) { + var maxNetworkIF = maxSarMap.get(networkIF.getName()); + if (maxNetworkIF != null && networkIF.compareTo(maxNetworkIF) > 0) { + maxSarMap.put(maxNetworkIF.getName(), networkIF); + } + } + + return MonitorVO.valueOf(uuid, uptime, df, free, sar); + } + + public static String execCommand(String command) { + Process process = null; + InputStream inputStream = null; + try { + process = new ProcessBuilder(command.split(" ")) + .redirectErrorStream(true) + .start(); + + //取得命令结果的输出流 + inputStream = process.getInputStream(); + var bytes = IOUtils.toByteArray(inputStream); + var result = StringUtils.bytesToString(bytes); + + // 其他线程都等待这个线程完成 + process.waitFor(); + // 获取javac线程的退出值,0代表正常退出,非0代表异常中止 + int exitValue = process.exitValue(); + + // 返回编译是否成功 + if (exitValue != 0) { + throw new Exception("执行命令错误,返回码:" + exitValue); + } + + return result; + } catch (Exception e) { + logger.error("命令执行未知异常", e); + } finally { + if (process != null) { + process.destroy(); + } + IOUtils.closeIO(inputStream); + } + + return StringUtils.EMPTY; + } + +} diff --git a/monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java b/monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java new file mode 100644 index 00000000..d1b3fdcd --- /dev/null +++ b/monitor/src/test/java/com/zfoo/monitor/ApplicationTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.monitor; + +import com.zfoo.monitor.util.OSUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.util.ThreadUtils; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import oshi.SystemInfo; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ApplicationTest { + + /** + * 仿Linux的uptime指令,可以用来监控cpu的负载 + */ + @Test + public void uptimeTest() { + var vo = OSUtils.uptime(); + System.out.println(JsonUtils.object2String(vo)); + System.out.println(vo.pressure()); + } + + /** + * 仿Linux的df指令,可以用来监控硬盘容量 + */ + @Test + public void dfTest() { + var df = OSUtils.df(); + df.forEach(it -> { + System.out.println(JsonUtils.object2String(it.toGB())); + System.out.println(it.pressure()); + }); + } + + /** + * 仿Linux的free指令,可以用来监控内存占用 + */ + @Test + public void freeTest() { + var free = OSUtils.free(); + System.out.println(JsonUtils.object2String(free.toGB())); + System.out.println(free.pressure()); + } + + /** + * 仿Linux的sar指令,可以用来监控网络IO + */ + @Test + public void sarTest() { + var sar = OSUtils.sar(); + sar.forEach(it -> { + System.out.println(JsonUtils.object2String(it)); + System.out.println(it.pressure()); + }); + } + + /** + * cpu的tick大小测试 + */ + @Ignore + @Test + public void cpuTest() { + var systemInfo = new SystemInfo(); + var hardware = systemInfo.getHardware(); + var os = systemInfo.getOperatingSystem(); + + while (true) { + var oldTicks = hardware.getProcessor().getSystemCpuLoadTicks(); + ThreadUtils.sleep(1000); + var usage = hardware.getProcessor().getSystemCpuLoadBetweenTicks(oldTicks); + System.out.println(usage); + } + } + + /** + * 控制台指令执行测试 + */ + @Ignore + @Test + public void execCommandTest() { + var str = OSUtils.execCommand("cmd /c jps"); + System.out.println(str); + } + + @Test + public void toPercentTest() { + var num = 0.123456D; + var str = OSUtils.toPercent(num); + Assert.assertEquals(str, "12.35%"); + } + + @Test + public void monitorTest() { + var monitor = OSUtils.monitor(); + System.out.println(monitor); + ThreadUtils.sleep(1000); + monitor = OSUtils.monitor(); + System.out.println(monitor); + monitor = OSUtils.maxMonitor(); + System.out.println(monitor); + } + +} diff --git a/net/README.md b/net/README.md new file mode 100644 index 00000000..0082bf67 --- /dev/null +++ b/net/README.md @@ -0,0 +1,35 @@ +### Ⅰ. 简介 + +1. 优雅的同步和异步请求,速度更快 +2. 服务注册和发现,配置中心使用的是zookeeper,可扩展成其它注册中心 +3. 自带高性能网关,自定义转发策略 +4. 服务可伸缩,负载均衡,集群监控,应有尽有。 +4. 基于Java11,所有的依赖包都是最新的jar包 + +```关键词 +变态的高性能,高可用性,高伸缩性(一般指增加机器),高扩展性(一般指代码层面的开闭原则) + +config,本地配置,zookeeper的注册发现,请求的负载均衡,都放在这个包下 +core,核心包,服务器,客户端的统一封装 +dispatcher,消息的分发 +handler,netty的handler,定义了客户端,服务器的一些通用handler +protocol,消息类的注册,消息的编解码,字节码增强等 +schema,spring的自定义标签的解析 +session,对netty的channel的封装 +task,通用任务线程池 +``` + +### Ⅱ. 服务器架构图 + +服务器架构图
+ +### Ⅲ. 网络通信规范 + +- 客户端对服务器的请求以Request结尾,返回以Response结尾 +- 服务器内部之间的调用以Ask结尾,返回以Answer结尾。 + +### Ⅳ. 教程 + +- [单机服务器教程](src/test/java/com/zfoo/net/core/tcp/server/TcpServerTest.java) +- [RPC教程](src/test/java/com/zfoo/net/core/provider/ProviderTest.java) +- [网关教程](src/test/java/com/zfoo/net/core/gateway/GatewayTest.java) diff --git a/net/pom.xml b/net/pom.xml new file mode 100644 index 00000000..1e089c02 --- /dev/null +++ b/net/pom.xml @@ -0,0 +1,330 @@ + + + 4.0.0 + + com.zfoo + net + 3.0 + + jar + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + com.zfoo + util + ${zfoo.util.version} + + + com.zfoo + event + ${zfoo.event.version} + + + com.zfoo + scheduler + ${zfoo.util.version} + + + com.zfoo + protocol + ${zfoo.protocol.version} + + + + + io.netty + netty-all + ${netty.version} + + + + + org.javassist + javassist + ${javassist.version} + + + + + org.apache.zookeeper + zookeeper + ${zookeeper.version} + + + io.netty + netty-all + + + io.netty + netty-handler + + + io.netty + netty-transport-native-epoll + + + slf4j-api + org.slf4j + + + slf4j-log4j12 + org.slf4j + + + log4j + log4j + + + commons-lang + commons-lang + + + + + + + + org.apache.curator + curator-framework + ${curator.version} + + + com.google.guava + guava + + + zookeeper + org.apache.zookeeper + + + slf4j-api + org.slf4j + + + + + + + + org.apache.curator + curator-recipes + ${curator.version} + + + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + diff --git a/net/src/main/java/com/zfoo/net/NetContext.java b/net/src/main/java/com/zfoo/net/NetContext.java new file mode 100644 index 00000000..2077b7ce --- /dev/null +++ b/net/src/main/java/com/zfoo/net/NetContext.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.net.config.manager.IConfigManager; +import com.zfoo.net.consumer.service.IConsumer; +import com.zfoo.net.core.AbstractServer; +import com.zfoo.net.core.tcp.TcpClient; +import com.zfoo.net.dispatcher.manager.IPacketDispatcher; +import com.zfoo.net.packet.service.IPacketService; +import com.zfoo.net.schema.NetProcessor; +import com.zfoo.net.session.manager.ISessionManager; +import com.zfoo.net.task.TaskManager; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.scheduler.SchedulerContext; +import com.zfoo.util.ThreadUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; + +import java.lang.reflect.Field; +import java.util.concurrent.ExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NetContext implements ApplicationListener, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(NetContext.class); + + private static NetContext instance; + + private ApplicationContext applicationContext; + + private IConfigManager configManager; + + private IPacketService packetService; + + private IPacketDispatcher packetDispatcher; + + private ISessionManager sessionManager; + + private IConsumer consumer; + + public static NetContext getNetContext() { + return instance; + } + + public static ApplicationContext getApplicationContext() { + return instance.applicationContext; + } + + public static IConfigManager getConfigManager() { + return instance.configManager; + } + + public static IPacketService getPacketService() { + return instance.packetService; + } + + public static ISessionManager getSessionManager() { + return instance.sessionManager; + } + + public static IPacketDispatcher getDispatcher() { + return instance.packetDispatcher; + } + + public static IConsumer getConsumer() { + return instance.consumer; + } + + @Override + public void onApplicationEvent(ApplicationContextEvent event) { + if (event instanceof ContextRefreshedEvent) { + if (instance != null) { + return; + } + + NetContext.instance = this; + instance.applicationContext = event.getApplicationContext(); + instance.configManager = applicationContext.getBean(IConfigManager.class); + instance.packetService = applicationContext.getBean(IPacketService.class); + instance.packetDispatcher = applicationContext.getBean(IPacketDispatcher.class); + instance.consumer = applicationContext.getBean(IConsumer.class); + instance.sessionManager = applicationContext.getBean(ISessionManager.class); + + var beanNames = applicationContext.getBeanDefinitionNames(); + var processor = applicationContext.getBean(NetProcessor.class); + for (var beanName : beanNames) { + processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName); + } + + NetContext.getPacketService().init(); + NetContext.getConfigManager().initRegistry(); + + } else if (event instanceof ContextClosedEvent) { + shutdownBefore(); + shutdownAfter(); + } + } + + + @Override + public int getOrder() { + return 0; + } + + public synchronized static void shutdownBefore() { + SchedulerContext.shutdown(); + } + + public static synchronized void shutdownAfter() { + // 关闭zookeeper的客户端 + NetContext.getConfigManager().getRegistry().shutdown(); + + + // 先关闭所有session + NetContext.getSessionManager().shutdown(); + + // 关闭客户端和服务器 + TcpClient.shutdown(); + AbstractServer.shutdownAllServers(); + + // 关闭TaskManager + try { + Field field = EventBus.class.getDeclaredField("executors"); + ReflectionUtils.makeAccessible(field); + + var executors = (ExecutorService[]) ReflectionUtils.getField(field, TaskManager.getInstance()); + for (ExecutorService executor : executors) { + ThreadUtils.shutdown(executor); + } + } catch (Throwable e) { + logger.error("Net thread pool failed shutdown: " + ExceptionUtils.getMessage(e)); + return; + } + + logger.info("Net shutdown gracefully."); + } + +} diff --git a/net/src/main/java/com/zfoo/net/config/manager/ConfigManager.java b/net/src/main/java/com/zfoo/net/config/manager/ConfigManager.java new file mode 100644 index 00000000..ad732eeb --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/manager/ConfigManager.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.manager; + +import com.zfoo.net.config.model.NetConfig; +import com.zfoo.net.consumer.balancer.AbstractConsumerLoadBalancer; +import com.zfoo.net.consumer.registry.IRegistry; +import com.zfoo.net.consumer.registry.ZookeeperRegistry; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.util.AssertionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConfigManager implements IConfigManager { + + private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class); + + /** + * 本地配置 + */ + private NetConfig localConfig; + + private AbstractConsumerLoadBalancer consumerLoadBalancer; + + /** + * 注册中心 + */ + private IRegistry registry; + + @Override + public NetConfig getLocalConfig() { + return localConfig; + } + + public void setLocalConfig(NetConfig localConfig) { + this.localConfig = localConfig; + } + + @Override + public AbstractConsumerLoadBalancer consumerLoadBalancer() { + return consumerLoadBalancer; + } + + @Override + public void initRegistry() { + // 通过protocol,写入provider的module的id和version + var providerConfig = localConfig.getProviderConfig(); + if (Objects.nonNull(providerConfig) && CollectionUtils.isNotEmpty(providerConfig.getModules())) { + var providerModules = new ArrayList(providerConfig.getModules().size()); + for (var providerModule : providerConfig.getModules()) { + var module = ProtocolManager.moduleByModuleName(providerModule.getName()); + AssertionUtils.isTrue(module != null, "服务提供者[name:{}]在协议文件中不存在", providerModule.getName()); + providerModules.add(module); + } + providerConfig.setModules(providerModules); + } + + // 通过protocol,写入consumer的module的id和version + var consumerConfig = localConfig.getConsumerConfig(); + if (Objects.nonNull(consumerConfig) && CollectionUtils.isNotEmpty(consumerConfig.getModules())) { + var consumerModules = new ArrayList(consumerConfig.getModules().size()); + for (var providerModule : consumerConfig.getModules()) { + var module = ProtocolManager.moduleByModuleName(providerModule.getName()); + AssertionUtils.isTrue(module != null, "消费者[name:{}]在协议文件中不存在", providerModule.getName()); + consumerModules.add(module); + } + consumerConfig.setModules(consumerModules); + consumerLoadBalancer = AbstractConsumerLoadBalancer.valueOf(consumerConfig.getLoadBalancer()); + } + + registry = new ZookeeperRegistry(); + registry.start(); + } + + @Override + public IRegistry getRegistry() { + return registry; + } + +} diff --git a/net/src/main/java/com/zfoo/net/config/manager/IConfigManager.java b/net/src/main/java/com/zfoo/net/config/manager/IConfigManager.java new file mode 100644 index 00000000..48a8e713 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/manager/IConfigManager.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.manager; + +import com.zfoo.net.config.model.NetConfig; +import com.zfoo.net.consumer.balancer.AbstractConsumerLoadBalancer; +import com.zfoo.net.consumer.registry.IRegistry; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IConfigManager { + + NetConfig getLocalConfig(); + + AbstractConsumerLoadBalancer consumerLoadBalancer(); + + void initRegistry(); + + IRegistry getRegistry(); +} diff --git a/net/src/main/java/com/zfoo/net/config/model/ConsumerConfig.java b/net/src/main/java/com/zfoo/net/config/model/ConsumerConfig.java new file mode 100644 index 00000000..6407c7a3 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/model/ConsumerConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.model; + + +import com.zfoo.protocol.registration.ProtocolModule; + +import java.util.List; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConsumerConfig { + + private String loadBalancer; + + private List modules; + + public static ConsumerConfig valueOf(String loadBalancer, List modules) { + ConsumerConfig config = new ConsumerConfig(); + config.loadBalancer = loadBalancer; + config.modules = modules; + return config; + } + + public static ConsumerConfig valueOf(List modules) { + ConsumerConfig config = new ConsumerConfig(); + config.modules = modules; + return config; + } + + public String getLoadBalancer() { + return loadBalancer; + } + + public void setLoadBalancer(String loadBalancer) { + this.loadBalancer = loadBalancer; + } + + public List getModules() { + return modules; + } + + public void setModules(List modules) { + this.modules = modules; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConsumerConfig that = (ConsumerConfig) o; + return Objects.equals(modules, that.modules); + } + + @Override + public int hashCode() { + return Objects.hash(modules); + } +} diff --git a/net/src/main/java/com/zfoo/net/config/model/HostConfig.java b/net/src/main/java/com/zfoo/net/config/model/HostConfig.java new file mode 100644 index 00000000..d2780f83 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/model/HostConfig.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.model; + +import java.util.Map; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class HostConfig { + + private String center; + private String user; + private String password; + private Map addressMap; + + public void setCenter(String center) { + this.center = center; + } + + public String getCenter() { + return center; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Map getAddressMap() { + return addressMap; + } + + public void setAddressMap(Map addressMap) { + this.addressMap = addressMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HostConfig that = (HostConfig) o; + return Objects.equals(center, that.center) && + Objects.equals(user, that.user) && + Objects.equals(password, that.password) && + Objects.equals(addressMap, that.addressMap); + } + + @Override + public int hashCode() { + return Objects.hash(center, user, password, addressMap); + } +} diff --git a/net/src/main/java/com/zfoo/net/config/model/MonitorConfig.java b/net/src/main/java/com/zfoo/net/config/model/MonitorConfig.java new file mode 100644 index 00000000..ba39d935 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/model/MonitorConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.model; + +import java.util.Map; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MonitorConfig { + + private String center; + private String user; + private String password; + private Map addressMap; + + public static MonitorConfig valueOf(String center, String user, String password, Map addressMap) { + MonitorConfig config = new MonitorConfig(); + config.center = center; + config.user = user; + config.password = password; + config.addressMap = addressMap; + return config; + } + + public String getCenter() { + return center; + } + + public void setCenter(String center) { + this.center = center; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Map getAddressMap() { + return addressMap; + } + + public void setAddressMap(Map addressMap) { + this.addressMap = addressMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MonitorConfig that = (MonitorConfig) o; + return Objects.equals(center, that.center) && + Objects.equals(user, that.user) && + Objects.equals(password, that.password) && + Objects.equals(addressMap, that.addressMap); + } + + @Override + public int hashCode() { + return Objects.hash(center, user, password, addressMap); + } +} diff --git a/net/src/main/java/com/zfoo/net/config/model/NetConfig.java b/net/src/main/java/com/zfoo/net/config/model/NetConfig.java new file mode 100644 index 00000000..92e28866 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/model/NetConfig.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.model; + +import com.zfoo.net.consumer.registry.RegisterVO; +import com.zfoo.protocol.generate.GenerateOperation; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NetConfig { + + + private String id; + private String protocolLocation; + + /** + * 协议生成属性变量对应于{@link GenerateOperation} + */ + private boolean foldProtocol; + private String protocolParam; + private boolean generateJsProtocol; + private boolean generateCsProtocol; + private boolean generateLuaProtocol; + + private RegistryConfig registryConfig; + private MonitorConfig monitorConfig; + private HostConfig hostConfig; + + private ProviderConfig providerConfig; + private ConsumerConfig consumerConfig; + + + public RegisterVO toLocalRegisterVO() { + return RegisterVO.valueOf(id, providerConfig, consumerConfig); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getProtocolLocation() { + return protocolLocation; + } + + public void setProtocolLocation(String protocolLocation) { + this.protocolLocation = protocolLocation; + } + + public boolean isFoldProtocol() { + return foldProtocol; + } + + public void setFoldProtocol(boolean foldProtocol) { + this.foldProtocol = foldProtocol; + } + + public String getProtocolParam() { + return protocolParam; + } + + public void setProtocolParam(String protocolParam) { + this.protocolParam = protocolParam; + } + + public boolean isGenerateJsProtocol() { + return generateJsProtocol; + } + + public void setGenerateJsProtocol(boolean generateJsProtocol) { + this.generateJsProtocol = generateJsProtocol; + } + + public boolean isGenerateCsProtocol() { + return generateCsProtocol; + } + + public void setGenerateCsProtocol(boolean generateCsProtocol) { + this.generateCsProtocol = generateCsProtocol; + } + + public boolean isGenerateLuaProtocol() { + return generateLuaProtocol; + } + + public void setGenerateLuaProtocol(boolean generateLuaProtocol) { + this.generateLuaProtocol = generateLuaProtocol; + } + + public RegistryConfig getRegistryConfig() { + return registryConfig; + } + + public void setRegistryConfig(RegistryConfig registryConfig) { + this.registryConfig = registryConfig; + } + + public MonitorConfig getMonitorConfig() { + return monitorConfig; + } + + public void setMonitorConfig(MonitorConfig monitorConfig) { + this.monitorConfig = monitorConfig; + } + + public HostConfig getHostConfig() { + return hostConfig; + } + + public void setHostConfig(HostConfig hostConfig) { + this.hostConfig = hostConfig; + } + + public ProviderConfig getProviderConfig() { + return providerConfig; + } + + public void setProviderConfig(ProviderConfig providerConfig) { + this.providerConfig = providerConfig; + } + + public ConsumerConfig getConsumerConfig() { + return consumerConfig; + } + + public void setConsumerConfig(ConsumerConfig consumerConfig) { + this.consumerConfig = consumerConfig; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + NetConfig netConfig = (NetConfig) o; + return generateJsProtocol == netConfig.generateJsProtocol && + Objects.equals(id, netConfig.id) && + Objects.equals(protocolLocation, netConfig.protocolLocation) && + Objects.equals(registryConfig, netConfig.registryConfig) && + Objects.equals(monitorConfig, netConfig.monitorConfig) && + Objects.equals(hostConfig, netConfig.hostConfig) && + Objects.equals(providerConfig, netConfig.providerConfig) && + Objects.equals(consumerConfig, netConfig.consumerConfig); + } + + @Override + public int hashCode() { + return Objects.hash(id, protocolLocation, generateJsProtocol, registryConfig, monitorConfig, hostConfig, providerConfig, consumerConfig); + } +} diff --git a/net/src/main/java/com/zfoo/net/config/model/ProviderConfig.java b/net/src/main/java/com/zfoo/net/config/model/ProviderConfig.java new file mode 100644 index 00000000..18c751f2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/model/ProviderConfig.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.model; + +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.net.HostAndPort; +import com.zfoo.util.net.NetUtils; + +import java.util.List; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ProviderConfig { + + public static transient final int DEFAULT_PORT = 12400; + + /** + * 对应于ITaskDispatch + */ + private String dispatch; + + private String dispatchThread; + + private String address; + + private List modules; + + public static ProviderConfig valueOf(String address, List modules) { + ProviderConfig config = new ProviderConfig(); + config.address = address; + config.modules = modules; + return config; + } + + public HostAndPort localHostAndPortOrDefault() { + if (StringUtils.isBlank(address)) { + var defaultHostAndPort = HostAndPort.valueOf(NetUtils.getLocalhostStr(), NetUtils.getAvailablePort(ProviderConfig.DEFAULT_PORT)); + this.address = defaultHostAndPort.toHostAndPortStr(); + return defaultHostAndPort; + } + return HostAndPort.valueOf(address); + } + + public String getDispatch() { + return dispatch; + } + + public void setDispatch(String dispatch) { + this.dispatch = dispatch; + } + + public String getDispatchThread() { + return dispatchThread; + } + + public void setDispatchThread(String dispatchThread) { + this.dispatchThread = dispatchThread; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getModules() { + return modules; + } + + public void setModules(List modules) { + this.modules = modules; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProviderConfig that = (ProviderConfig) o; + return Objects.equals(address, that.address) && Objects.equals(modules, that.modules); + } + + @Override + public int hashCode() { + return Objects.hash(address, modules); + } +} diff --git a/net/src/main/java/com/zfoo/net/config/model/RegistryConfig.java b/net/src/main/java/com/zfoo/net/config/model/RegistryConfig.java new file mode 100644 index 00000000..523e6124 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/config/model/RegistryConfig.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config.model; + +import com.zfoo.protocol.util.StringUtils; + +import java.util.Map; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RegistryConfig { + + private String center; + private String user; + private String password; + private Map addressMap; + + public static RegistryConfig valueOf(String center, String user, String password, Map addressMap) { + RegistryConfig config = new RegistryConfig(); + config.center = center; + config.user = user; + config.password = password; + config.addressMap = addressMap; + return config; + } + + public boolean hasZookeeperAuthor() { + return !(StringUtils.isBlank(user) || StringUtils.isBlank(password)); + } + + public String toZookeeperAuthor() { + return user + StringUtils.COLON + password; + } + + public String getCenter() { + return center; + } + + public void setCenter(String center) { + this.center = center; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Map getAddressMap() { + return addressMap; + } + + public void setAddressMap(Map addressMap) { + this.addressMap = addressMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RegistryConfig that = (RegistryConfig) o; + return Objects.equals(center, that.center) && + Objects.equals(user, that.user) && + Objects.equals(password, that.password) && + Objects.equals(addressMap, that.addressMap); + } + + @Override + public int hashCode() { + return Objects.hash(center, user, password, addressMap); + } +} diff --git a/net/src/main/java/com/zfoo/net/consumer/balancer/AbstractConsumerLoadBalancer.java b/net/src/main/java/com/zfoo/net/consumer/balancer/AbstractConsumerLoadBalancer.java new file mode 100644 index 00000000..d467fc66 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/balancer/AbstractConsumerLoadBalancer.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.balancer; + +import com.zfoo.net.NetContext; +import com.zfoo.net.consumer.registry.RegisterVO; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.util.StringUtils; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AbstractConsumerLoadBalancer implements IConsumerLoadBalancer { + + public static AbstractConsumerLoadBalancer valueOf(String loadBalancer) { + AbstractConsumerLoadBalancer balancer; + switch (loadBalancer) { + case "random": + balancer = RandomConsumerLoadBalancer.getInstance(); + break; + case "consistent-hash": + balancer = ConsistentHashConsumerLoadBalancer.getInstance(); + break; + case "shortest-time": + balancer = ShortestTimeConsumerLoadBalancer.getInstance(); + break; + default: + throw new RuntimeException(StringUtils.format("无法识别负载均衡器[{}]", loadBalancer)); + } + return balancer; + } + + public List getSessionsByPacket(IPacket packet) { + return getSessionsByModule(ProtocolManager.moduleByProtocolId(packet.protocolId())); + } + + public List getSessionsByModule(ProtocolModule module) { + var clientSessionMap = NetContext.getSessionManager().getClientSessionMap(); + var sessions = clientSessionMap.values().stream() + .filter(it -> { + var attribute = it.getAttribute(AttributeType.CONSUMER); + if (Objects.nonNull(attribute)) { + var registerVO = (RegisterVO) attribute; + if (Objects.nonNull(registerVO.getProviderConfig()) && registerVO.getProviderConfig().getModules().contains(module)) { + return true; + } else { + return false; + } + } else { + return false; + } + }) + .collect(Collectors.toList()); + return sessions; + } + + + public boolean sessionHasModule(Session session, IPacket packet) { + + var attribute = session.getAttribute(AttributeType.CONSUMER); + if (Objects.isNull(attribute)) { + return false; + } + + var registerVO = (RegisterVO) attribute; + if (Objects.isNull(registerVO.getProviderConfig())) { + return false; + } + + var module = ProtocolManager.moduleByProtocolId(packet.protocolId()); + return registerVO.getProviderConfig().getModules().contains(module); + } +} diff --git a/net/src/main/java/com/zfoo/net/consumer/balancer/ConsistentHashConsumerLoadBalancer.java b/net/src/main/java/com/zfoo/net/consumer/balancer/ConsistentHashConsumerLoadBalancer.java new file mode 100644 index 00000000..cd15bd0b --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/balancer/ConsistentHashConsumerLoadBalancer.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.balancer; + +import com.zfoo.net.NetContext; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.math.ConsistentHash; +import org.springframework.lang.Nullable; + +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 一致性hash负载均衡器,同一个session总是发到同一提供者 + *

+ * 通过argument计算一致性hash + * + * @author jaysunxiao + * @version 3.0 + */ +public class ConsistentHashConsumerLoadBalancer extends AbstractConsumerLoadBalancer { + + public static final ConsistentHashConsumerLoadBalancer INSTANCE = new ConsistentHashConsumerLoadBalancer(); + + private volatile int lastClientSessionChangeId = 0; + private static final Map> consistentHashMap = new ConcurrentHashMap<>(); + private static final int VIRTUAL_NODE_NUMS = 200; + + private ConsistentHashConsumerLoadBalancer() { + } + + public static ConsistentHashConsumerLoadBalancer getInstance() { + return INSTANCE; + } + + /** + * 通过argument的toString计算一致性hash,所以传入的argument一般要能代表唯一性,比如用户的id + * + * @param packet 请求包 + * @param argument 参数,一般要能代表唯一性,比如用户的id + * @return 调用的session + */ + @Override + public Session loadBalancer(IPacket packet, Object argument) { + if (argument == null) { + return RandomConsumerLoadBalancer.getInstance().loadBalancer(packet, argument); + } + + // 如果更新时间不匹配,则更新到最新的服务提供者 + var currentClientSessionChangeId = NetContext.getSessionManager().getClientSessionChangeId(); + if (currentClientSessionChangeId != lastClientSessionChangeId) { + var modules = new HashSet<>(consistentHashMap.keySet()); + + for (var module : modules) { + updateModuleToConsistentHash(module); + } + + lastClientSessionChangeId = currentClientSessionChangeId; + } + + var module = ProtocolManager.moduleByProtocolId(packet.protocolId()); + var consistentHash = consistentHashMap.get(module); + if (consistentHash == null) { + consistentHash = updateModuleToConsistentHash(module); + } + if (consistentHash == null) { + throw new RuntimeException(StringUtils.format("没有服务提供者提供服务[{}]", module)); + } + var sid = consistentHash.getRealNode(argument).getValue(); + return NetContext.getSessionManager().getClientSession(sid); + + } + + + @Nullable + private ConsistentHash updateModuleToConsistentHash(ProtocolModule module) { + var sessionStringList = getSessionsByModule(module) + .stream() + .map(session -> new Pair<>(session.getAttribute(AttributeType.CONSUMER).toString(), session.getSid())) + .sorted((a, b) -> a.getKey().compareTo(b.getKey())) + .collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(sessionStringList) && !consistentHashMap.containsKey(module)) { + consistentHashMap.remove(module); + return null; + } + + var consistentHash = new ConsistentHash<>(sessionStringList, VIRTUAL_NODE_NUMS); + consistentHashMap.put(module, consistentHash); + return consistentHash; + } + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/balancer/IConsumerLoadBalancer.java b/net/src/main/java/com/zfoo/net/consumer/balancer/IConsumerLoadBalancer.java new file mode 100644 index 00000000..a9aed868 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/balancer/IConsumerLoadBalancer.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.balancer; + +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import org.springframework.lang.Nullable; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IConsumerLoadBalancer { + + /** + * 只有一致性hash会使用这个argument参数,如果在一致性hash没有传入argument默认使用随机负载均衡 + * + * @param packet 请求包 + * @param argument 计算参数 + * @return 一个服务提供者的session + */ + Session loadBalancer(IPacket packet, @Nullable Object argument); + + default void beforeLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) { + } + + default void afterLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) { + } + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/balancer/RandomConsumerLoadBalancer.java b/net/src/main/java/com/zfoo/net/consumer/balancer/RandomConsumerLoadBalancer.java new file mode 100644 index 00000000..e23796ad --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/balancer/RandomConsumerLoadBalancer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.balancer; + +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.exception.RunException; +import com.zfoo.util.math.RandomUtils; + +/** + * 随机负载均衡器,任选服务提供者的其中之一 + * + * @author jaysunxiao + * @version 3.0 + */ +public class RandomConsumerLoadBalancer extends AbstractConsumerLoadBalancer { + + private static final RandomConsumerLoadBalancer INSTANCE = new RandomConsumerLoadBalancer(); + + private RandomConsumerLoadBalancer() { + } + + public static RandomConsumerLoadBalancer getInstance() { + return INSTANCE; + } + + @Override + public Session loadBalancer(IPacket packet, Object argument) { + var module = ProtocolManager.moduleByProtocolId(packet.protocolId()); + var sessions = getSessionsByModule(module); + + if (sessions.isEmpty()) { + throw new RunException("没有服务提供者提供服务[{}]", module); + } + + return RandomUtils.randomEle(sessions); + } + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/balancer/ShortestTimeConsumerLoadBalancer.java b/net/src/main/java/com/zfoo/net/consumer/balancer/ShortestTimeConsumerLoadBalancer.java new file mode 100644 index 00000000..fb9abbc0 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/balancer/ShortestTimeConsumerLoadBalancer.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.balancer; + +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.exception.RunException; +import com.zfoo.scheduler.util.TimeUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 最少时间调用负载均衡器,优先选择调用时间最短的session + * + * @author jaysunxiao + * @version 3.0 + */ +public class ShortestTimeConsumerLoadBalancer extends AbstractConsumerLoadBalancer { + + private static final ShortestTimeConsumerLoadBalancer INSTANCE = new ShortestTimeConsumerLoadBalancer(); + + private ShortestTimeConsumerLoadBalancer() { + } + + public static ShortestTimeConsumerLoadBalancer getInstance() { + return INSTANCE; + } + + @Override + public Session loadBalancer(IPacket packet, Object argument) { + var module = ProtocolManager.moduleByProtocolId(packet.protocolId()); + var sessions = getSessionsByModule(module); + + if (sessions.isEmpty()) { + throw new RunException("没有服务提供者提供服务[{}]", module); + } + + var sortedSessions = sessions.stream() + .sorted((a, b) -> { + var aMap = (Map) a.getAttribute(AttributeType.RESPONSE_TIME); + var bMap = (Map) b.getAttribute(AttributeType.RESPONSE_TIME); + if (aMap == null) { + return -1; + } else if (bMap == null) { + return 1; + } else { + var aTime = aMap.get(packet.protocolId()); + var bTime = bMap.get(packet.protocolId()); + if (aTime == null) { + return -1; + } else if (bTime == null) { + return 1; + } else { + return (aTime > bTime) ? 1 : -1; + } + } + }).findFirst(); + return sortedSessions.get(); + } + + @Override + public void beforeLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) { + // 因为要通过最短响应时间来路由分发消息,这里使用更精确的时间 + attachment.setTimestamp(TimeUtils.currentTimeMillis()); + } + + @Override + public void afterLoadBalancer(Session session, IPacket packet, SignalPacketAttachment attachment) { + var map = (Map) session.getAttribute(AttributeType.RESPONSE_TIME); + if (map == null) { + map = new ConcurrentHashMap<>(); + session.putAttribute(AttributeType.RESPONSE_TIME, map); + } + map.put(packet.protocolId(), TimeUtils.currentTimeMillis() - attachment.getTimestamp()); + } + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/event/ConsumerStartEvent.java b/net/src/main/java/com/zfoo/net/consumer/event/ConsumerStartEvent.java new file mode 100644 index 00000000..669316e5 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/event/ConsumerStartEvent.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.event; + +import com.zfoo.event.model.event.IEvent; +import com.zfoo.net.consumer.registry.RegisterVO; +import com.zfoo.net.session.model.Session; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConsumerStartEvent implements IEvent { + + private RegisterVO registerVO; + private Session session; + + public static ConsumerStartEvent valueOf(RegisterVO registerVO, Session session) { + var event = new ConsumerStartEvent(); + event.registerVO = registerVO; + event.session = session; + return event; + } + + public RegisterVO getRegisterVO() { + return registerVO; + } + + public void setRegisterVO(RegisterVO registerVO) { + this.registerVO = registerVO; + } + + public Session getSession() { + return session; + } + + public void setSession(Session session) { + this.session = session; + } +} diff --git a/net/src/main/java/com/zfoo/net/consumer/registry/IRegistry.java b/net/src/main/java/com/zfoo/net/consumer/registry/IRegistry.java new file mode 100644 index 00000000..a86ed023 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/registry/IRegistry.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.registry; + +import org.apache.zookeeper.CreateMode; +import org.springframework.lang.Nullable; + +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IRegistry { + + void start(); + + void checkConsumer(); + + void addData(String path, byte[] bytes, CreateMode mode); + + void removeData(String path); + + byte[] queryData(String path); + + boolean haveNode(String path); + + List children(String path); + + Set remoteProviderRegisterSet(); + + /** + * 监听path路径下的更新 + * + * @param listenerPath 需要监听的路径 + * @param updateCallback 回调方法,第一个参数是路径,第二个是变化的内容 + * @param removeCallback 回调方法,第一个参数是路径,第二个是变化的内容 + */ + void addListener(String listenerPath, @Nullable BiConsumer updateCallback, @Nullable Consumer removeCallback); + + void shutdown(); + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/registry/RegisterVO.java b/net/src/main/java/com/zfoo/net/consumer/registry/RegisterVO.java new file mode 100644 index 00000000..6f58c999 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/registry/RegisterVO.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.registry; + +import com.zfoo.net.config.model.ConsumerConfig; +import com.zfoo.net.config.model.ProviderConfig; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.security.IdUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RegisterVO { + + private static final Logger logger = LoggerFactory.getLogger(RegisterVO.class); + + private static final String uuid = IdUtils.getUUID(); + + private String id; + private ProviderConfig providerConfig; + private ConsumerConfig consumerConfig; + + + public static boolean providerHasConsumerModule(RegisterVO provider, RegisterVO consumer) { + if (Objects.isNull(provider) || Objects.isNull(provider.providerConfig) || CollectionUtils.isEmpty(provider.providerConfig.getModules()) + || Objects.isNull(consumer) || Objects.isNull(consumer.consumerConfig) || CollectionUtils.isEmpty(consumer.consumerConfig.getModules())) { + return false; + } + + return provider.getProviderConfig().getModules().stream().anyMatch(it -> consumer.getConsumerConfig().getModules().contains(it)); + } + + public static RegisterVO valueOf(String id, ProviderConfig providerConfig, ConsumerConfig consumerConfig) { + RegisterVO config = new RegisterVO(); + config.id = id; + config.providerConfig = providerConfig; + config.consumerConfig = consumerConfig; + return config; + } + + @Nullable + public static RegisterVO parseString(String str) { + try { + var vo = new RegisterVO(); + var splits = str.split("\\|"); + + vo.id = splits[0].trim(); + + String providerAddress = null; + + for (int i = 1; i < splits.length; i++) { + var s = splits[i].trim(); + if (s.startsWith("provider")) { + var providerModules = parseModules(s); + vo.providerConfig = ProviderConfig.valueOf(providerAddress, providerModules); + } else if (s.startsWith("consumer")) { + var consumerModules = parseModules(s); + vo.consumerConfig = ConsumerConfig.valueOf(consumerModules); + } else { + providerAddress = s; + } + } + + return vo; + } catch (Exception e) { + logger.error(ExceptionUtils.getMessage(e)); + return null; + } + } + + private static List parseModules(String str) { + var moduleSplits = StringUtils.substringBeforeLast( + StringUtils.substringAfterFirst(str, StringUtils.LEFT_SQUARE_BRACKET) + , StringUtils.RIGHT_SQUARE_BRACKET).split(StringUtils.COMMA); + + var modules = Arrays.stream(moduleSplits) + .map(it -> it.trim()) + .map(it -> it.split(StringUtils.HYPHEN)) + .map(it -> new ProtocolModule(Byte.parseByte(it[0]), it[1], it[2])) + .collect(Collectors.toList()); + return modules; + } + + public String toProviderString() { + return toString(); + } + + public String toConsumerString() { + return toString() + + StringUtils.SPACE + StringUtils.VERTICAL_BAR + StringUtils.SPACE + + uuid; + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append(id); + + if (Objects.nonNull(providerConfig)) { + var providerAddress = providerConfig.getAddress(); + if (StringUtils.isBlank(providerAddress)) { + throw new RuntimeException(StringUtils.format("providerConfig的address不能为空")); + } + builder.append(StringUtils.SPACE).append(StringUtils.VERTICAL_BAR).append(StringUtils.SPACE); + builder.append(providerAddress); + + builder.append(StringUtils.SPACE).append(StringUtils.VERTICAL_BAR).append(StringUtils.SPACE); + var providerModules = providerConfig.getModules().stream() + .map(it -> StringUtils.joinWith(StringUtils.HYPHEN, it.getId(), it.getName(), ProtocolModule.versionNumToStr(it.getVersion()))) + .collect(Collectors.toList()); + builder.append(StringUtils.format("provider:[{}]" + , StringUtils.joinWith(StringUtils.COMMA + StringUtils.SPACE, providerModules.toArray()))); + } + + if (Objects.nonNull(consumerConfig)) { + builder.append(StringUtils.SPACE).append(StringUtils.VERTICAL_BAR).append(StringUtils.SPACE); + + var consumerModules = consumerConfig.getModules().stream() + .map(it -> StringUtils.joinWith(StringUtils.HYPHEN, it.getId(), it.getName(), ProtocolModule.versionNumToStr(it.getVersion()))) + .collect(Collectors.toList()); + builder.append(StringUtils.format("consumer:[{}]" + , StringUtils.joinWith(StringUtils.COMMA + StringUtils.SPACE, consumerModules.toArray()))); + } + + return builder.toString(); + } + + + public String getId() { + return id; + } + + public ProviderConfig getProviderConfig() { + return providerConfig; + } + + public ConsumerConfig getConsumerConfig() { + return consumerConfig; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RegisterVO that = (RegisterVO) o; + return Objects.equals(id, that.id) && Objects.equals(providerConfig, that.providerConfig) + && Objects.equals(consumerConfig, that.consumerConfig); + } + + @Override + public int hashCode() { + return Objects.hash(id, providerConfig, consumerConfig); + } +} diff --git a/net/src/main/java/com/zfoo/net/consumer/registry/ZookeeperRegistry.java b/net/src/main/java/com/zfoo/net/consumer/registry/ZookeeperRegistry.java new file mode 100644 index 00000000..3c3e4802 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/registry/ZookeeperRegistry.java @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.registry; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.net.NetContext; +import com.zfoo.net.consumer.event.ConsumerStartEvent; +import com.zfoo.net.core.tcp.TcpClient; +import com.zfoo.net.core.tcp.TcpServer; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.util.SessionUtils; +import com.zfoo.protocol.collection.ConcurrentArrayList; +import com.zfoo.protocol.collection.ConcurrentHashSet; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.SchedulerContext; +import com.zfoo.util.ThreadUtils; +import com.zfoo.util.net.HostAndPort; +import io.netty.util.concurrent.FastThreadLocalThread; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.framework.recipes.cache.ChildData; +import org.apache.curator.framework.recipes.cache.CuratorCache; +import org.apache.curator.framework.recipes.cache.CuratorCacheListener; +import org.apache.curator.framework.state.ConnectionState; +import org.apache.curator.framework.state.ConnectionStateListener; +import org.apache.curator.retry.RetryNTimes; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * 服务注册,服务发现 + * + * @author jaysunxiao + * @version 3.0 + */ +public class ZookeeperRegistry implements IRegistry { + private static final Logger logger = LoggerFactory.getLogger(ZookeeperRegistry.class); + + private static final String ROOT_PATH = "/zfoo"; + private static final String PROVIDER_ROOT_PATH = ROOT_PATH + "/provider"; + private static final String CONSUMER_ROOT_PATH = ROOT_PATH + "/consumer"; + + private static final long RETRY_SECONDS = 5; + + private static final ExecutorService executor = Executors.newSingleThreadExecutor(new ConfigThreadFactory()); + + private static class ConfigThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + // config-p1-t1 = config-pool-1-thread-1 + ConfigThreadFactory() { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = "config-p" + poolNumber.getAndIncrement() + "-t"; + } + + @Override + public Thread newThread(Runnable runnable) { + Thread t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(false); + t.setPriority(Thread.NORM_PRIORITY); + t.setUncaughtExceptionHandler((thread, e) -> logger.error(thread.toString(), e)); + return t; + } + } + + + private CuratorFramework curator; + /** + * provider的监听 + */ + private CuratorCache providerCuratorCache; + /** + * consumer需要消费的provider集合 + */ + private Set providerCacheSet = new ConcurrentHashSet<>(); + /** + * 本地注册信息 + */ + private RegisterVO localRegisterVO = NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO(); + + + /** + * addListener中的cache全部会被添加到这个集合中,这个集合不包括providerCuratorCache + */ + private List listenerList = new ConcurrentArrayList<>(); + + @Override + public void start() { + var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig(); + if (Objects.isNull(registryConfig)) { + logger.warn("没有配置注册中心registry,将不能启用服务注册和发现"); + return; + } + + // 先启动本地服务提供者,再启动curator + startProvider(); + + startCurator(); + + startProviderCache(); + } + + private void startProvider() { + var providerConfig = NetContext.getConfigManager().getLocalConfig().getProviderConfig(); + + if (Objects.isNull(providerConfig)) { + logger.warn("没有发现服务提供者,不对外提供服务"); + return; + } + + var providerServer = new TcpServer(providerConfig.localHostAndPortOrDefault()); + providerServer.start(); + } + + private void startCurator() { + var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig(); + + if (!registryConfig.getCenter().toLowerCase().matches("zookeeper")) { + throw new IllegalArgumentException(StringUtils + .format("[center:{}]注册中心只能是zookeeper", JsonUtils.object2String(registryConfig))); + } + + var zookeeperConnectStr = HostAndPort.toHostAndPortListStr(HostAndPort.toHostAndPortList(registryConfig.getAddressMap().values())); + var builder = CuratorFrameworkFactory.builder(); + builder.connectString(zookeeperConnectStr); + if (registryConfig.hasZookeeperAuthor()) { + builder.authorization("digest", StringUtils.bytes(registryConfig.toZookeeperAuthor())); + } + builder.sessionTimeoutMs(40_000); + builder.connectionTimeoutMs(10_000); + builder.retryPolicy(new RetryNTimes(1, 3_000)); + + curator = builder.build(); + curator.getConnectionStateListenable().addListener(new ConnectionStateListener() { + @Override + public void stateChanged(CuratorFramework client, ConnectionState state) { + switch (state) { + case LOST: + // 忽略配置中心失去连接,使用本地配置的缓存 + logger.error("[zookeeper:{}]失去连接,使用缓存", zookeeperConnectStr); + break; + case SUSPENDED: + case READ_ONLY: + logger.warn("[zookeeper:{}]忽略的[state{}]", zookeeperConnectStr, state); + break; + case CONNECTED: + case RECONNECTED: + createZookeeperRootPath(); + initZookeeper(); + break; + default: + logger.error("[zookeeper:{}]未知状态[state{}]", zookeeperConnectStr, state); + } + } + }, executor); + + curator.start(); + try { + curator.blockUntilConnected(); + } catch (Throwable t) { + throw new RuntimeException("启动zookeeper异常", t); + } + } + + private void createZookeeperRootPath() { + try { + // 创建zookeeper的根路径 + var rootStat = curator.checkExists().forPath(ROOT_PATH); + if (Objects.isNull(rootStat)) { + var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig(); + var builder = curator.create(); + builder.creatingParentsIfNeeded(); + if (registryConfig.hasZookeeperAuthor()) { + var zookeeperAuthorStr = registryConfig.toZookeeperAuthor(); + var aclList = List.of(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(zookeeperAuthorStr)))); + builder.withACL(aclList); + } + builder.withMode(CreateMode.PERSISTENT); + builder.forPath(ROOT_PATH, StringUtils.bytes(registryConfig.getCenter())); + } else { + var registryConfig = NetContext.getConfigManager().getLocalConfig().getRegistryConfig(); + var bytes = curator.getData().storingStatIn(new Stat()).forPath(ROOT_PATH); + var rootPathData = StringUtils.bytesToString(bytes); + + // 检查zookeeper根节点的内容 + if (!rootPathData.equals(registryConfig.getCenter())) { + throw new RuntimeException(StringUtils.format("zookeeper的rootPath[{}]内容配置错误[{}],期望的内容是[{}],请检查相关节点并重新启动", ROOT_PATH, rootPathData, registryConfig.getCenter())); + } + + // 检查zookeeper根节点的权限 + if (registryConfig.hasZookeeperAuthor()) { + try { + var providerRootPathAclList = curator.getACL().forPath(ROOT_PATH); + AssertionUtils.notEmpty(providerRootPathAclList); + AssertionUtils.isTrue(providerRootPathAclList.size() == 1); + var zookeeperAuthorStr = registryConfig.toZookeeperAuthor(); + var aclList = List.of(new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest(zookeeperAuthorStr)))); + AssertionUtils.isTrue(providerRootPathAclList.get(0).equals(aclList.get(0))); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("zookeeper的rootPath[{}]权限配置错误[{}]", ROOT_PATH, ExceptionUtils.getMessage(e))); + } + } + + } + + + var providerStat = curator.checkExists().forPath(PROVIDER_ROOT_PATH); + if (Objects.isNull(providerStat)) { + curator.create() + .withMode(CreateMode.PERSISTENT) + .forPath(PROVIDER_ROOT_PATH, StringUtils.EMPTY_BYTES); + } + + var consumerStat = curator.checkExists().forPath(CONSUMER_ROOT_PATH); + if (Objects.isNull(consumerStat)) { + curator.create() + .withMode(CreateMode.PERSISTENT) + .forPath(CONSUMER_ROOT_PATH, StringUtils.EMPTY_BYTES); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void startProviderCache() { + // 初始化providerCache + providerCuratorCache = CuratorCache.builder(curator, PROVIDER_ROOT_PATH) + .withExceptionHandler(e -> { + logger.error("providerCuratorCache未知异常", e); + initZookeeper(); + }) + .build(); + + providerCuratorCache.listenable().addListener(new CuratorCacheListener() { + @Override + public void event(Type type, ChildData oldData, ChildData newData) { + switch (type) { + case NODE_CHANGED: + logger.error("不需要处理的[oldData:{}][newData:{}]", childDataToString(oldData), childDataToString(newData)); + initZookeeper(); + break; + case NODE_CREATED: + var providerStr = StringUtils.substringAfterFirst(newData.getPath(), PROVIDER_ROOT_PATH + StringUtils.SLASH); + var provider = RegisterVO.parseString(providerStr); + if (RegisterVO.providerHasConsumerModule(provider, localRegisterVO)) { + providerCacheSet.add(provider); + checkConsumer(); + logger.info("发现新的订阅服务[{}]", providerStr); + } + break; + case NODE_DELETED: + var oldProviderStr = StringUtils.substringAfterFirst(oldData.getPath(), PROVIDER_ROOT_PATH + StringUtils.SLASH); + var oldProvider = RegisterVO.parseString(oldProviderStr); + if (providerCacheSet.contains(oldProvider)) { + providerCacheSet.remove(oldProvider); + checkConsumer(); + logger.info("取消订阅服务[{}]", oldProviderStr); + } + break; + default: + } + } + + @Override + public void initialized() { + initZookeeper(); + } + }, executor); + + providerCuratorCache.start(); + } + + private void initZookeeper() { + executor.execute(() -> { + try { + initLocalProvider(); + + initConsumerCache(); + } catch (Exception e) { + logger.error("zookeeper初始化失败,等待[{}]秒,重新初始化", RETRY_SECONDS, e); + SchedulerContext.getSchedulerManager().schedule(new Runnable() { + @Override + public void run() { + initZookeeper(); + } + }, RETRY_SECONDS, TimeUnit.SECONDS); + } + }); + } + + private void initLocalProvider() throws Exception { + if (Objects.nonNull(localRegisterVO.getProviderConfig())) { + var localProviderVoStr = localRegisterVO.toProviderString(); + var localProviderPath = PROVIDER_ROOT_PATH + StringUtils.SLASH + localProviderVoStr; + + var localProviderStat = curator.checkExists().forPath(localProviderPath); + if (Objects.isNull(localProviderStat)) { + curator.create() + .withMode(CreateMode.EPHEMERAL) + .forPath(localProviderPath, StringUtils.EMPTY.getBytes()); + logger.info("注册服务成功[{}]", localProviderVoStr); + } else { + // 如果服务提供者已经有节点了,防止这个节点是是上次来不及删除的临时节点 + var curatorSessionId = curator.getZookeeperClient().getZooKeeper().getSessionId(); + var providerNodeSessionId = localProviderStat.getEphemeralOwner(); + if (curatorSessionId != providerNodeSessionId) { + curator.delete() + .guaranteed() + .deletingChildrenIfNeeded() + .withVersion(localProviderStat.getVersion()) + .forPath(localProviderPath); + throw new RuntimeException(StringUtils.format("curator[sessionId:{}]和providerNode[sessionId:{}]的session不一致" + , curatorSessionId, providerNodeSessionId)); + } + } + } + } + + private void initConsumerCache() throws Exception { + // 初始化providerCacheSet + var remoteProviderSet = curator.getChildren().forPath(PROVIDER_ROOT_PATH).stream() + .filter(it -> !StringUtils.isBlank(it) && !"null".equals(it)) + .map(it -> RegisterVO.parseString(it)) + .filter(it -> Objects.nonNull(it)) + .filter(it -> RegisterVO.providerHasConsumerModule(it, localRegisterVO)) + .collect(Collectors.toSet()); + + providerCacheSet.clear(); + providerCacheSet.addAll(remoteProviderSet); + + // 初始化consumer,providerCacheSet改变会导致消费者改变 + checkConsumer(); + } + + @Override + public void checkConsumer() { + if (curator.getState() == CuratorFrameworkState.STOPPED) { + return; + } + + executor.execute(() -> doCheckConsumer()); + } + + private void doCheckConsumer() { + if (curator.getState() != CuratorFrameworkState.STARTED) { + logger.error("curator还没有启动,忽略本次consumer的检查"); + return; + } + + logger.info("开始通过[providerCacheSet:{}]检查[consumer:{}]", providerCacheSet, NetContext.getSessionManager().getClientSessionMap().size()); + + var recheckFlag = false; + + for (var providerCache : providerCacheSet) { + var consumerClientList = NetContext.getSessionManager().getClientSessionMap().values().stream() + .filter(it -> { + var attribute = it.getAttribute(AttributeType.CONSUMER); + return Objects.nonNull(attribute) && attribute.equals(providerCache); + }) + .collect(Collectors.toList()); + + if (consumerClientList.size() == 1) { + var consumer = consumerClientList.get(0); + if (SessionUtils.isActive(consumer)) { + continue; + } else { + recheckFlag = true; + NetContext.getSessionManager().removeClientSession(consumer); + logger.error("[consumer:{}]失去连接,从clientSession中移除", consumer); + continue; + } + } else if (consumerClientList.size() > 1) { + logger.error("[consumerClientList:{}]中有多个重复的[RegisterVO:{}]", consumerClientList, providerCache); + continue; + } + + var client = new TcpClient(HostAndPort.valueOf(providerCache.getProviderConfig().getAddress())); + var session = client.start(); + if (Objects.isNull(session)) { + logger.error("[consumer:{}]启动失败,等待[{}]秒,重新检查consumer", providerCache, RETRY_SECONDS); + recheckFlag = true; + } else { + session.putAttribute(AttributeType.CONSUMER, providerCache); + EventBus.asyncSubmit(ConsumerStartEvent.valueOf(providerCache, session)); + + try { + var path = CONSUMER_ROOT_PATH + StringUtils.SLASH + localRegisterVO.toConsumerString(); + var stat = curator.checkExists().forPath(path); + if (Objects.isNull(stat)) { + curator.create() + .withMode(CreateMode.EPHEMERAL) + .forPath(path); + } else { + curator.setData().forPath(path); + } + + } catch (Exception e) { + // 因为并不关心consumer的状态,这种失败只需要记录一个错误日志就可以了 + logger.error("consumer写入zookeeper失败", e); + } + } + } + + if (recheckFlag) { + SchedulerContext.getSchedulerManager().schedule(new Runnable() { + @Override + public void run() { + checkConsumer(); + } + }, RETRY_SECONDS, TimeUnit.SECONDS); + } + } + + @Override + public void addData(String path, byte[] bytes, CreateMode mode) { + try { + var providerStat = curator.checkExists().forPath(path); + + if (Objects.isNull(providerStat)) { + curator.create() + .creatingParentsIfNeeded() + .withMode(mode) + .forPath(path, bytes); + } else { + curator.setData().forPath(path, bytes); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void removeData(String path) { + try { + curator.delete().guaranteed().deletingChildrenIfNeeded().forPath(path); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public byte[] queryData(String path) { + try { + return curator.getData().storingStatIn(new Stat()).forPath(path); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public boolean haveNode(String path) { + try { + return Objects.nonNull(curator.checkExists().forPath(path)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public List children(String path) { + try { + var children = curator.getChildren().forPath(path).stream() + .filter(it -> !StringUtils.isBlank(it) && !"null".equals(it)) + .collect(Collectors.toList()); + return children; + } catch (Exception e) { + logger.error("未知异常", e); + } catch (Throwable t) { + logger.error("未知错误", t); + } + return Collections.emptyList(); + } + + @Override + public Set remoteProviderRegisterSet() { + try { + var remoteProviderSet = curator.getChildren().forPath(PROVIDER_ROOT_PATH).stream() + .filter(it -> !StringUtils.isBlank(it) && !"null".equals(it)) + .map(it -> RegisterVO.parseString(it)) + .filter(it -> Objects.nonNull(it)) + .collect(Collectors.toSet()); + return remoteProviderSet; + } catch (Exception e) { + logger.error("未知异常", e); + } catch (Throwable t) { + logger.error("未知错误", t); + } + return Collections.emptySet(); + } + + @Override + public void addListener(String listenerPath, BiConsumer updateCallback, Consumer removeCallback) { + try { + var providerStat = curator.checkExists().forPath(listenerPath); + if (Objects.isNull(providerStat)) { + curator.create() + .creatingParentsIfNeeded() + .withMode(CreateMode.PERSISTENT) + .forPath(listenerPath, StringUtils.EMPTY_BYTES); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + var listener = CuratorCache.builder(curator, listenerPath).build(); + listener.listenable().addListener(new CuratorCacheListener() { + @Override + public void event(Type type, ChildData oldData, ChildData newData) { + switch (type) { + case NODE_CHANGED: + case NODE_CREATED: + logger.info("listener child updated [oldData:{}] [newData:{}]", childDataToString(oldData), childDataToString(newData)); + if (updateCallback != null) { + try { + updateCallback.accept(newData.getPath(), newData.getData()); + } catch (Exception e) { + logger.error("listener child updated error", e); + } + } + break; + case NODE_DELETED: + if (removeCallback != null) { + removeCallback.accept(oldData.getPath()); + } + break; + default: + } + } + }, executor); + listener.start(); + listenerList.add(listener); + } + + @Override + public void shutdown() { + try { + if (curator.getState() == CuratorFrameworkState.STARTED) { + // 删除服务提供者的临时节点 + if (Objects.nonNull(localRegisterVO.getProviderConfig())) { + var localProviderPath = PROVIDER_ROOT_PATH + StringUtils.SLASH + localRegisterVO.toProviderString(); + var localProviderStat = curator.checkExists().forPath(localProviderPath); + if (Objects.nonNull(localProviderStat)) { + curator.delete().guaranteed().deletingChildrenIfNeeded().forPath(localProviderPath); + } + } + + // 删除服务消费者的临时节点 + if (Objects.nonNull(localRegisterVO.getConsumerConfig())) { + var localConsumerPath = CONSUMER_ROOT_PATH + StringUtils.SLASH + localRegisterVO.toConsumerString(); + var localConsumerStat = curator.checkExists().forPath(localConsumerPath); + if (Objects.nonNull(localConsumerStat)) { + curator.delete().guaranteed().deletingChildrenIfNeeded().forPath(localConsumerPath); + } + } + } + } catch (Throwable e) { + logger.error(ExceptionUtils.getMessage(e)); + } + + try { + listenerList.forEach(it -> IOUtils.closeIO(it)); + IOUtils.closeIO(providerCuratorCache, curator); + ThreadUtils.shutdown(executor); + } catch (Throwable e) { + logger.error(ExceptionUtils.getMessage(e)); + } + } + + private String childDataToString(ChildData childData) { + if (childData == null) { + return StringUtils.EMPTY; + } + + // 只打印data数据比较小的内容 + if (childData.getData() == null || childData.getData().length <= 8) { + return childData.toString(); + } + + return StringUtils.format("[path:{}] [stat:{}] [dataSize:{}]", childData.getPath(), childData.getStat(), childData.getData().length); + } + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/service/Consumer.java b/net/src/main/java/com/zfoo/net/consumer/service/Consumer.java new file mode 100644 index 00000000..9f696505 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/service/Consumer.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.service; + +import com.zfoo.net.NetContext; +import com.zfoo.net.dispatcher.manager.PacketDispatcher; +import com.zfoo.net.dispatcher.model.answer.AsyncAnswer; +import com.zfoo.net.dispatcher.model.answer.SyncAnswer; +import com.zfoo.net.dispatcher.model.exception.ErrorResponseException; +import com.zfoo.net.dispatcher.model.exception.NetTimeOutException; +import com.zfoo.net.dispatcher.model.exception.UnexpectedProtocolException; +import com.zfoo.net.packet.common.Error; +import com.zfoo.net.packet.model.NoAnswerAttachment; +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.math.HashUtils; +import com.zfoo.util.math.RandomUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * 服务调度和负载均衡,两个关键点:摘除故障节点,负载均衡 + *

+ * 在clientSession中选择一个可用的session,最终还是调用的IPacketDispatcherManager中的方法 + * + * @author jaysunxiao + * @version 3.0 + */ +public class Consumer implements IConsumer { + + private static final Logger logger = LoggerFactory.getLogger(Consumer.class); + + + @Override + public void send(IPacket packet, Object argument) { + try { + var loadBalancer = NetContext.getConfigManager().consumerLoadBalancer(); + var session = loadBalancer.loadBalancer(packet, argument); + var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument); + NetContext.getDispatcher().send(session, packet, NoAnswerAttachment.valueOf(executorConsistentHash)); + } catch (Throwable t) { + logger.error("consumer发送未知异常", t); + } + } + + @Override + public SyncAnswer syncAsk(IPacket packet, Class answerClass, Object argument) throws Exception { + var loadBalancer = NetContext.getConfigManager().consumerLoadBalancer(); + var session = loadBalancer.loadBalancer(packet, argument); + + + // 下面的代码逻辑同PacketDispatcher的syncAsk,如果修改的话,记得一起修改 + var clientAttachment = new SignalPacketAttachment(); + var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument); + clientAttachment.setExecutorConsistentHash(executorConsistentHash); + + try { + session.addClientSignalAttachment(clientAttachment); + + // load balancer之前调用 + loadBalancer.beforeLoadBalancer(session, packet, clientAttachment); + + NetContext.getDispatcher().send(session, packet, clientAttachment); + + IPacket responsePacket = clientAttachment.getResponseFuture().get(PacketDispatcher.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + + if (responsePacket.protocolId() == Error.errorProtocolId()) { + throw new ErrorResponseException((Error) responsePacket); + } + if (answerClass != null && answerClass != responsePacket.getClass()) { + throw new UnexpectedProtocolException(StringUtils.format("client expect protocol:[{}], but found protocol:[{}]" + , answerClass, responsePacket.getClass().getName())); + } + var syncAnswer = new SyncAnswer<>((T) responsePacket, clientAttachment); + + // load balancer之后调用 + loadBalancer.afterLoadBalancer(session, packet, clientAttachment); + return syncAnswer; + } catch (TimeoutException e) { + throw new NetTimeOutException(StringUtils.format("syncRequest timeout exception, ask:[{}], attachment:[{}]" + , JsonUtils.object2String(packet), JsonUtils.object2String(clientAttachment))); + } finally { + session.removeClientSignalAttachment(clientAttachment); + } + } + + @Override + public AsyncAnswer asyncAsk(IPacket packet, Class answerClass, Object argument) { + var loadBalancer = NetContext.getConfigManager().consumerLoadBalancer(); + var session = loadBalancer.loadBalancer(packet, argument); + var asyncAnswer = NetContext.getDispatcher().asyncAsk(session, packet, answerClass, argument); + + // load balancer之前调用 + loadBalancer.beforeLoadBalancer(session, packet, asyncAnswer.getFutureAttachment()); + + // load balancer之后调用 + asyncAnswer.thenAccept(responsePacket -> loadBalancer.afterLoadBalancer(session, packet, asyncAnswer.getFutureAttachment())); + return asyncAnswer; + } + +} diff --git a/net/src/main/java/com/zfoo/net/consumer/service/IConsumer.java b/net/src/main/java/com/zfoo/net/consumer/service/IConsumer.java new file mode 100644 index 00000000..342e9cb2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/consumer/service/IConsumer.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.consumer.service; + +import com.zfoo.net.dispatcher.model.answer.AsyncAnswer; +import com.zfoo.net.dispatcher.model.answer.SyncAnswer; +import com.zfoo.protocol.IPacket; +import org.springframework.lang.Nullable; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IConsumer { + + /** + * 直接发送,不需要任何返回值 + * + * @param packet 需要发送的包 + * @param argument 计算负载均衡的参数,比如用户的id + */ + void send(IPacket packet, @Nullable Object argument); + + SyncAnswer syncAsk(IPacket packet, Class answerClass, @Nullable Object argument) throws Exception; + + AsyncAnswer asyncAsk(IPacket packet, Class answerClass, @Nullable Object argument); + +} diff --git a/net/src/main/java/com/zfoo/net/core/AbstractClient.java b/net/src/main/java/com/zfoo/net/core/AbstractClient.java new file mode 100644 index 00000000..8f392b88 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/AbstractClient.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core; + +import com.zfoo.net.NetContext; +import com.zfoo.net.handler.BaseDispatcherHandler; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.util.net.HostAndPort; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollSocketChannel; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AbstractClient implements IClient { + + private static final Logger logger = LoggerFactory.getLogger(AbstractClient.class); + + private static final EventLoopGroup nioEventLoopGroup = Epoll.isAvailable() + ? new EpollEventLoopGroup(Runtime.getRuntime().availableProcessors() + 1, new DefaultThreadFactory("netty-client", true)) + : new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() + 1, new DefaultThreadFactory("netty-client", true)); + + private String hostAddress; + private int port; + + private Bootstrap bootstrap; + + public AbstractClient(HostAndPort host) { + this.hostAddress = host.getHost(); + this.port = host.getPort(); + } + + public abstract ChannelInitializer channelChannelInitializer(); + + @Override + public synchronized Session start() { + return doStart(channelChannelInitializer()); + } + + private synchronized Session doStart(ChannelInitializer channelChannelInitializer) { + this.bootstrap = new Bootstrap(); + this.bootstrap.group(nioEventLoopGroup) + .channel(Epoll.isAvailable() ? EpollSocketChannel.class : NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .handler(channelChannelInitializer()); + var channelFuture = bootstrap.connect(hostAddress, port); + channelFuture.syncUninterruptibly(); + + if (channelFuture.isSuccess()) { + if (channelFuture.channel().isActive()) { + var channel = channelFuture.channel(); + var session = BaseDispatcherHandler.initChannel(channel); + NetContext.getSessionManager().addClientSession(session); + logger.info("TcpClient started at [{}]", channel.localAddress()); + return session; + } + } else if (channelFuture.cause() != null) { + logger.error(ExceptionUtils.getMessage(channelFuture.cause())); + } else { + logger.error("启动客户端[client:{}]未知错误", this); + } + return null; + } + + + public synchronized static void shutdown() { + AbstractServer.shutdownEventLoopGracefully(nioEventLoopGroup); + } + +} diff --git a/net/src/main/java/com/zfoo/net/core/AbstractServer.java b/net/src/main/java/com/zfoo/net/core/AbstractServer.java new file mode 100644 index 00000000..51ea788b --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/AbstractServer.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core; + +import com.zfoo.util.net.HostAndPort; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.concurrent.DefaultThreadFactory; +import io.netty.util.concurrent.EventExecutorGroup; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AbstractServer implements IServer { + private static final Logger logger = LoggerFactory.getLogger(AbstractServer.class); + + // 所有的服务器都可以在这个列表中取到 + private static final List allServers = new ArrayList<>(1); + + private String hostAddress; + private int port; + + + // 配置服务端nio线程组,服务端接受客户端连接 + private EventLoopGroup bossGroup; + + // SocketChannel的网络读写 + private EventLoopGroup workerGroup; + + private ChannelFuture channelFuture; + + private Channel channel; + + public AbstractServer(HostAndPort host) { + this.hostAddress = host.getHost(); + this.port = host.getPort(); + } + + public abstract ChannelInitializer channelChannelInitializer(); + + @Override + public void start() { + doStart(channelChannelInitializer()); + } + + protected synchronized void doStart(ChannelInitializer channelChannelInitializer) { + var cpuNum = Runtime.getRuntime().availableProcessors(); + bossGroup = Epoll.isAvailable() + ? new EpollEventLoopGroup(Math.max(1, cpuNum / 4), new DefaultThreadFactory("netty-boss", true)) + : new NioEventLoopGroup(Math.max(1, cpuNum / 4), new DefaultThreadFactory("netty-boss", true)); + + workerGroup = Epoll.isAvailable() + ? new EpollEventLoopGroup(cpuNum * 2, new DefaultThreadFactory("netty-worker", true)) + : new NioEventLoopGroup(cpuNum * 2, new DefaultThreadFactory("netty-worker", true)); + + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.TCP_NODELAY, true) + .childHandler(channelChannelInitializer); + // 绑定端口,同步等待成功 + // channelFuture = bootstrap.bind(hostAddress, port).sync(); + // 等待服务端监听端口关闭 + // channelFuture.channel().closeFuture().sync(); + + + // 异步 + channelFuture = bootstrap.bind(hostAddress, port); + channelFuture.syncUninterruptibly(); + channel = channelFuture.channel(); + + allServers.add(this); + + logger.info("TcpServer started at [{}:{}]", hostAddress, port); + } + + + @Override + public synchronized void shutdown() { + shutdownEventLoopGracefully(bossGroup); + + shutdownEventLoopGracefully(workerGroup); + + if (channelFuture != null) { + try { + channelFuture.channel().close().syncUninterruptibly(); + } catch (Exception e) { + logger.warn(e.getMessage(), e); + } + } + + if (channel != null) { + try { + channel.close(); + } catch (Exception e) { + logger.warn(e.getMessage(), e); + } + } + } + + public synchronized static void shutdownEventLoopGracefully(EventExecutorGroup executor) { + try { + if (executor.isShutdown() || executor.isTerminated()) { + executor.shutdownGracefully(); + } + } catch (Exception e) { + logger.error("EventLoop Thread pool [{}] is failed to shutdown! ", executor, e); + return; + } + logger.info("EventLoop Thread pool [{}] shuts down gracefully.", executor); + } + + public synchronized static void shutdownAllServers() { + allServers.forEach(it -> it.shutdown()); + } + +} diff --git a/net/src/main/java/com/zfoo/net/core/IClient.java b/net/src/main/java/com/zfoo/net/core/IClient.java new file mode 100644 index 00000000..4546223b --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/IClient.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core; + +import com.zfoo.net.session.model.Session; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IClient { + + Session start(); + +} diff --git a/net/src/main/java/com/zfoo/net/core/IServer.java b/net/src/main/java/com/zfoo/net/core/IServer.java new file mode 100644 index 00000000..2fe94810 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/IServer.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IServer { + + void start(); + + void shutdown(); + +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/GatewayServer.java b/net/src/main/java/com/zfoo/net/core/gateway/GatewayServer.java new file mode 100644 index 00000000..e48e08e3 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/GatewayServer.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway; + +import com.zfoo.net.core.AbstractServer; +import com.zfoo.net.handler.GatewayDispatcherHandler; +import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler; +import com.zfoo.net.handler.idle.ServerIdleHandler; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.util.net.HostAndPort; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; + +import java.util.function.BiFunction; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class GatewayServer extends AbstractServer { + private static final Logger logger = LoggerFactory.getLogger(GatewayServer.class); + + private BiFunction packetFilter; + + public GatewayServer(HostAndPort host, @Nullable BiFunction packetFilter) { + super(host); + this.packetFilter = packetFilter; + } + + @Override + public ChannelInitializer channelChannelInitializer() { + return new GatewayChannelHandler(packetFilter); + } + + + private static class GatewayChannelHandler extends ChannelInitializer { + + private BiFunction packetFilter; + + public GatewayChannelHandler(BiFunction packetFilter) { + this.packetFilter = packetFilter; + } + + @Override + protected void initChannel(SocketChannel channel) { + channel.pipeline().addLast(new IdleStateHandler(0, 0, 180)); + channel.pipeline().addLast(new ServerIdleHandler()); + channel.pipeline().addLast(new TcpPacketCodecHandler()); + channel.pipeline().addLast(new GatewayDispatcherHandler(packetFilter)); + } + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/IGatewayLoadBalancer.java b/net/src/main/java/com/zfoo/net/core/gateway/IGatewayLoadBalancer.java new file mode 100644 index 00000000..c832b3de --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/IGatewayLoadBalancer.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway; + +/** + * 网关负载均衡使用计算一致性hash的参数,如果packet继承了这个接口,则网关的一致性hash负载均衡优先使用这个接口计算一致性hash; + * + * @author jaysunxiao + * @version 3.0 + */ +public interface IGatewayLoadBalancer { + + Object loadBalancerConsistentHashObject(); + +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/WebsocketGatewayServer.java b/net/src/main/java/com/zfoo/net/core/gateway/WebsocketGatewayServer.java new file mode 100644 index 00000000..7434ddaf --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/WebsocketGatewayServer.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway; + +import com.zfoo.net.core.AbstractServer; +import com.zfoo.net.handler.GatewayDispatcherHandler; +import com.zfoo.net.handler.codec.websocket.WebSocketCodecHandler; +import com.zfoo.net.handler.idle.ServerIdleHandler; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.util.net.HostAndPort; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; + +import java.util.function.BiFunction; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class WebsocketGatewayServer extends AbstractServer { + private static final Logger logger = LoggerFactory.getLogger(WebsocketGatewayServer.class); + + private BiFunction packetFilter; + + public WebsocketGatewayServer(HostAndPort host, @Nullable BiFunction packetFilter) { + super(host); + this.packetFilter = packetFilter; + } + + @Override + public ChannelInitializer channelChannelInitializer() { + return new GatewayChannelHandler(packetFilter); + } + + + private static class GatewayChannelHandler extends ChannelInitializer { + + private BiFunction packetFilter; + + public GatewayChannelHandler(BiFunction packetFilter) { + this.packetFilter = packetFilter; + } + + @Override + protected void initChannel(SocketChannel channel) { + channel.pipeline().addLast(new IdleStateHandler(0, 0, 180)); + channel.pipeline().addLast(new ServerIdleHandler()); + + channel.pipeline().addLast(new HttpServerCodec()); + channel.pipeline().addLast(new ChunkedWriteHandler()); + channel.pipeline().addLast(new HttpObjectAggregator(64 * 1024)); + channel.pipeline().addLast(new WebSocketServerProtocolHandler("/websocket")); + channel.pipeline().addLast(new WebSocketCodecHandler()); + channel.pipeline().addLast(new GatewayDispatcherHandler(packetFilter)); + } + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/WebsocketSslGatewayServer.java b/net/src/main/java/com/zfoo/net/core/gateway/WebsocketSslGatewayServer.java new file mode 100644 index 00000000..fde244df --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/WebsocketSslGatewayServer.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway; + +import com.zfoo.net.core.AbstractServer; +import com.zfoo.net.handler.GatewayDispatcherHandler; +import com.zfoo.net.handler.codec.websocket.WebSocketCodecHandler; +import com.zfoo.net.handler.idle.ServerIdleHandler; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.util.net.HostAndPort; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.SSLException; +import java.io.InputStream; +import java.util.function.BiFunction; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class WebsocketSslGatewayServer extends AbstractServer { + + private static final Logger logger = LoggerFactory.getLogger(WebsocketSslGatewayServer.class); + + private SslContext sslContext; + + private BiFunction packetFilter; + + public WebsocketSslGatewayServer(HostAndPort host, InputStream pem, InputStream key, BiFunction packetFilter) { + super(host); + try { + this.sslContext = SslContextBuilder.forServer(pem, key).build(); + } catch (SSLException e) { + logger.error(ExceptionUtils.getMessage(e)); + } + this.packetFilter = packetFilter; + } + + @Override + public ChannelInitializer channelChannelInitializer() { + return new GatewayChannelHandler(sslContext, packetFilter); + } + + + private static class GatewayChannelHandler extends ChannelInitializer { + + private SslContext sslContext; + private BiFunction packetFilter; + + public GatewayChannelHandler(SslContext sslContext, BiFunction packetFilter) { + this.sslContext = sslContext; + this.packetFilter = packetFilter; + } + + @Override + protected void initChannel(SocketChannel channel) { + channel.pipeline().addLast(new IdleStateHandler(0, 0, 180)); + channel.pipeline().addLast(new ServerIdleHandler()); + + channel.pipeline().addLast(sslContext.newHandler(channel.alloc())); + channel.pipeline().addLast(new HttpServerCodec()); + channel.pipeline().addLast(new ChunkedWriteHandler()); + channel.pipeline().addLast(new HttpObjectAggregator(64 * 1024)); + channel.pipeline().addLast(new WebSocketServerProtocolHandler("/")); + channel.pipeline().addLast(new WebSocketCodecHandler()); + channel.pipeline().addLast(new GatewayDispatcherHandler(packetFilter)); + } + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidAsk.java b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidAsk.java new file mode 100644 index 00000000..63742a2f --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidAsk.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class AuthUidAsk implements IPacket { + + public static final transient short PROTOCOL_ID = 22; + + private String gatewayHostAndPort; + + private long sid; + private long uid; + + public static AuthUidAsk valueOf(String gatewayHostAndPort, long sid, long uid) { + var ask = new AuthUidAsk(); + ask.gatewayHostAndPort = gatewayHostAndPort; + ask.sid = sid; + ask.uid = uid; + return ask; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getGatewayHostAndPort() { + return gatewayHostAndPort; + } + + public void setGatewayHostAndPort(String gatewayHostAndPort) { + this.gatewayHostAndPort = gatewayHostAndPort; + } + + public long getSid() { + return sid; + } + + public void setSid(long sid) { + this.sid = sid; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayCheck.java b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayCheck.java new file mode 100644 index 00000000..9e9f0e2d --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayCheck.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.protocol.IPacket; + +/** + * 网关登录成功过后,将uid授权给网关 + * + * @author jaysunxiao + * @version 3.0 + */ +public class AuthUidToGatewayCheck implements IPacket { + + public static final transient short PROTOCOL_ID = 20; + + private long uid; + + public static AuthUidToGatewayCheck valueOf(long uid) { + var authUidToGateway = new AuthUidToGatewayCheck(); + authUidToGateway.uid = uid; + return authUidToGateway; + } + + public static long getAuthProtocolId() { + return PROTOCOL_ID; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayConfirm.java b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayConfirm.java new file mode 100644 index 00000000..270aa57a --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayConfirm.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.protocol.IPacket; + +/** + * 网关登录成功过后,将uid授权给网关的返回 + * + * @author jaysunxiao + * @version 3.0 + */ +public class AuthUidToGatewayConfirm implements IPacket { + + public static final transient short PROTOCOL_ID = 21; + + private long uid; + + public static AuthUidToGatewayConfirm valueOf(long uid) { + var authUidToGateway = new AuthUidToGatewayConfirm(); + authUidToGateway.uid = uid; + return authUidToGateway; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayEvent.java b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayEvent.java new file mode 100644 index 00000000..6f53f3a5 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/AuthUidToGatewayEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.event.model.event.IEvent; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class AuthUidToGatewayEvent implements IEvent { + + private long sid; + private long uid; + + public static AuthUidToGatewayEvent valueOf(long sid, long uid) { + var event = new AuthUidToGatewayEvent(); + event.sid = sid; + event.uid = uid; + return event; + } + + public long getSid() { + return sid; + } + + public void setSid(long sid) { + this.sid = sid; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveAsk.java b/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveAsk.java new file mode 100644 index 00000000..c72ab212 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveAsk.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class GatewaySessionInactiveAsk implements IPacket { + + public static final transient short PROTOCOL_ID = 23; + + private String gatewayHostAndPort; + + private long sid; + private long uid; + + public static GatewaySessionInactiveAsk valueOf(String gatewayHostAndPort, long sid, long uid) { + var ask = new GatewaySessionInactiveAsk(); + ask.gatewayHostAndPort = gatewayHostAndPort; + ask.sid = sid; + ask.uid = uid; + return ask; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getGatewayHostAndPort() { + return gatewayHostAndPort; + } + + public void setGatewayHostAndPort(String gatewayHostAndPort) { + this.gatewayHostAndPort = gatewayHostAndPort; + } + + public long getSid() { + return sid; + } + + public void setSid(long sid) { + this.sid = sid; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveEvent.java b/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveEvent.java new file mode 100644 index 00000000..5dacb905 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySessionInactiveEvent.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.event.model.event.IEvent; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class GatewaySessionInactiveEvent implements IEvent { + + private long sid; + private long uid; + + public static GatewaySessionInactiveEvent valueOf(long sid, long uid) { + var event = new GatewaySessionInactiveEvent(); + event.sid = sid; + event.uid = uid; + return event; + } + + public long getSid() { + return sid; + } + + public void setSid(long sid) { + this.sid = sid; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySynchronizeSidAsk.java b/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySynchronizeSidAsk.java new file mode 100644 index 00000000..21c788d7 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/gateway/model/GatewaySynchronizeSidAsk.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.model; + +import com.zfoo.protocol.IPacket; + +import java.util.Map; + +/** + * 同步网关的session信息到push + * + * @author jaysunxiao + * @version 3.0 + */ +public class GatewaySynchronizeSidAsk implements IPacket { + + public static final transient short PROTOCOL_ID = 24; + + private String gatewayHostAndPort; + + private Map sidMap; + + public static GatewaySynchronizeSidAsk valueOf(String gatewayHostAndPort, Map sidMap) { + var ask = new GatewaySynchronizeSidAsk(); + ask.gatewayHostAndPort = gatewayHostAndPort; + ask.sidMap = sidMap; + return ask; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public static short gatewaySynchronizeProtocolId() { + return PROTOCOL_ID; + } + + public Map getSidMap() { + return sidMap; + } + + public void setSidMap(Map sidMap) { + this.sidMap = sidMap; + } + + public String getGatewayHostAndPort() { + return gatewayHostAndPort; + } + + public void setGatewayHostAndPort(String gatewayHostAndPort) { + this.gatewayHostAndPort = gatewayHostAndPort; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/tcp/TcpClient.java b/net/src/main/java/com/zfoo/net/core/tcp/TcpClient.java new file mode 100644 index 00000000..327675db --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/tcp/TcpClient.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp; + +import com.zfoo.net.core.AbstractClient; +import com.zfoo.net.handler.ClientDispatcherHandler; +import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler; +import com.zfoo.net.handler.idle.ClientIdleHandler; +import com.zfoo.util.net.HostAndPort; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TcpClient extends AbstractClient { + + private static final Logger logger = LoggerFactory.getLogger(TcpClient.class); + + public TcpClient(HostAndPort host) { + super(host); + } + + @Override + public ChannelInitializer channelChannelInitializer() { + return new TcpChannelInitHandler(); + } + + + private static class TcpChannelInitHandler extends ChannelInitializer { + @Override + protected void initChannel(SocketChannel channel) { + channel.pipeline().addLast(new IdleStateHandler(0, 0, 60)); + channel.pipeline().addLast(new ClientIdleHandler()); + channel.pipeline().addLast(new TcpPacketCodecHandler()); + channel.pipeline().addLast(new ClientDispatcherHandler()); + } + } + + +} diff --git a/net/src/main/java/com/zfoo/net/core/tcp/TcpServer.java b/net/src/main/java/com/zfoo/net/core/tcp/TcpServer.java new file mode 100644 index 00000000..934f3969 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/tcp/TcpServer.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp; + +import com.zfoo.net.core.AbstractServer; +import com.zfoo.net.handler.ServerDispatcherHandler; +import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler; +import com.zfoo.net.handler.idle.ServerIdleHandler; +import com.zfoo.util.net.HostAndPort; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.timeout.IdleStateHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TcpServer extends AbstractServer { + + private static final Logger logger = LoggerFactory.getLogger(TcpServer.class); + + public TcpServer(HostAndPort host) { + super(host); + } + + @Override + public ChannelInitializer channelChannelInitializer() { + return new TcpChannelHandler(); + } + + + private static class TcpChannelHandler extends ChannelInitializer { + @Override + protected void initChannel(SocketChannel channel) { + channel.pipeline().addLast(new IdleStateHandler(0, 0, 180)); + channel.pipeline().addLast(new ServerIdleHandler()); + channel.pipeline().addLast(new TcpPacketCodecHandler()); + channel.pipeline().addLast(new ServerDispatcherHandler()); + } + } +} diff --git a/net/src/main/java/com/zfoo/net/core/tcp/model/ServerSessionInactiveEvent.java b/net/src/main/java/com/zfoo/net/core/tcp/model/ServerSessionInactiveEvent.java new file mode 100644 index 00000000..090c2ac3 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/tcp/model/ServerSessionInactiveEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp.model; + +import com.zfoo.event.model.event.IEvent; +import com.zfoo.net.session.model.Session; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ServerSessionInactiveEvent implements IEvent { + + private Session session; + + public static ServerSessionInactiveEvent valueOf(Session session) { + var event = new ServerSessionInactiveEvent(); + event.session = session; + return event; + } + + public Session getSession() { + return session; + } + + public void setSession(Session session) { + this.session = session; + } +} diff --git a/net/src/main/java/com/zfoo/net/core/websocket/WebsocketServer.java b/net/src/main/java/com/zfoo/net/core/websocket/WebsocketServer.java new file mode 100644 index 00000000..4db71e33 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/core/websocket/WebsocketServer.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.websocket; + +import com.zfoo.net.core.AbstractServer; +import com.zfoo.net.handler.ServerDispatcherHandler; +import com.zfoo.net.handler.codec.websocket.WebSocketCodecHandler; +import com.zfoo.util.net.HostAndPort; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class WebsocketServer extends AbstractServer { + + private static final Logger logger = LoggerFactory.getLogger(WebsocketServer.class); + + public WebsocketServer(HostAndPort host) { + super(host); + } + + @Override + public ChannelInitializer channelChannelInitializer() { + return new WebSocketServerInitializer(); + } + + + public class WebSocketServerInitializer extends ChannelInitializer { + + @Override + public void initChannel(SocketChannel channel) { + ChannelPipeline pipeline = channel.pipeline(); + // 编解码 http 请求 + pipeline.addLast(new HttpServerCodec()); + // 写文件内容,支持异步发送大的码流,一般用于发送文件流 + pipeline.addLast(new ChunkedWriteHandler()); + // 聚合解码 HttpRequest/HttpContent/LastHttpContent 到 FullHttpRequest + // 保证接收的 Http 请求的完整性 + pipeline.addLast(new HttpObjectAggregator(64 * 1024)); + // 处理其他的 WebSocketFrame + pipeline.addLast(new WebSocketServerProtocolHandler("/websocket")); + // 编解码WebSocketFrame二进制协议 + pipeline.addLast(new WebSocketCodecHandler()); + pipeline.addLast(new ServerDispatcherHandler()); + } + + } + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/manager/IPacketDispatcher.java b/net/src/main/java/com/zfoo/net/dispatcher/manager/IPacketDispatcher.java new file mode 100644 index 00000000..47f26bb0 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/manager/IPacketDispatcher.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.manager; + +import com.zfoo.net.dispatcher.model.answer.AsyncAnswer; +import com.zfoo.net.dispatcher.model.answer.SyncAnswer; +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import org.springframework.lang.Nullable; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IPacketDispatcher { + + void send(Session session, IPacket packet); + + /** + * send()和receive()是消息的发送和接收的入口,可以直接调用,是最轻量级发送和接收方式 + */ + void send(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment); + + void receive(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment); + + void doReceive(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment); + + /** + * attention:syncRequest和asyncRequest只能客户端调用 + * 同一个客户端可以同时发送多条同步或者异步消息。 + * 服务器对每个请求消息也只能回复一条消息,不能在处理一条不同或者异步消息的时候回复多条消息。 + * + * @param session 一个网络通信的会话 + * @param packet 一个网络通信包,消息体 + * @param answerClass 等待返回包的class类。 + * 如果为null,则不会检查这个class类的协议号是否和返回消息体的协议号相等; + * 如果不为null,会检查返回包的协议号。为null的情况主要用在网关。 + * @param 请求消息需要服务器返回的类型 + * @param argument 参数,主要用来计算一致性hashId。 + * 1.IConsumer会使用这个参数计算负载到哪个服务提供者; + * 2.服务提供者收到请求过后会使用这个参数来计算再哪个线程执行任务; + * 3.如果是异步请求,消费者收到消息过后会通过这个参数计算再哪个线程执行回调。 + * 综上所述,这个参数会在上面三种情况使用。 + * @return 服务器返回的消息Response + * @throws Exception 如果超时或者其它异常 + */ + SyncAnswer syncAsk(Session session, IPacket packet, @Nullable Class answerClass, @Nullable Object argument) throws Exception; + + AsyncAnswer asyncAsk(Session session, IPacket packet, @Nullable Class answerClass, @Nullable Object argument); + + void registerPacketReceiverDefinition(Object bean); + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/manager/PacketDispatcher.java b/net/src/main/java/com/zfoo/net/dispatcher/manager/PacketDispatcher.java new file mode 100644 index 00000000..2aa7d196 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/manager/PacketDispatcher.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.manager; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.event.model.event.IEvent; +import com.zfoo.net.NetContext; +import com.zfoo.net.core.gateway.model.AuthUidToGatewayCheck; +import com.zfoo.net.core.gateway.model.AuthUidToGatewayConfirm; +import com.zfoo.net.core.gateway.model.AuthUidToGatewayEvent; +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.dispatcher.model.answer.AsyncAnswer; +import com.zfoo.net.dispatcher.model.answer.SyncAnswer; +import com.zfoo.net.dispatcher.model.exception.ErrorResponseException; +import com.zfoo.net.dispatcher.model.exception.NetTimeOutException; +import com.zfoo.net.dispatcher.model.exception.UnexpectedProtocolException; +import com.zfoo.net.dispatcher.model.vo.EnhanceUtils; +import com.zfoo.net.dispatcher.model.vo.IPacketReceiver; +import com.zfoo.net.dispatcher.model.vo.PacketReceiverDefinition; +import com.zfoo.net.packet.common.Error; +import com.zfoo.net.packet.common.Heartbeat; +import com.zfoo.net.packet.model.EncodedPacketInfo; +import com.zfoo.net.packet.model.GatewayPacketAttachment; +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.net.packet.service.PacketService; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.net.task.TaskManager; +import com.zfoo.net.task.model.ReceiveTask; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.math.HashUtils; +import com.zfoo.util.math.RandomUtils; +import io.netty.util.concurrent.FastThreadLocal; +import javassist.CannotCompileException; +import javassist.NotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.lang.Nullable; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * 消息派发 + * + * @author jaysunxiao + * @version 3.0 + */ +public class PacketDispatcher implements IPacketDispatcher { + + private static final Logger logger = LoggerFactory.getLogger(PacketDispatcher.class); + + public static final long DEFAULT_TIMEOUT = 3000; + + /** + * 客户端和服务端都有接受packet的方法,packetReceiverList对应的就是包的接收方法 + */ + private final IPacketReceiver[] packetReceiverList = new IPacketReceiver[ProtocolManager.MAX_PROTOCOL_NUM]; + + /** + * 会把receive收到的attachment存储在这个地方,只针对task线程。 + * doWithReceivePacket会设置receivePacketAttachment,但是在方法调用完成会取消,不需要过多关注。 + * asyncRequest会再次设置receivePacketAttachment,需要重点关注。 + */ + private final FastThreadLocal serverReceiveSignalPacketAttachment = new FastThreadLocal<>(); + + + @Override + public void receive(Session session, IPacket packet, @Nullable IPacketAttachment packetAttachment) { + if (packet.protocolId() == Heartbeat.heartbeatProtocolId()) { + logger.info("heartbeat"); + return; + } + + // 发送者(客户端)同步和异步消息的接收,发送者通过packetId判断重复 + if (packetAttachment != null) { + switch (packetAttachment.packetType()) { + case SIGNAL_PACKET: + var signalPacketAttachment = (SignalPacketAttachment) packetAttachment; + + if (signalPacketAttachment.isClient()) { + // 服务器收到signalPacketAttachment,不做任何处理 + signalPacketAttachment.setClient(false); + + } else { + // 客户端收到服务器应答,客户端发送的时候isClient为true,服务器收到的时候将其设置为false + var attachment = (SignalPacketAttachment) session.removeClientSignalAttachment(signalPacketAttachment); + if (attachment != null) { + attachment.getResponseFuture().complete(packet); + } else { + logger.error("client receives packet:[{}] and packetAttachment:[{}] from server, but clientPacketAttachmentMap has no attachment, perhaps timeout exception." + , JsonUtils.object2String(packet), JsonUtils.object2String(packetAttachment)); + } + return; + } + + break; + case GATEWAY_PACKET: + var gatewayPacketAttachment = (GatewayPacketAttachment) packetAttachment; + if (gatewayPacketAttachment.isClient()) { + gatewayPacketAttachment.setClient(false); + } else { + var gatewaySession = NetContext.getSessionManager().getServerSession(gatewayPacketAttachment.getSid()); + if (gatewaySession != null) { + var signalAttachment = gatewayPacketAttachment.getSignalPacketAttachment(); + if (signalAttachment != null) { + signalAttachment.setClient(false); + } + // 网关授权,授权完成直接返回 + if (AuthUidToGatewayCheck.getAuthProtocolId() == packet.protocolId()) { + var uid = ((AuthUidToGatewayCheck) packet).getUid(); + if (uid <= 0) { + logger.error("错误的网关授权信息,uid必须大于0"); + return; + } + gatewaySession.putAttribute(AttributeType.UID, uid); + EventBus.asyncSubmit(AuthUidToGatewayEvent.valueOf(gatewaySession.getSid(), uid)); + + NetContext.getDispatcher().send(session, AuthUidToGatewayConfirm.valueOf(uid), new GatewayPacketAttachment(gatewaySession, null)); + return; + } + send(gatewaySession, packet, signalAttachment); + } else { + logger.error("gateway receives packet:[{}] and packetAttachment:[{}] from server" + + ", but serverSessionMap has no session[id:{}], perhaps client disconnected from gateway." + , JsonUtils.object2String(packet), JsonUtils.object2String(packetAttachment), gatewayPacketAttachment.getSid()); + } + return; + } + break; + case NORMAL_PACKET: + + break; + default: + break; + } + } + + // 正常发送消息的接收 + TaskManager.getInstance().addTask(new ReceiveTask(session, packet, packetAttachment)); + } + + @Override + public void send(Session session, IPacket packet, IPacketAttachment packetAttachment) { + if (session == null) { + logger.error("session is null and can not be sent."); + return; + } + if (packet == null) { + logger.error("packet is null and can not be sent."); + return; + } + + var packetInfo = EncodedPacketInfo.valueOf(packet, packetAttachment); + + var channel = session.getChannel(); + channel.writeAndFlush(packetInfo); + } + + @Override + public void send(Session session, IPacket packet) { + // 服务器异步返回的消息的发送会有signalPacketAttachment,验证返回的消息是否满足 + var serverSignalPacketAttachment = serverReceiveSignalPacketAttachment.get(); + + if (serverSignalPacketAttachment != null) { + if (serverSignalPacketAttachment.isClient()) { + // 客户端发送的时候不应该有serverSignalPacketAttachment + logger.error("client can not have serverSignalPacketAttachment:[{}] and packet:[{}]", serverSignalPacketAttachment, packet); + } else if (Error.errorProtocolId() == packet.protocolId()) { + // 错误信息直接返回 + } + } + + + send(session, packet, serverSignalPacketAttachment); + } + + + @Override + public SyncAnswer syncAsk(Session session, IPacket packet, @Nullable Class answerClass, @Nullable Object argument) throws Exception { + var clientAttachment = new SignalPacketAttachment(); + var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument); + clientAttachment.setExecutorConsistentHash(executorConsistentHash); + + try { + session.addClientSignalAttachment(clientAttachment); + send(session, packet, clientAttachment); + + IPacket responsePacket = clientAttachment.getResponseFuture().get(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + + if (responsePacket.protocolId() == Error.errorProtocolId()) { + throw new ErrorResponseException((Error) responsePacket); + } + if (answerClass != null && answerClass != responsePacket.getClass()) { + throw new UnexpectedProtocolException(StringUtils.format("client expect protocol:[{}], but found protocol:[{}]" + , answerClass, responsePacket.getClass().getName())); + } + + return new SyncAnswer<>((T) responsePacket, clientAttachment); + } catch (TimeoutException e) { + throw new NetTimeOutException(StringUtils.format("syncRequest timeout exception, ask:[{}], attachment:[{}]" + , JsonUtils.object2String(packet), JsonUtils.object2String(clientAttachment))); + } finally { + session.removeClientSignalAttachment(clientAttachment); + } + + } + + @Override + public AsyncAnswer asyncAsk(Session session, IPacket packet, @Nullable Class answerClass, @Nullable Object argument) { + var clientAttachment = new SignalPacketAttachment(); + var executorConsistentHash = (argument == null) ? RandomUtils.randomInt() : HashUtils.fnvHash(argument); + clientAttachment.setExecutorConsistentHash(executorConsistentHash); + + // 服务器在同步或异步的消息处理中,又调用了同步或异步的方法,这时候threadReceiverAttachment不为空 + var serverSignalPacketAttachment = serverReceiveSignalPacketAttachment.get(); + + try { + var asyncAnswer = new AsyncAnswer(); + asyncAnswer.setFutureAttachment(clientAttachment); + + clientAttachment.getResponseFuture() + .completeOnTimeout(null, DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS) + .thenApply(response -> { + if (response == null) { + throw new NetTimeOutException(StringUtils.format("asyncRequest timeout exception, ask:[{}], attachment:[{}]" + , JsonUtils.object2String(packet), JsonUtils.object2String(clientAttachment))); + } + + if (response.protocolId() == Error.errorProtocolId()) { + throw new ErrorResponseException((Error) response); + } + + if (answerClass != null && answerClass != response.getClass()) { + throw new UnexpectedProtocolException(StringUtils.format("client expect protocol:[{}], but found protocol:[{}]" + , answerClass, response.getClass().getName())); + } + return response; + }) + .whenCompleteAsync((responsePacket, e) -> { + try { + session.removeClientSignalAttachment(clientAttachment); + + // 如果有异常的话,whenCompleteAsync的下一个thenAccept不会执行 + if (e != null) { + logger.error(ExceptionUtils.getMessage(e)); + return; + } + + // 接收者在同步或异步的消息处理中,又调用了异步的方法,这时候threadServerAttachment不为空 + if (serverSignalPacketAttachment != null) { + serverReceiveSignalPacketAttachment.set(serverSignalPacketAttachment); + } + + // 异步返回,回调业务逻辑 + asyncAnswer.setFuturePacket((T) responsePacket); + asyncAnswer.consume(); + } catch (Exception exception) { + logger.error("consume response error requestPacket:[{}] and responsePacket:[{}]", + JsonUtils.object2String(packet), JsonUtils.object2String(responsePacket), exception); + } finally { + if (serverSignalPacketAttachment != null) { + serverReceiveSignalPacketAttachment.set(null); + } + } + + }, TaskManager.getInstance().getExecutorByConsistentHash(executorConsistentHash)); + + + session.addClientSignalAttachment(clientAttachment); + + // 等到上层调用whenComplete才会发送消息 + asyncAnswer.setAskCallback(() -> send(session, packet, clientAttachment)); + return asyncAnswer; + } catch (Exception e) { + session.removeClientSignalAttachment(clientAttachment); + throw e; + } + } + + + /** + * 正常消息的接收 + *

+ * 发送者同时能发送多个包 + * 接收者同时只能处理一个session的一个包,同一个发送者发送过来的包排队处理 + */ + @Override + public void doReceive(Session session, IPacket packet, IPacketAttachment packetAttachment) { + + try { + var packetReceiver = packetReceiverList[packet.protocolId()]; + if (packetReceiver == null) { + throw new RuntimeException(StringUtils.format("no any packetReceiverDefinition found for this [packet:{}]", packet.getClass().getName())); + } + + // 接收者(服务器)同步和异步消息的接收 + if (packetAttachment != null) { + switch (packetAttachment.packetType()) { + case SIGNAL_PACKET: + serverReceiveSignalPacketAttachment.set((SignalPacketAttachment) packetAttachment); + break; + default: + break; + } + } + + // 调用PacketReceiver + packetReceiver.invoke(session, packet, packetAttachment); + + } catch (Exception e) { + logger.error(StringUtils.format("e[{}][{}]未知exception异常[e:{}]", session.getAttribute(AttributeType.UID), session.getSid(), e.getMessage()), e); + } catch (Throwable t) { + logger.error(StringUtils.format("e[{}][{}]未知error错误[t:{}]", session.getAttribute(AttributeType.UID), session.getSid(), t.getMessage()), t); + } finally { + // 如果有服务器在处理同步或者异步消息的时候由于错误没有返回给客户端消息,则可能会残留serverAttachment,所以先移除 + if (packetAttachment != null) { + switch (packetAttachment.packetType()) { + case SIGNAL_PACKET: + serverReceiveSignalPacketAttachment.set(null); + break; + default: + break; + } + } + } + } + + + @Override + public void registerPacketReceiverDefinition(Object bean) { + var clazz = bean.getClass(); + + if (!ReflectionUtils.isPOJOClass(clazz)) { + return; + } + + var methods = ReflectionUtils.getMethodsByAnnoInPOJOClass(clazz, PacketReceiver.class); + for (var method : methods) { + var paramClazzs = method.getParameterTypes(); + + AssertionUtils.isTrue(paramClazzs.length == 2 || paramClazzs.length == 3 + , "[class:{}] [method:{}] must have two or three parameter!", bean.getClass().getName(), method.getName()); + + AssertionUtils.isTrue(Session.class.isAssignableFrom(paramClazzs[0]) + , "[class:{}] [method:{}],the first parameter must be Session type parameter Exception.", bean.getClass().getName(), method.getName()); + + AssertionUtils.isTrue(IPacket.class.isAssignableFrom(paramClazzs[1]) + , "[class:{}] [method:{}],the second parameter must be IPacket type parameter Exception.", bean.getClass().getName(), method.getName()); + + AssertionUtils.isTrue(paramClazzs.length != 3 || IPacketAttachment.class.isAssignableFrom(paramClazzs[2]) + , "[class:{}] [method:{}],the third parameter must be IPacketAttachment type parameter Exception.", bean.getClass().getName(), method.getName()); + + var packetClazz = (Class) paramClazzs[1]; + var attachmentClazz = paramClazzs.length == 3 ? paramClazzs[2] : null; + var packetName = packetClazz.getCanonicalName(); + var methodName = method.getName(); + + AssertionUtils.isTrue(Modifier.isPublic(method.getModifiers()) + , "[class:{}] [method:{}] [packet:{}] must use 'public' as modifier!", bean.getClass().getName(), methodName, packetName); + + AssertionUtils.isTrue(!Modifier.isStatic(method.getModifiers()) + , "[class:{}] [method:{}] [packet:{}] can not use 'static' as modifier!", bean.getClass().getName(), methodName, packetName); + + var expectedMethodName = StringUtils.format("at{}", packetClazz.getSimpleName()); + AssertionUtils.isTrue(methodName.equals(expectedMethodName) + , "[class:{}] [method:{}] [packet:{}] expects '{}' as method name!", bean.getClass().getName(), methodName, packetName, expectedMethodName); + + // 如果以Request结尾的请求,那么attachment应该为GatewayAttachment + // 如果以Ask结尾的请求,那么attachment不能为GatewayAttachment + if (attachmentClazz != null) { + if (packetName.endsWith(PacketService.NET_REQUEST_SUFFIX)) { + AssertionUtils.isTrue(attachmentClazz.equals(GatewayPacketAttachment.class) + , "[class:{}] [method:{}] [packet:{}] must use [attachment:{}]!", bean.getClass().getName(), methodName, packetName, GatewayPacketAttachment.class.getCanonicalName()); + } else if (packetName.endsWith(PacketService.NET_ASK_SUFFIX)) { + AssertionUtils.isTrue(!attachmentClazz.equals(GatewayPacketAttachment.class) + , "[class:{}] [method:{}] [packet:{}] can not match with [attachment:{}]!", bean.getClass().getName(), methodName, packetName, GatewayPacketAttachment.class.getCanonicalName()); + } + } + + try { + var protocolIdField = packetClazz.getDeclaredField(ProtocolManager.PROTOCOL_ID); + ReflectionUtils.makeAccessible(protocolIdField); + var protocolId = (short) protocolIdField.get(null); + var receiverDefinition = new PacketReceiverDefinition(bean, method, packetClazz, attachmentClazz); + var enhanceReceiverDefinition = EnhanceUtils.createPacketReceiver(receiverDefinition); + packetReceiverList[protocolId] = enhanceReceiverDefinition; + } catch (NoSuchFieldException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException | CannotCompileException | NotFoundException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/anno/PacketReceiver.java b/net/src/main/java/com/zfoo/net/dispatcher/model/anno/PacketReceiver.java new file mode 100644 index 00000000..241756aa --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/anno/PacketReceiver.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface PacketReceiver { +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/answer/AsyncAnswer.java b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/AsyncAnswer.java new file mode 100644 index 00000000..7a40dc08 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/AsyncAnswer.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.answer; + +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.protocol.IPacket; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class AsyncAnswer implements IAsyncAnswer { + + private T futurePacket; + private SignalPacketAttachment futureAttachment; + + private List> consumerList = new ArrayList<>(2); + + private Runnable askCallback; + + @Override + public IAsyncAnswer thenAccept(Consumer consumer) { + consumerList.add(consumer); + return this; + } + + @Override + public void whenComplete(Consumer consumer) { + thenAccept(consumer); + askCallback.run(); + } + + public void consume() { + consumerList.forEach(it -> it.accept(futurePacket)); + } + + public T getFuturePacket() { + return futurePacket; + } + + public void setFuturePacket(T futurePacket) { + this.futurePacket = futurePacket; + } + + public SignalPacketAttachment getFutureAttachment() { + return futureAttachment; + } + + public void setFutureAttachment(SignalPacketAttachment futureAttachment) { + this.futureAttachment = futureAttachment; + } + + public Runnable getAskCallback() { + return askCallback; + } + + public void setAskCallback(Runnable askCallback) { + this.askCallback = askCallback; + } +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/answer/IAsyncAnswer.java b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/IAsyncAnswer.java new file mode 100644 index 00000000..0266987d --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/IAsyncAnswer.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.answer; + +import com.zfoo.protocol.IPacket; + +import java.util.function.Consumer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IAsyncAnswer { + + IAsyncAnswer thenAccept(Consumer consumer); + + /** + * 接收到异步返回的消息,并处理这个消息,异步请求必须要调用这个方法 + */ + void whenComplete(Consumer consumer); + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/answer/ISyncAnswer.java b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/ISyncAnswer.java new file mode 100644 index 00000000..03a5f674 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/ISyncAnswer.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.answer; + +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ISyncAnswer { + + /** + * @return 请求的返回包 + */ + T packet(); + + /** + * @return 同步和异步控制的附加包 + */ + SignalPacketAttachment attachment(); + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/answer/SyncAnswer.java b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/SyncAnswer.java new file mode 100644 index 00000000..c3fb71ec --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/answer/SyncAnswer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.answer; + +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SyncAnswer implements ISyncAnswer { + + + private T packet; + private SignalPacketAttachment attachment; + + public SyncAnswer(T packet, SignalPacketAttachment attachment) { + this.packet = packet; + this.attachment = attachment; + } + + @Override + public T packet() { + return packet; + } + + @Override + public SignalPacketAttachment attachment() { + return attachment; + } + + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/exception/ErrorResponseException.java b/net/src/main/java/com/zfoo/net/dispatcher/model/exception/ErrorResponseException.java new file mode 100644 index 00000000..7bb165a3 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/exception/ErrorResponseException.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.exception; + +import com.zfoo.net.packet.common.Error; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ErrorResponseException extends RuntimeException { + + public ErrorResponseException(Error error) { + super(error.toString()); + } + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/exception/NetTimeOutException.java b/net/src/main/java/com/zfoo/net/dispatcher/model/exception/NetTimeOutException.java new file mode 100644 index 00000000..30354c86 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/exception/NetTimeOutException.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.exception; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NetTimeOutException extends RuntimeException { + + public NetTimeOutException(String s) { + super(s); + } + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/exception/UnexpectedProtocolException.java b/net/src/main/java/com/zfoo/net/dispatcher/model/exception/UnexpectedProtocolException.java new file mode 100644 index 00000000..726e4b4d --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/exception/UnexpectedProtocolException.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.exception; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class UnexpectedProtocolException extends RuntimeException { + + public UnexpectedProtocolException(String s) { + super(s); + } + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/vo/EnhanceUtils.java b/net/src/main/java/com/zfoo/net/dispatcher/model/vo/EnhanceUtils.java new file mode 100644 index 00000000..14024ac2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/vo/EnhanceUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.vo; + +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.security.IdUtils; +import javassist.*; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class EnhanceUtils { + + static { + var classArray = new Class[]{ + IPacket.class, + IPacketAttachment.class, + IPacketReceiver.class, + Session.class + }; + + var classPool = ClassPool.getDefault(); + + for (var clazz : classArray) { + if (classPool.find(clazz.getCanonicalName()) == null) { + ClassClassPath classPath = new ClassClassPath(clazz); + classPool.insertClassPath(classPath); + } + } + } + + public static IPacketReceiver createPacketReceiver(PacketReceiverDefinition definition) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + var classPool = ClassPool.getDefault(); + + Object bean = definition.getBean(); + Method method = definition.getMethod(); + Class packetClazz = definition.getPacketClazz(); + Class attachmentClazz = definition.getAttachmentClazz(); + + // 定义类名称 + CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getCanonicalName() + "Dispatcher" + IdUtils.getLocalIntId()); + enhanceClazz.addInterface(classPool.get(IPacketReceiver.class.getCanonicalName())); + + // 定义类中的一个成员 + CtField field = new CtField(classPool.get(bean.getClass().getCanonicalName()), "bean", enhanceClazz); + field.setModifiers(Modifier.PRIVATE); + enhanceClazz.addField(field); + + // 定义类的构造器 + CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getCanonicalName()}), enhanceClazz); + constructor.setBody("{this.bean=$1;}"); + constructor.setModifiers(Modifier.PUBLIC); + enhanceClazz.addConstructor(constructor); + + // 定义类实现的接口方法 + CtMethod invokeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "invoke", classPool.get(new String[]{Session.class.getCanonicalName(), IPacket.class.getCanonicalName(), IPacketAttachment.class.getCanonicalName()}), enhanceClazz); + invokeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + if (attachmentClazz == null) { + // 强制类型转换 + String invokeMethodBody = StringUtils.format("{this.bean.{}($1, ({})$2);}", method.getName(), packetClazz.getCanonicalName()); + invokeMethod.setBody(invokeMethodBody); + } else { + String invokeMethodBody = StringUtils.format("{this.bean.{}($1, ({})$2, ({})$3);}", method.getName(), packetClazz.getCanonicalName(), attachmentClazz.getCanonicalName()); + invokeMethod.setBody(invokeMethodBody); + } + enhanceClazz.addMethod(invokeMethod); + + // 释放缓存 + enhanceClazz.detach(); + + Class resultClazz = enhanceClazz.toClass(IPacketReceiver.class); + Constructor resultConstructor = resultClazz.getConstructor(bean.getClass()); + IPacketReceiver receiver = (IPacketReceiver) resultConstructor.newInstance(bean); + return receiver; + } +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/vo/IPacketReceiver.java b/net/src/main/java/com/zfoo/net/dispatcher/model/vo/IPacketReceiver.java new file mode 100644 index 00000000..562a7dbe --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/vo/IPacketReceiver.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.vo; + +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IPacketReceiver { + + void invoke(Session session, IPacket packet, IPacketAttachment attachment); + +} diff --git a/net/src/main/java/com/zfoo/net/dispatcher/model/vo/PacketReceiverDefinition.java b/net/src/main/java/com/zfoo/net/dispatcher/model/vo/PacketReceiverDefinition.java new file mode 100644 index 00000000..77199f55 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/dispatcher/model/vo/PacketReceiverDefinition.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.dispatcher.model.vo; + +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.util.ReflectionUtils; + +import java.lang.reflect.Method; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PacketReceiverDefinition implements IPacketReceiver { + + /** + * 一个facade的bean,这个bean里有void methodName(Session session,CM_Int cm)接受的方法 + */ + private Object bean; + + /** + * 接受的方法void methodName(Session session,CM_Int cm) + */ + private Method method; + + /** + * 接收的包的Class类,如CM_Int + */ + private Class packetClazz; + + /** + * 接收的包的附加包的Class类,如GatewayPacketAttachment + */ + private Class attachmentClazz; + + public PacketReceiverDefinition(Object bean, Method method, Class packetClazz, Class attachmentClazz) { + this.bean = bean; + this.method = method; + this.packetClazz = packetClazz; + this.attachmentClazz = attachmentClazz; + ReflectionUtils.makeAccessible(method); + } + + @Override + public void invoke(Session session, IPacket packet, IPacketAttachment attachment) { + if (attachmentClazz == null) { + ReflectionUtils.invokeMethod(bean, method, session, packet); + } else { + ReflectionUtils.invokeMethod(bean, method, session, packet, attachment); + } + } + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + + public Method getMethod() { + return method; + } + + public void setMethod(Method method) { + this.method = method; + } + + public Class getPacketClazz() { + return packetClazz; + } + + public void setPacketClazz(Class packetClazz) { + this.packetClazz = packetClazz; + } + + public Class getAttachmentClazz() { + return attachmentClazz; + } + + public void setAttachmentClazz(Class attachmentClazz) { + this.attachmentClazz = attachmentClazz; + } + +} diff --git a/net/src/main/java/com/zfoo/net/handler/BaseDispatcherHandler.java b/net/src/main/java/com/zfoo/net/handler/BaseDispatcherHandler.java new file mode 100644 index 00000000..fac559d4 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/BaseDispatcherHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler; + +import com.zfoo.net.NetContext; +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.net.util.SessionUtils; +import com.zfoo.protocol.util.StringUtils; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.AttributeKey; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@ChannelHandler.Sharable +public class BaseDispatcherHandler extends ChannelInboundHandlerAdapter { + + private static final Logger logger = LoggerFactory.getLogger(BaseDispatcherHandler.class); + + public static final AttributeKey SESSION_KEY = AttributeKey.valueOf("session"); + + public static Session initChannel(Channel channel) { + var sessionAttr = channel.attr(SESSION_KEY); + var session = new Session(channel); + var setSuccessful = sessionAttr.compareAndSet(null, session); + if (!setSuccessful) { + channel.close(); + throw new RuntimeException(StringUtils.format("无法设置[channel:{}]的session", channel)); + } + + session.putAttribute(AttributeType.CHANNEL_REMOTE_ADDRESS, StringUtils.substringAfterFirst(channel.remoteAddress().toString(), StringUtils.SLASH)); + return session; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + var session = SessionUtils.getSession(ctx); + if (session == null) { + return; + } + DecodedPacketInfo decodedPacketInfo = (DecodedPacketInfo) msg; + NetContext.getDispatcher().receive(session, decodedPacketInfo.getPacket(), decodedPacketInfo.getPacketAttachment()); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + try { + logger.error("[session{}]未知异常", SessionUtils.sessionInfo(ctx), cause); + } finally { + ctx.close(); + } + } + +} diff --git a/net/src/main/java/com/zfoo/net/handler/ClientDispatcherHandler.java b/net/src/main/java/com/zfoo/net/handler/ClientDispatcherHandler.java new file mode 100644 index 00000000..79e16f54 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/ClientDispatcherHandler.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler; + +import com.zfoo.net.NetContext; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.util.SessionUtils; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@ChannelHandler.Sharable +public class ClientDispatcherHandler extends BaseDispatcherHandler { + + private static final Logger logger = LoggerFactory.getLogger(ClientDispatcherHandler.class); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + logger.info("client channel [{}] is active", SessionUtils.sessionInfo(ctx)); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + + var session = SessionUtils.getSession(ctx); + if (session != null) { + var consumeAttribute = session.getAttribute(AttributeType.CONSUMER); + NetContext.getSessionManager().removeClientSession(session); + + // 如果是消费者inactive,还需要触发客户端消费者检查事件,以便重新连接 + if (consumeAttribute != null) { + NetContext.getConfigManager().getRegistry().checkConsumer(); + } + } + logger.warn("[channel:{}] is inactive", SessionUtils.sessionInfo(ctx)); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + logger.error("[session{}]未知异常", SessionUtils.sessionInfo(ctx), cause); + } + +} diff --git a/net/src/main/java/com/zfoo/net/handler/GatewayDispatcherHandler.java b/net/src/main/java/com/zfoo/net/handler/GatewayDispatcherHandler.java new file mode 100644 index 00000000..74b7c2bf --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/GatewayDispatcherHandler.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.net.NetContext; +import com.zfoo.net.consumer.balancer.ConsistentHashConsumerLoadBalancer; +import com.zfoo.net.core.gateway.IGatewayLoadBalancer; +import com.zfoo.net.core.gateway.model.GatewaySessionInactiveEvent; +import com.zfoo.net.packet.common.Heartbeat; +import com.zfoo.net.packet.common.Ping; +import com.zfoo.net.packet.common.Pong; +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.packet.model.GatewayPacketAttachment; +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.net.util.SessionUtils; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.BiFunction; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@ChannelHandler.Sharable +public class GatewayDispatcherHandler extends ServerDispatcherHandler { + + private static final Logger logger = LoggerFactory.getLogger(GatewayDispatcherHandler.class); + + private BiFunction packetFilter; + + public GatewayDispatcherHandler(BiFunction packetFilter) { + this.packetFilter = packetFilter; + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) { + // 请求者的session,一般是serverSession + var session = SessionUtils.getSession(ctx); + if (session == null) { + return; + } + + var decodedPacketInfo = (DecodedPacketInfo) msg; + var packet = decodedPacketInfo.getPacket(); + if (packet.protocolId() == Heartbeat.heartbeatProtocolId()) { + return; + } + if (packet.protocolId() == Ping.pingProtocolId()) { + NetContext.getDispatcher().send(session, Pong.valueOf(TimeUtils.now()), null); + return; + } + + // 过滤非法包 + if (packetFilter != null && packetFilter.apply(session, packet)) { + throw new IllegalArgumentException(StringUtils.format("[session:{}]发送了一个非法包[{}]" + , SessionUtils.sessionInfo(ctx), JsonUtils.object2String(packet))); + } + + var signalAttachment = (SignalPacketAttachment) decodedPacketInfo.getPacketAttachment(); + var gatewayPacketAttachment = new GatewayPacketAttachment(session, signalAttachment); + + // 网关优先使用IGatewayLoadBalancer作为一致性hash的计算参数,然后才会使用客户端的session做参数 + if (packet instanceof IGatewayLoadBalancer) { + var loadBalancerConsistentHashObject = ((IGatewayLoadBalancer) packet).loadBalancerConsistentHashObject(); + gatewayPacketAttachment.useExecutorConsistentHash(loadBalancerConsistentHashObject); + forwardingPacket(packet, gatewayPacketAttachment, loadBalancerConsistentHashObject); + return; + } else { + // 使用用户的uid做一致性hash + var uid = (Long) session.getAttribute(AttributeType.UID); + if (uid != null) { + forwardingPacket(packet, gatewayPacketAttachment, uid); + return; + } + } + // 再使用session的sid做一致性hash,因为每次客户端连接过来sid都会改变,所以客户端重写建立连接的话可能会被路由到其它的服务器 + // 如果有特殊需求的话,可以考虑去重写网关的转发策略 + var sid = session.getSid(); + forwardingPacket(packet, gatewayPacketAttachment, sid); + } + + /** + * 转发网关收到的包 + */ + private void forwardingPacket(IPacket packet, IPacketAttachment attachment, Object argument) { + try { + var consumerSession = ConsistentHashConsumerLoadBalancer.getInstance().loadBalancer(packet, argument); + NetContext.getDispatcher().send(consumerSession, packet, attachment); + } catch (Exception e) { + logger.error("网关发生异常", e); + } catch (Throwable t) { + logger.error("网关发生错误", t); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + var session = SessionUtils.getSession(ctx); + if (session == null) { + return; + } + + var sid = session.getSid(); + var uid = (Long) session.getAttribute(AttributeType.UID); + EventBus.asyncSubmit(GatewaySessionInactiveEvent.valueOf(sid, uid == null ? 0 : uid.longValue())); + + super.channelInactive(ctx); + } + +} diff --git a/net/src/main/java/com/zfoo/net/handler/ServerDispatcherHandler.java b/net/src/main/java/com/zfoo/net/handler/ServerDispatcherHandler.java new file mode 100644 index 00000000..bd0c96ab --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/ServerDispatcherHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.net.NetContext; +import com.zfoo.net.core.tcp.model.ServerSessionInactiveEvent; +import com.zfoo.net.util.SessionUtils; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@ChannelHandler.Sharable +public class ServerDispatcherHandler extends BaseDispatcherHandler { + + private static final Logger logger = LoggerFactory.getLogger(ServerDispatcherHandler.class); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + var session = initChannel(ctx.channel()); + NetContext.getSessionManager().addServerSession(session); + logger.info("server channel [{}] is active", SessionUtils.sessionInfo(ctx)); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + + var session = SessionUtils.getSession(ctx); + if (session == null) { + return; + } + NetContext.getSessionManager().removeServerSession(session); + EventBus.asyncSubmit(ServerSessionInactiveEvent.valueOf(session)); + logger.warn("[channel:{}] is inactive", SessionUtils.sessionInfo(ctx)); + } +} diff --git a/net/src/main/java/com/zfoo/net/handler/codec/tcp/TcpPacketCodecHandler.java b/net/src/main/java/com/zfoo/net/handler/codec/tcp/TcpPacketCodecHandler.java new file mode 100644 index 00000000..ae0d3b05 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/codec/tcp/TcpPacketCodecHandler.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler.codec.tcp; + +import com.zfoo.net.NetContext; +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.packet.model.EncodedPacketInfo; +import com.zfoo.net.util.SessionUtils; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageCodec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * header(4byte) + protocolId(2byte) + packet + * header = body(bytes.length) + protocolId.length(2byte) + * + * @author jaysunxiao + * @version 3.0 + */ +public class TcpPacketCodecHandler extends ByteToMessageCodec { + + private static final Logger logger = LoggerFactory.getLogger(TcpPacketCodecHandler.class); + + // 数据包的最大长度限制,防止恶意的攻击 + private static final int MAX_LENGTH = 1 * IOUtils.BITS_PER_MB; + + private int length; + private boolean remain = false; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { + try { + if (!remain) { + // 不够读一个int + if (in.readableBytes() <= ProtocolManager.PROTOCOL_HEAD_LENGTH) { + return; + } + length = in.readInt(); + remain = true; + } + + // 如果长度超过限制,则抛出异常断开连接 + if (length > MAX_LENGTH) { + throw new IllegalArgumentException(StringUtils + .format("[session:{}]的包头长度[length:{}]超过最大长度[maxLength:{}]限制" + , SessionUtils.sessionInfo(ctx), length, MAX_LENGTH)); + } + + // ByteBuf里的数据太小 + if (in.readableBytes() < length) { + return; + } + + remain = false; + + DecodedPacketInfo packetInfo = NetContext.getPacketService().read(in); + + out.add(packetInfo); + } catch (Exception e) { + logger.error("[session:{}]解码exception异常", SessionUtils.sessionInfo(ctx), e); + throw e; + } catch (Throwable t) { + logger.error("[session:{}]解码throwable错误", SessionUtils.sessionInfo(ctx), t); + throw t; + } + } + + @Override + protected void encode(ChannelHandlerContext ctx, EncodedPacketInfo packetInfo, ByteBuf out) { + try { + NetContext.getPacketService().write(out, packetInfo.getPacket(), packetInfo.getPacketAttachment()); + } catch (Exception e) { + logger.error("[session:{}][{}]编码exception异常", SessionUtils.sessionInfo(ctx), packetInfo.getPacket().getClass().getSimpleName(), e); + throw e; + } catch (Throwable t) { + logger.error("[session:{}][{}]编码throwable错误", SessionUtils.sessionInfo(ctx), packetInfo.getPacket().getClass().getSimpleName(), t); + throw t; + } + } + +} diff --git a/net/src/main/java/com/zfoo/net/handler/codec/websocket/WebSocketCodecHandler.java b/net/src/main/java/com/zfoo/net/handler/codec/websocket/WebSocketCodecHandler.java new file mode 100644 index 00000000..51cd7b70 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/codec/websocket/WebSocketCodecHandler.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler.codec.websocket; + +import com.zfoo.net.NetContext; +import com.zfoo.net.handler.codec.tcp.TcpPacketCodecHandler; +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.packet.model.EncodedPacketInfo; +import com.zfoo.net.util.SessionUtils; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageCodec; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * header(4byte) + protocolId(2byte) + packet + * header = body(bytes.length) + protocolId.length(2byte) + * + * @author jaysunxiao + * @version 3.0 + */ +public class WebSocketCodecHandler extends MessageToMessageCodec { + + private static final Logger logger = LoggerFactory.getLogger(TcpPacketCodecHandler.class); + + // 数据包的最大长度限制,防止恶意的攻击 + private static final int MAX_LENGTH = 100 * IOUtils.BYTES_PER_KB; + + private int length; + private boolean remain = false; + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, WebSocketFrame webSocketFrame, List list) { + try { + ByteBuf in = webSocketFrame.content(); + + if (!remain) { + // 不够读一个int + if (in.readableBytes() <= ProtocolManager.PROTOCOL_HEAD_LENGTH) { + return; + } + length = in.readInt(); + remain = true; + } + + // 如果长度超过限制,则抛出异常断开连接 + if (length > MAX_LENGTH) { + throw new IllegalArgumentException(StringUtils + .format("[session:{}]的包头长度[length:{}]超过最大长度[maxLength:{}]限制" + , SessionUtils.sessionInfo(channelHandlerContext), length, MAX_LENGTH)); + } + + // ByteBuf里的数据太小 + if (in.readableBytes() < length) { + return; + } + + remain = false; + + DecodedPacketInfo packetInfo = NetContext.getPacketService().read(in); + + list.add(packetInfo); + } catch (Exception e) { + logger.error("exception异常", e); + throw e; + } catch (Throwable t) { + logger.error("throwable错误", t); + throw t; + } + } + + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, EncodedPacketInfo out, List list) { + try { + ByteBuf byteBuf = Unpooled.directBuffer(); + byteBuf.clear(); + + NetContext.getPacketService().write(byteBuf, out.getPacket(), out.getPacketAttachment()); + list.add(new BinaryWebSocketFrame(byteBuf)); + } catch (Exception e) { + logger.error("[{}]编码exception异常", JsonUtils.object2String(out), e); + throw e; + } catch (Throwable t) { + logger.error("[{}]编码throwable错误", JsonUtils.object2String(out), t); + throw t; + } + } + + +} diff --git a/net/src/main/java/com/zfoo/net/handler/idle/ClientIdleHandler.java b/net/src/main/java/com/zfoo/net/handler/idle/ClientIdleHandler.java new file mode 100644 index 00000000..d71f1bf2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/idle/ClientIdleHandler.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler.idle; + +import com.zfoo.net.packet.common.Heartbeat; +import com.zfoo.net.packet.model.EncodedPacketInfo; +import com.zfoo.net.util.SessionUtils; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@ChannelHandler.Sharable +public class ClientIdleHandler extends ChannelDuplexHandler { + + private static final Logger logger = LoggerFactory.getLogger(ClientIdleHandler.class); + + private static final EncodedPacketInfo heartbeatPacket = EncodedPacketInfo.valueOf(Heartbeat.getInstance(), null); + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + IdleStateEvent event = (IdleStateEvent) evt; + if (event.state() == IdleState.ALL_IDLE) { + logger.warn("client sends heartbeat packet to {}", SessionUtils.sessionInfo(ctx)); + ctx.channel().writeAndFlush(heartbeatPacket); + } + } + + } +} diff --git a/net/src/main/java/com/zfoo/net/handler/idle/ServerIdleHandler.java b/net/src/main/java/com/zfoo/net/handler/idle/ServerIdleHandler.java new file mode 100644 index 00000000..dcf54498 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/handler/idle/ServerIdleHandler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.handler.idle; + +import com.zfoo.net.util.SessionUtils; +import io.netty.channel.ChannelDuplexHandler; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@ChannelHandler.Sharable +public class ServerIdleHandler extends ChannelDuplexHandler { + + private static final Logger logger = LoggerFactory.getLogger(ServerIdleHandler.class); + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { + if (evt instanceof IdleStateEvent) { + IdleStateEvent event = (IdleStateEvent) evt; + if (event.state() == IdleState.ALL_IDLE) { + logger.warn("[channel:{}] is time out for close", SessionUtils.sessionInfo(ctx)); + } + ctx.close(); + } + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/Error.java b/net/src/main/java/com/zfoo/net/packet/common/Error.java new file mode 100644 index 00000000..3cacac5b --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/Error.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Error implements IPacket { + + public static final transient short PROTOCOL_ID = 101; + + private int module; + + private int errorCode; + + private String errorMessage; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public static short errorProtocolId() { + return PROTOCOL_ID; + } + + @Override + public String toString() { + FormattingTuple message = MessageFormatter.arrayFormat( + "module:[{}], errorCode:[{}], errorMessage:[{}]", new Object[]{module, errorCode, errorMessage}); + return message.getMessage(); + } + + public static Error valueOf(int module, int errorCode, String errorMessage) { + Error response = new Error(); + response.module = module; + response.errorCode = errorCode; + response.errorMessage = errorMessage; + return response; + } + + public static Error valueOf(IPacket packet, int errorCode, String errorMessage) { + Error response = new Error(); + response.module = ProtocolManager.getProtocol(packet.protocolId()).module(); + response.errorCode = errorCode; + response.errorMessage = errorMessage; + return response; + } + + public static Error valueOf(IPacket packet, int errorCode) { + return valueOf(packet, errorCode, null); + } + + public static Error valueOf(IPacket packet, String errorMessage) { + return valueOf(packet, 0, errorMessage); + } + + public static Error valueOf(String errorMessage) { + return valueOf(0, 0, errorMessage); + } + + public int getModule() { + return module; + } + + public void setModule(int module) { + this.module = module; + } + + public int getErrorCode() { + return errorCode; + } + + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/Heartbeat.java b/net/src/main/java/com/zfoo/net/packet/common/Heartbeat.java new file mode 100644 index 00000000..2df4483d --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/Heartbeat.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Heartbeat implements IPacket { + + public static final transient short PROTOCOL_ID = 102; + + private static Heartbeat INSTANCE = new Heartbeat(); + + public static Heartbeat getInstance() { + return INSTANCE; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public static short heartbeatProtocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/Message.java b/net/src/main/java/com/zfoo/net/packet/common/Message.java new file mode 100644 index 00000000..49a4960e --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/Message.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; + +/** + * 通用的返回,既可以用在远程调用,又可以嵌套在其它协议里 + * + * @author jaysunxiao + * @version 3.0 + */ +public class Message implements IPacket { + + public static final transient short PROTOCOL_ID = 100; + + private byte module; + + /** + * 1是成功,其它的均视为失败的请求 + */ + private int code; + + private String message; + + public static Message valueOf(IPacket packet, int code, String message) { + var mess = new Message(); + mess.module = ProtocolManager.moduleByProtocolId(packet.protocolId()).getId(); + mess.code = code; + mess.message = message; + return mess; + } + + public static Message valueOf(IPacket packet, int code) { + return Message.valueOf(packet, code, null); + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public boolean success() { + return code == 1; + } + + public byte getModule() { + return module; + } + + public void setModule(byte module) { + this.module = module; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/PairLS.java b/net/src/main/java/com/zfoo/net/packet/common/PairLS.java new file mode 100644 index 00000000..7cf4f900 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/PairLS.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.zfoo.protocol.IPacket; + +/** + * Long + String + * + * @author jaysunxiao + * @version 3.0 + */ +public class PairLS implements IPacket { + + public static final transient short PROTOCOL_ID = 113; + + @JsonSerialize(using = ToStringSerializer.class) + private long key; + + private String value; + + public static PairLS valueOf(long key, String value) { + var pair = new PairLS(); + pair.key = key; + pair.value = value; + return pair; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getKey() { + return key; + } + + public void setKey(long key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/PairLong.java b/net/src/main/java/com/zfoo/net/packet/common/PairLong.java new file mode 100644 index 00000000..c1870a8d --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/PairLong.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.zfoo.protocol.IPacket; + +import java.util.Comparator; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PairLong implements IPacket { + + public static final transient short PROTOCOL_ID = 111; + + public static transient final Comparator NATURAL_VALUE_COMPARATOR = (a, b) -> Long.compare(a.getValue(), b.getValue()); + + @JsonSerialize(using = ToStringSerializer.class) + private long key; + + @JsonSerialize(using = ToStringSerializer.class) + private long value; + + public static PairLong valueOf(long key, long value) { + var pair = new PairLong(); + pair.key = key; + pair.value = value; + return pair; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getKey() { + return key; + } + + public void setKey(long key) { + this.key = key; + } + + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/PairString.java b/net/src/main/java/com/zfoo/net/packet/common/PairString.java new file mode 100644 index 00000000..83e30454 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/PairString.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PairString implements IPacket { + + public static final transient short PROTOCOL_ID = 112; + + private String key; + + private String value; + + public static PairString valueOf(String key, String value) { + var pair = new PairString(); + pair.key = key; + pair.value = value; + return pair; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/Ping.java b/net/src/main/java/com/zfoo/net/packet/common/Ping.java new file mode 100644 index 00000000..b052e778 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/Ping.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Ping implements IPacket { + + public static final transient short PROTOCOL_ID = 103; + + public static short pingProtocolId() { + return PROTOCOL_ID; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/Pong.java b/net/src/main/java/com/zfoo/net/packet/common/Pong.java new file mode 100644 index 00000000..369bbbf2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/Pong.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Pong implements IPacket { + + public static final transient short PROTOCOL_ID = 104; + + /** + * 服务器当前的时间戳 + */ + private long time; + + public static Pong valueOf(long time) { + var pong = new Pong(); + pong.time = time; + return pong; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/TripleLSS.java b/net/src/main/java/com/zfoo/net/packet/common/TripleLSS.java new file mode 100644 index 00000000..a0753c21 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/TripleLSS.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.zfoo.protocol.IPacket; + +/** + * Long + String + String + * + * @author jaysunxiao + * @version 3.0 + */ +public class TripleLSS implements IPacket { + + public static final transient short PROTOCOL_ID = 116; + + @JsonSerialize(using = ToStringSerializer.class) + private long left; + private String middle; + private String right; + + public static TripleLSS valueOf(long left, String middle, String right) { + var triple = new TripleLSS(); + triple.left = left; + triple.middle = middle; + triple.right = right; + return triple; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getLeft() { + return left; + } + + public void setLeft(long left) { + this.left = left; + } + + public String getMiddle() { + return middle; + } + + public void setMiddle(String middle) { + this.middle = middle; + } + + public String getRight() { + return right; + } + + public void setRight(String right) { + this.right = right; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/TripleLong.java b/net/src/main/java/com/zfoo/net/packet/common/TripleLong.java new file mode 100644 index 00000000..9351db14 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/TripleLong.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TripleLong implements IPacket { + + public static final transient short PROTOCOL_ID = 114; + + @JsonSerialize(using = ToStringSerializer.class) + private long left; + @JsonSerialize(using = ToStringSerializer.class) + private long middle; + @JsonSerialize(using = ToStringSerializer.class) + private long right; + + public static TripleLong valueOf(long left, long middle, long right) { + var triple = new TripleLong(); + triple.left = left; + triple.middle = middle; + triple.right = right; + return triple; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public long getLeft() { + return left; + } + + public void setLeft(long left) { + this.left = left; + } + + public long getMiddle() { + return middle; + } + + public void setMiddle(long middle) { + this.middle = middle; + } + + public long getRight() { + return right; + } + + public void setRight(long right) { + this.right = right; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/common/TripleString.java b/net/src/main/java/com/zfoo/net/packet/common/TripleString.java new file mode 100644 index 00000000..687ac2fb --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/common/TripleString.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.common; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TripleString implements IPacket { + + public static final transient short PROTOCOL_ID = 115; + + private String left; + private String middle; + private String right; + + public static TripleString valueOf(String left, String middle, String right) { + var triple = new TripleString(); + triple.left = left; + triple.middle = middle; + triple.right = right; + return triple; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getLeft() { + return left; + } + + public void setLeft(String left) { + this.left = left; + } + + public String getMiddle() { + return middle; + } + + public void setMiddle(String middle) { + this.middle = middle; + } + + public String getRight() { + return right; + } + + public void setRight(String right) { + this.right = right; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/DecodedPacketInfo.java b/net/src/main/java/com/zfoo/net/packet/model/DecodedPacketInfo.java new file mode 100644 index 00000000..8d7038ea --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/DecodedPacketInfo.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class DecodedPacketInfo { + + /** + * 解码后的包 + */ + private IPacket packet; + + /** + * 解码后的包的附加包 + */ + private IPacketAttachment packetAttachment; + + + public static DecodedPacketInfo valueOf(IPacket packet, IPacketAttachment packetAttachment) { + DecodedPacketInfo packetInfo = new DecodedPacketInfo(); + packetInfo.packet = packet; + packetInfo.packetAttachment = packetAttachment; + return packetInfo; + } + + + public IPacket getPacket() { + return packet; + } + + public void setPacket(IPacket packet) { + this.packet = packet; + } + + public IPacketAttachment getPacketAttachment() { + return packetAttachment; + } + + public void setPacketAttachment(IPacketAttachment packetAttachment) { + this.packetAttachment = packetAttachment; + } + +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/EncodedPacketInfo.java b/net/src/main/java/com/zfoo/net/packet/model/EncodedPacketInfo.java new file mode 100644 index 00000000..fabd36a5 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/EncodedPacketInfo.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +import com.zfoo.protocol.IPacket; +import org.springframework.lang.Nullable; + +/** + * 被解码后的Packet的信息 + * + * @author jaysunxiao + * @version 3.0 + */ +public class EncodedPacketInfo { + + /** + * 解码后的包 + */ + private IPacket packet; + + /** + * 解码后的包的附加包 + */ + private IPacketAttachment packetAttachment; + + /** + * 长度 + */ + private int length; + /** + * 加密所用时间 + */ + private long encodedTime; + + public static EncodedPacketInfo valueOf(IPacket packet, @Nullable IPacketAttachment packetAttachment) { + EncodedPacketInfo packetInfo = new EncodedPacketInfo(); + packetInfo.packet = packet; + packetInfo.packetAttachment = packetAttachment; + return packetInfo; + } + + public IPacket getPacket() { + return packet; + } + + public void setPacket(IPacket packet) { + this.packet = packet; + } + + public IPacketAttachment getPacketAttachment() { + return packetAttachment; + } + + public void setPacketAttachment(IPacketAttachment packetAttachment) { + this.packetAttachment = packetAttachment; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public long getEncodedTime() { + return encodedTime; + } + + public void setEncodedTime(long encodedTime) { + this.encodedTime = encodedTime; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/GatewayPacketAttachment.java b/net/src/main/java/com/zfoo/net/packet/model/GatewayPacketAttachment.java new file mode 100644 index 00000000..cf423905 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/GatewayPacketAttachment.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.util.math.HashUtils; +import org.springframework.lang.Nullable; + +/** + * 附加包对业务层透明,禁止在业务层使用 + * + * @author jaysunxiao + * @version 3.0 + */ +public class GatewayPacketAttachment implements IPacketAttachment { + + public static final transient short PROTOCOL_ID = 1; + + /** + * session的id,一般是客户端连接网关的那个sid + */ + private long sid; + + /** + * 用戶Id,从网关转发到后面的消息必须要附带用户的Id信息,要不然无法知道是哪个用户发过来的,0代表没有用户id + */ + private long uid; + + /** + * 是否使用consistentHashId作为一致性hashId + */ + private boolean useExecutorConsistentHash; + /** + * 用来在TaskManage中计算一致性hash的参数 + */ + private int executorConsistentHash; + + /** + * true为客户端,false为服务端 + */ + private boolean client; + + + /** + * 客户端发到网关的可能是一个带有同步或者异步的附加包,网关转发的时候需要把这个附加包给带上 + */ + private SignalPacketAttachment signalPacketAttachment; + + + public GatewayPacketAttachment() { + } + + public GatewayPacketAttachment(Session session, @Nullable SignalPacketAttachment signalPacketAttachment) { + this.client = true; + this.sid = session.getSid(); + var uid = session.getAttribute(AttributeType.UID); + this.uid = uid == null ? 0 : (long) uid; + this.signalPacketAttachment = signalPacketAttachment; + } + + public GatewayPacketAttachment(long sid, long uid) { + this.sid = sid; + this.uid = uid; + } + + + @Override + public PacketAttachmentType packetType() { + return PacketAttachmentType.GATEWAY_PACKET; + } + + @Override + public int executorConsistentHash() { + if (useExecutorConsistentHash) { + return executorConsistentHash; + } else { + return HashUtils.fnvHash(uid); + } + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public void useExecutorConsistentHash(Object argument) { + this.useExecutorConsistentHash = true; + this.executorConsistentHash = HashUtils.fnvHash(argument); + } + + public long getSid() { + return sid; + } + + public void setSid(long sid) { + this.sid = sid; + } + + public long getUid() { + return uid; + } + + public void setUid(long uid) { + this.uid = uid; + } + + public boolean isUseExecutorConsistentHash() { + return useExecutorConsistentHash; + } + + public void setUseExecutorConsistentHash(boolean useExecutorConsistentHash) { + this.useExecutorConsistentHash = useExecutorConsistentHash; + } + + public int getExecutorConsistentHash() { + return executorConsistentHash; + } + + public void setExecutorConsistentHash(int executorConsistentHash) { + this.executorConsistentHash = executorConsistentHash; + } + + public boolean isClient() { + return client; + } + + public void setClient(boolean client) { + this.client = client; + } + + + public SignalPacketAttachment getSignalPacketAttachment() { + return signalPacketAttachment; + } + + public void setSignalPacketAttachment(SignalPacketAttachment signalPacketAttachment) { + this.signalPacketAttachment = signalPacketAttachment; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/IPacketAttachment.java b/net/src/main/java/com/zfoo/net/packet/model/IPacketAttachment.java new file mode 100644 index 00000000..85990bb2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/IPacketAttachment.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IPacketAttachment extends IPacket { + + PacketAttachmentType packetType(); + + /** + * 用来确定这条消息在哪一个线程处理 + * + * @return 一致性hashId + */ + int executorConsistentHash(); + +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/NoAnswerAttachment.java b/net/src/main/java/com/zfoo/net/packet/model/NoAnswerAttachment.java new file mode 100644 index 00000000..651bcaf3 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/NoAnswerAttachment.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +/** + * 附加包对业务层透明,禁止在业务层使用 + * + * @author jaysunxiao + * @version 3.0 + */ +public class NoAnswerAttachment implements IPacketAttachment { + + public static final transient short PROTOCOL_ID = 2; + + /** + * 用来在TaskManage中计算一致性hash的参数 + */ + private int executorConsistentHash; + + public static NoAnswerAttachment valueOf(int executorConsistentHash) { + var attachment = new NoAnswerAttachment(); + attachment.executorConsistentHash = executorConsistentHash; + return attachment; + } + + @Override + public PacketAttachmentType packetType() { + return PacketAttachmentType.NO_ANSWER_PACKET; + } + + @Override + public int executorConsistentHash() { + return executorConsistentHash; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + public int getExecutorConsistentHash() { + return executorConsistentHash; + } + + public void setExecutorConsistentHash(int executorConsistentHash) { + this.executorConsistentHash = executorConsistentHash; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/PacketAttachmentType.java b/net/src/main/java/com/zfoo/net/packet/model/PacketAttachmentType.java new file mode 100644 index 00000000..49622596 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/PacketAttachmentType.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public enum PacketAttachmentType { + + /** + * 正常的附加包 + */ + NORMAL_PACKET((byte) 0, null), + + /** + * 带有同步或者异步信息的附加包 + */ + SIGNAL_PACKET((byte) 1, SignalPacketAttachment.class), + + /** + * 带有网关信息的附加包 + */ + GATEWAY_PACKET((byte) 2, GatewayPacketAttachment.class), + + /** + * 无返回消息的附加包 + */ + NO_ANSWER_PACKET((byte) 3, NoAnswerAttachment.class), + + ; + + + public static final Map map = new HashMap<>(values().length); + + static { + for (var packetType : PacketAttachmentType.values()) { + map.put(packetType.packetType, packetType); + } + } + + public static PacketAttachmentType getPacketType(byte packetType) { + return map.getOrDefault(packetType, PacketAttachmentType.NORMAL_PACKET); + } + + public byte getPacketType() { + return packetType; + } + + private byte packetType; + private Class clazz; + + PacketAttachmentType(byte packetType, Class clazz) { + this.packetType = packetType; + this.clazz = clazz; + } + +} diff --git a/net/src/main/java/com/zfoo/net/packet/model/SignalPacketAttachment.java b/net/src/main/java/com/zfoo/net/packet/model/SignalPacketAttachment.java new file mode 100644 index 00000000..16758473 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/model/SignalPacketAttachment.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.model; + +import com.zfoo.protocol.IPacket; +import com.zfoo.scheduler.util.TimeUtils; +import com.zfoo.util.security.IdUtils; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +/** + * 附加包对业务层透明,禁止在业务层使用 + * + * @author jaysunxiao + * @version 3.0 + */ +public class SignalPacketAttachment implements IPacketAttachment { + + public static final transient short PROTOCOL_ID = 0; + + /** + * 唯一标识一个packet, 唯一表示一个PacketAttachment,hashcode() and equals() 也通过packetId计算 + */ + private int packetId = IdUtils.getLocalIntId(); + + /** + * 用来在TaskManage中计算一致性hash的参数 + */ + private int executorConsistentHash = -1; + + /** + * true为客户端,false为服务端 + */ + private boolean client = true; + + /** + * 客户端发送的时间 + */ + private transient long timestamp = TimeUtils.now(); + + /** + * 客户端收到服务器回复的时候回调的方法 + */ + private transient CompletableFuture responseFuture = new CompletableFuture<>(); + + public SignalPacketAttachment() { + } + + + @Override + public PacketAttachmentType packetType() { + return PacketAttachmentType.SIGNAL_PACKET; + } + + @Override + public int executorConsistentHash() { + return executorConsistentHash; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SignalPacketAttachment that = (SignalPacketAttachment) o; + return Objects.equals(packetId, that.packetId); + } + + @Override + public int hashCode() { + return Objects.hash(packetId); + } + + public int getPacketId() { + return packetId; + } + + public void setPacketId(int packetId) { + this.packetId = packetId; + } + + public int getExecutorConsistentHash() { + return executorConsistentHash; + } + + public void setExecutorConsistentHash(int executorConsistentHash) { + this.executorConsistentHash = executorConsistentHash; + } + + public boolean isClient() { + return client; + } + + public void setClient(boolean client) { + this.client = client; + } + + + public CompletableFuture getResponseFuture() { + return responseFuture; + } + + public void setResponseFuture(CompletableFuture responseFuture) { + this.responseFuture = responseFuture; + } +} diff --git a/net/src/main/java/com/zfoo/net/packet/service/IPacketService.java b/net/src/main/java/com/zfoo/net/packet/service/IPacketService.java new file mode 100644 index 00000000..1717b6fa --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/service/IPacketService.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.service; + +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.protocol.IPacket; +import io.netty.buffer.ByteBuf; +import org.springframework.lang.Nullable; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IPacketService { + + void init(); + + DecodedPacketInfo read(ByteBuf buffer); + + void write(ByteBuf buffer, IPacket packet, @Nullable IPacketAttachment packetAttachment); + +} diff --git a/net/src/main/java/com/zfoo/net/packet/service/PacketService.java b/net/src/main/java/com/zfoo/net/packet/service/PacketService.java new file mode 100644 index 00000000..6eeee6a9 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/packet/service/PacketService.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.service; + +import com.zfoo.net.NetContext; +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.generate.GenerateOperation; +import com.zfoo.protocol.generate.GenerateProtocolFile; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.xml.XmlProtocols; +import com.zfoo.util.DomUtils; +import io.netty.buffer.ByteBuf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ResourceUtils; + +import java.io.IOException; +import java.util.function.Predicate; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PacketService implements IPacketService { + + private static final Logger logger = LoggerFactory.getLogger(PacketService.class); + + /** + * 网络包的约定规则如下: + * 1. 客户端的请求约定以Request结尾,服务器的响应约定以Response结尾 + * 2. 服务器内部请求约定以Ask结尾,服务器内部的响应约定以Answer结尾 + * 3. 服务器主动通知客户端以Notice结尾 + * 4. 公共的协议放在common模块 + */ + public static final String NET_REQUEST_SUFFIX = "Request"; + public static final String NET_RESPONSE_SUFFIX = "Response"; + + public static final String NET_ASK_SUFFIX = "Ask"; + public static final String NET_ANSWER_SUFFIX = "Answer"; + + public static final String NET_NOTICE_SUFFIX = "Notice"; + + + public static final String NET_COMMON_MODULE = "common"; + + private Predicate netGenerateProtocolFilter = registration + -> ProtocolManager.moduleByModuleId(registration.module()).getName().matches(NET_COMMON_MODULE) + || registration.protocolConstructor().getDeclaringClass().getSimpleName().endsWith(NET_REQUEST_SUFFIX) + || registration.protocolConstructor().getDeclaringClass().getSimpleName().endsWith(NET_RESPONSE_SUFFIX) + || registration.protocolConstructor().getDeclaringClass().getSimpleName().endsWith(NET_NOTICE_SUFFIX); + + public PacketService() { + + } + + @Override + public void init() { + var applicationContext = NetContext.getApplicationContext(); + + var protocolLocation = NetContext.getConfigManager().getLocalConfig().getProtocolLocation(); + + var foldProtocol = NetContext.getConfigManager().getLocalConfig().isFoldProtocol(); + var protocolParam = NetContext.getConfigManager().getLocalConfig().getProtocolParam(); + var generateJsProtocol = NetContext.getConfigManager().getLocalConfig().isGenerateJsProtocol(); + var generateCsharpProtocol = NetContext.getConfigManager().getLocalConfig().isGenerateCsProtocol(); + var generateLuaProtocol = NetContext.getConfigManager().getLocalConfig().isGenerateLuaProtocol(); + var generateOperation = new GenerateOperation(); + generateOperation.setFoldProtocol(foldProtocol); + generateOperation.setProtocolParam(protocolParam); + generateOperation.setGenerateJsProtocol(generateJsProtocol); + generateOperation.setGenerateCsharpProtocol(generateCsharpProtocol); + generateOperation.setGenerateLuaProtocol(generateLuaProtocol); + + // 设置生成协议的过滤器 + GenerateProtocolFile.generateProtocolFilter = netGenerateProtocolFilter; + + // 解析protocol.xml文件,并将协议生成ProtocolRegistration + var resource = applicationContext.getResource(ResourceUtils.CLASSPATH_URL_PREFIX + protocolLocation); + try { + var xmlProtocols = DomUtils.inputStream2Object(resource.getInputStream(), XmlProtocols.class); + ProtocolManager.initProtocol(xmlProtocols, generateOperation); + } catch (IOException e) { + logger.error(ExceptionUtils.getMessage(e)); + throw new RuntimeException(e); + } + } + + @Override + public DecodedPacketInfo read(ByteBuf buffer) { + // 包的长度在上一层已经解析过 + + // 解析包体 + var packet = ProtocolManager.read(buffer); + // 解析包的附加包 + var attachment = ByteBufUtils.readBoolean(buffer); + var packetAttachment = attachment ? ((IPacketAttachment) ProtocolManager.read(buffer)) : null; + return DecodedPacketInfo.valueOf(packet, packetAttachment); + } + + @Override + public void write(ByteBuf buffer, IPacket packet, IPacketAttachment packetAttachment) { + + if (packet == null) { + logger.error("packet is null and can not be sent."); + return; + } + + buffer.clear(); + + // 预留写入包的长度,一个int字节大小 + buffer.writeInt(ProtocolManager.PROTOCOL_HEAD_LENGTH); + + // 写入包packet + ProtocolManager.write(buffer, packet); + + // 写入包的附加包packetAttachment + if (packetAttachment == null) { + ByteBufUtils.writeBoolean(buffer, false); + } else { + ByteBufUtils.writeBoolean(buffer, true); + // 写入包的附加包attachment + ProtocolManager.write(buffer, packetAttachment); + } + + int length = buffer.readableBytes(); + + int packetLength = length - ProtocolManager.PROTOCOL_HEAD_LENGTH; + + buffer.writerIndex(0); + + buffer.writeInt(packetLength); + + buffer.writerIndex(length); + } +} diff --git a/net/src/main/java/com/zfoo/net/schema/NamespaceHandler.java b/net/src/main/java/com/zfoo/net/schema/NamespaceHandler.java new file mode 100644 index 00000000..43ed650d --- /dev/null +++ b/net/src/main/java/com/zfoo/net/schema/NamespaceHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.schema; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NamespaceHandler extends NamespaceHandlerSupport { + + private final String NET_TAG = "config"; + + @Override + public void init() { + registerBeanDefinitionParser(NET_TAG, new NetDefinitionParser()); + } + +} diff --git a/net/src/main/java/com/zfoo/net/schema/NetDefinitionParser.java b/net/src/main/java/com/zfoo/net/schema/NetDefinitionParser.java new file mode 100644 index 00000000..8a544983 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/schema/NetDefinitionParser.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.schema; + +import com.zfoo.net.NetContext; +import com.zfoo.net.config.manager.ConfigManager; +import com.zfoo.net.config.model.*; +import com.zfoo.net.consumer.service.Consumer; +import com.zfoo.net.dispatcher.manager.PacketDispatcher; +import com.zfoo.net.packet.service.PacketService; +import com.zfoo.net.session.manager.SessionManager; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.DomUtils; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NetDefinitionParser implements BeanDefinitionParser { + + @Override + public AbstractBeanDefinition parse(Element element, ParserContext parserContext) { + Class clazz; + String name; + BeanDefinitionBuilder builder; + + // 注册NetConfig + parseNetConfig(element, parserContext); + + // 注册NetSpringContext + clazz = NetContext.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册ConfigManager + clazz = ConfigManager.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + builder.addPropertyReference("localConfig", NetConfig.class.getCanonicalName()); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + // 注册NetProcessor + clazz = NetProcessor.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + // 注册ProtocolManager + clazz = ProtocolManager.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + // 注册PacketService + clazz = PacketService.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + // 注册PacketDispatcherManager + clazz = PacketDispatcher.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + // 注册Consumer + clazz = Consumer.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + // 注册SessionManager + clazz = SessionManager.class; + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + + return builder.getBeanDefinition(); + } + + + private void parseNetConfig(Element element, ParserContext parserContext) { + var clazz = NetConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("id", "id", builder, element, parserContext); + resolvePlaceholder("protocol-location", "protocolLocation", builder, element, parserContext); + resolvePlaceholder("generate-js-protocol", "generateJsProtocol", builder, element, parserContext); + resolvePlaceholder("generate-cs-protocol", "generateCsProtocol", builder, element, parserContext); + resolvePlaceholder("generate-lua-protocol", "generateLuaProtocol", builder, element, parserContext); + resolvePlaceholder("fold-protocol", "foldProtocol", builder, element, parserContext); + resolvePlaceholder("protocol-param", "protocolParam", builder, element, parserContext); + + var registryElement = DomUtils.getFirstChildElementByTagName(element, "registry"); + if (registryElement != null) { + parseRegistryConfig(registryElement, parserContext); + builder.addPropertyReference("registryConfig", RegistryConfig.class.getCanonicalName()); + } + + var monitorElement = DomUtils.getFirstChildElementByTagName(element, "monitor"); + if (monitorElement != null) { + parseMonitorConfig(monitorElement, parserContext); + builder.addPropertyReference("monitorConfig", MonitorConfig.class.getCanonicalName()); + } + + var hostElement = DomUtils.getFirstChildElementByTagName(element, "host"); + if (hostElement != null) { + builder.addPropertyReference("hostConfig", HostConfig.class.getCanonicalName()); + parseHostConfig(hostElement, parserContext); + } + + var providerElement = DomUtils.getFirstChildElementByTagName(element, "provider"); + if (providerElement != null) { + builder.addPropertyReference("providerConfig", ProviderConfig.class.getCanonicalName()); + parseProviderConfig(providerElement, parserContext); + } + + var consumerElement = DomUtils.getFirstChildElementByTagName(element, "consumer"); + if (consumerElement != null) { + parseConsumerConfig(consumerElement, parserContext); + builder.addPropertyReference("consumerConfig", ConsumerConfig.class.getCanonicalName()); + } + + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private void parseRegistryConfig(Element element, ParserContext parserContext) { + var clazz = RegistryConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("center", "center", builder, element, parserContext); + resolvePlaceholder("user", "user", builder, element, parserContext); + resolvePlaceholder("password", "password", builder, element, parserContext); + var addressMap = parseAddress(element, parserContext); + builder.addPropertyValue("addressMap", addressMap); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private void parseMonitorConfig(Element element, ParserContext parserContext) { + var clazz = MonitorConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("center", "center", builder, element, parserContext); + resolvePlaceholder("user", "user", builder, element, parserContext); + resolvePlaceholder("password", "password", builder, element, parserContext); + var addressMap = parseAddress(element, parserContext); + builder.addPropertyValue("addressMap", addressMap); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private void parseHostConfig(Element element, ParserContext parserContext) { + var clazz = HostConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("center", "center", builder, element, parserContext); + resolvePlaceholder("user", "user", builder, element, parserContext); + resolvePlaceholder("password", "password", builder, element, parserContext); + var addressMap = parseAddress(element, parserContext); + builder.addPropertyValue("addressMap", addressMap); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private void parseProviderConfig(Element element, ParserContext parserContext) { + var clazz = ProviderConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("dispatch", "dispatch", builder, element, parserContext); + resolvePlaceholder("dispatch-thread", "dispatchThread", builder, element, parserContext); + resolvePlaceholder("address", "address", builder, element, parserContext); + + var providerModules = parseModules("provider", element, parserContext); + builder.addPropertyValue("modules", providerModules); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private void parseConsumerConfig(Element element, ParserContext parserContext) { + var clazz = ConsumerConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("load-balancer", "loadBalancer", builder, element, parserContext); + + var consumerModules = parseModules("consumer", element, parserContext); + builder.addPropertyValue("modules", consumerModules); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private ManagedList parseModules(String param, Element element, ParserContext parserContext) { + var moduleElementList = DomUtils.getChildElementsByTagName(element, "module"); + var modules = new ManagedList(); + var environment = parserContext.getReaderContext().getEnvironment(); + for (var i = 0; i < moduleElementList.size(); i++) { + var addressElement = moduleElementList.get(i); + var clazz = ProtocolModule.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("name"))); + + modules.add(new BeanDefinitionHolder(builder.getBeanDefinition(), StringUtils.format("{}.{}{}", clazz.getCanonicalName(), param, i))); + } + return modules; + } + + private ManagedMap parseAddress(Element element, ParserContext parserContext) { + var addressElementList = DomUtils.getChildElementsByTagName(element, "address"); + var addressMap = new ManagedMap(); + + for (var addressElement : addressElementList) { + var name = addressElement.getAttribute("name"); + var urlAttribute = addressElement.getAttribute("url"); + var url = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(urlAttribute); + addressMap.put(name, url); + } + return addressMap; + } + + private void resolvePlaceholder(String attributeName, String fieldName, BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { + var attributeValue = element.getAttribute(attributeName); + var environment = parserContext.getReaderContext().getEnvironment(); + var placeholder = environment.resolvePlaceholders(attributeValue); + builder.addPropertyValue(fieldName, placeholder); + } +} diff --git a/net/src/main/java/com/zfoo/net/schema/NetProcessor.java b/net/src/main/java/com/zfoo/net/schema/NetProcessor.java new file mode 100644 index 00000000..f82d1b5c --- /dev/null +++ b/net/src/main/java/com/zfoo/net/schema/NetProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.schema; + +import com.zfoo.net.NetContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NetProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (NetContext.getNetContext() == null) { + return bean; + } + NetContext.getDispatcher().registerPacketReceiverDefinition(bean); + return bean; + } + +} diff --git a/net/src/main/java/com/zfoo/net/session/manager/ISessionManager.java b/net/src/main/java/com/zfoo/net/session/manager/ISessionManager.java new file mode 100644 index 00000000..5fee942c --- /dev/null +++ b/net/src/main/java/com/zfoo/net/session/manager/ISessionManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.session.manager; + +import com.zfoo.net.session.model.Session; + +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ISessionManager { + + void addServerSession(Session session); + + void removeServerSession(Session session); + + Session getServerSession(Long id); + + Map getServerSessionMap(); + + + void addClientSession(Session session); + + void removeClientSession(Session session); + + Session getClientSession(Long id); + + Map getClientSessionMap(); + + int getClientSessionChangeId(); + + void shutdown(); + +} diff --git a/net/src/main/java/com/zfoo/net/session/manager/SessionManager.java b/net/src/main/java/com/zfoo/net/session/manager/SessionManager.java new file mode 100644 index 00000000..f21f9842 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/session/manager/SessionManager.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.session.manager; + +import com.zfoo.net.session.model.Session; +import com.zfoo.net.util.SessionUtils; +import com.zfoo.util.security.IdUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SessionManager implements ISessionManager { + + private static final Logger logger = LoggerFactory.getLogger(SessionManager.class); + + /** + * 作为服务器,被别的客户端连接的Session + */ + private final Map serverSessionMap = new ConcurrentHashMap<>(); + + + /** + * 作为客户端,连接别的服务器的Session + */ + private final Map clientSessionMap = new ConcurrentHashMap<>(); + + private volatile int clientSessionChangeId = IdUtils.getLocalIntId(); + + + @Override + public void addServerSession(Session session) { + if (serverSessionMap.containsKey(session.getSid())) { + logger.error("server收到重复的[session:{}]", SessionUtils.sessionInfo(session)); + return; + } + serverSessionMap.put(session.getSid(), session); + } + + @Override + public void removeServerSession(Session session) { + if (!serverSessionMap.containsKey(session.getSid())) { + logger.error("SessionManager中的serverSession没有包含[session:{}],所以无法移除", SessionUtils.sessionInfo(session)); + return; + } + serverSessionMap.remove(session.getSid()); + session.close(); + } + + @Override + public Session getServerSession(Long id) { + return serverSessionMap.get(id); + } + + @Override + public Map getServerSessionMap() { + return Collections.unmodifiableMap(serverSessionMap); + } + + @Override + public void addClientSession(Session session) { + if (clientSessionMap.containsKey(session.getSid())) { + logger.error("client收到重复的[session:{}]", SessionUtils.sessionInfo(session)); + return; + } + clientSessionMap.put(session.getSid(), session); + clientSessionChangeId = IdUtils.getLocalIntId(); + } + + @Override + public void removeClientSession(Session session) { + if (!clientSessionMap.containsKey(session.getSid())) { + logger.error("SessionManager中的clientSession没有包含[session:{}],所以无法移除", SessionUtils.sessionInfo(session)); + return; + } + clientSessionMap.remove(session.getSid()); + session.close(); + clientSessionChangeId = IdUtils.getLocalIntId(); + } + + @Override + public Session getClientSession(Long id) { + return clientSessionMap.get(id); + } + + @Override + public Map getClientSessionMap() { + return Collections.unmodifiableMap(clientSessionMap); + } + + @Override + public int getClientSessionChangeId() { + return clientSessionChangeId; + } + + @Override + public synchronized void shutdown() { + clientSessionMap.values().forEach(it -> { + try { + it.close(); + } catch (Exception e) { + logger.error("关闭[session:{}]发生未知异常", SessionUtils.sessionInfo(it), e); + } + }); + + serverSessionMap.values().forEach(it -> { + try { + it.close(); + } catch (Exception e) { + logger.error("关闭[session:{}]发生未知异常", SessionUtils.sessionInfo(it), e); + } + }); + + logger.info("已关闭客户端和服务器所有的session"); + } +} diff --git a/net/src/main/java/com/zfoo/net/session/model/AttributeType.java b/net/src/main/java/com/zfoo/net/session/model/AttributeType.java new file mode 100644 index 00000000..ef0c8ea3 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/session/model/AttributeType.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.session.model; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public enum AttributeType { + + + CHANNEL_REMOTE_ADDRESS, + + + /** + * 一般是客户端session + */ + CONSUMER, + + RESPONSE_TIME, + + /** + * session的uid + */ + UID, + + /** + * 网关ip + */ + GATEWAY_HOST_AND_PORT, + + ; + +} diff --git a/net/src/main/java/com/zfoo/net/session/model/Session.java b/net/src/main/java/com/zfoo/net/session/model/Session.java new file mode 100644 index 00000000..aa531a30 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/session/model/Session.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.session.model; + +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.protocol.util.StringUtils; +import io.netty.channel.Channel; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Session { + + private static final AtomicLong ATOMIC_LONG = new AtomicLong(0); + + /** + * session的id + */ + private long sid; + + private Channel channel; + + /** + * Session附带的属性参数 + */ + private Map attributes = new EnumMap<>(AttributeType.class); + + /** + * 客户端Session控制同步或异步的附加包,key:packetId + */ + private Map clientSignalPacketAttachmentMap = new ConcurrentHashMap<>(); + + + public Session(Channel channel) { + if (channel == null) { + throw new IllegalArgumentException("channel不能为空"); + } + this.sid = ATOMIC_LONG.getAndIncrement(); + this.channel = channel; + } + + + public void addClientSignalAttachment(SignalPacketAttachment packetAttachment) { + clientSignalPacketAttachmentMap.put(packetAttachment.getPacketId(), packetAttachment); + } + + public IPacketAttachment removeClientSignalAttachment(SignalPacketAttachment packetAttachment) { + return clientSignalPacketAttachmentMap.remove(packetAttachment.getPacketId()); + } + + + @Override + public String toString() { + return StringUtils.format("[sid:{}] [channel:{}] [attributes:{}]", sid, channel, attributes); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Session session = (Session) o; + return sid == session.sid; + } + + @Override + public int hashCode() { + return Objects.hash(sid); + } + + public long getSid() { + return sid; + } + + public void setSid(long sid) { + this.sid = sid; + } + + public synchronized void putAttribute(AttributeType key, Object value) { + attributes.put(key, value); + } + + public synchronized void removeAttribute(AttributeType key) { + attributes.remove(key); + } + + + public Object getAttribute(AttributeType key) { + return attributes.get(key); + } + + public Map getClientSignalPacketAttachmentMap() { + return Collections.unmodifiableMap(clientSignalPacketAttachmentMap); + } + + public Channel getChannel() { + return channel; + } + + public void close() { + channel.close(); + clientSignalPacketAttachmentMap.clear(); + } +} diff --git a/net/src/main/java/com/zfoo/net/task/ITaskManager.java b/net/src/main/java/com/zfoo/net/task/ITaskManager.java new file mode 100644 index 00000000..55ed1245 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/ITaskManager.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task; + +import com.zfoo.net.task.model.ReceiveTask; + +import java.util.concurrent.ExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ITaskManager { + + void addTask(ReceiveTask task); + + ExecutorService getExecutorByConsistentHash(int hash); +} diff --git a/net/src/main/java/com/zfoo/net/task/TaskManager.java b/net/src/main/java/com/zfoo/net/task/TaskManager.java new file mode 100644 index 00000000..a4a98171 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/TaskManager.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task; + +import com.zfoo.net.NetContext; +import com.zfoo.net.task.model.AbstractTaskDispatch; +import com.zfoo.net.task.model.ITaskDispatch; +import com.zfoo.net.task.model.ReceiveTask; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.math.RandomUtils; +import io.netty.util.concurrent.FastThreadLocalThread; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public final class TaskManager implements ITaskManager { + + private static final Logger logger = LoggerFactory.getLogger(TaskManager.class); + + private static final TaskManager INSTANCE = new TaskManager(); + + // 线程池的大小 + public static final int EXECUTOR_SIZE; + + private static final ITaskDispatch taskDispatch; + + + /** + * 使用不同的线程池,让线程池之间实现隔离,互不影响 + */ + private static final ExecutorService[] executors; + + static { + var localConfig = NetContext.getConfigManager().getLocalConfig(); + var providerConfig = localConfig.getProviderConfig(); + + var dispatch = providerConfig == null ? "consistent-hash" : providerConfig.getDispatch(); + var dispatchThread = (providerConfig == null || StringUtils.isBlank(providerConfig.getDispatchThread())) + ? "default" : providerConfig.getDispatchThread(); + + EXECUTOR_SIZE = "default".equals(dispatchThread) ? (Runtime.getRuntime().availableProcessors() + 1) : Integer.parseInt(dispatchThread); + taskDispatch = AbstractTaskDispatch.valueOf(dispatch); + + executors = new ExecutorService[EXECUTOR_SIZE]; + for (int i = 0; i < executors.length; i++) { + var namedThreadFactory = new TaskThreadFactory(); + executors[i] = Executors.newSingleThreadExecutor(namedThreadFactory); + } + } + + private static class TaskThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + TaskThreadFactory() { + var s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = "task-p" + poolNumber.getAndIncrement() + "-t"; + } + + @Override + public Thread newThread(Runnable runnable) { + var t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(false); + t.setPriority(Thread.NORM_PRIORITY); + t.setUncaughtExceptionHandler((thread, e) -> logger.error(thread.toString(), e)); + return t; + } + } + + private TaskManager() { + } + + public static TaskManager getInstance() { + return INSTANCE; + } + + @Override + public void addTask(ReceiveTask task) { + taskDispatch.getExecutor(task).execute(task); + } + + @Override + public ExecutorService getExecutorByConsistentHash(int executorConsistentHash) { + if (executorConsistentHash >= 0) { + return executors[Math.abs(executorConsistentHash % EXECUTOR_SIZE)]; + } else { + return executors[RandomUtils.randomInt(TaskManager.EXECUTOR_SIZE)]; + } + } +} diff --git a/net/src/main/java/com/zfoo/net/task/model/AbstractTaskDispatch.java b/net/src/main/java/com/zfoo/net/task/model/AbstractTaskDispatch.java new file mode 100644 index 00000000..41ea4de2 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/AbstractTaskDispatch.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task.model; + +import com.zfoo.protocol.util.StringUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AbstractTaskDispatch implements ITaskDispatch { + + public static ITaskDispatch valueOf(String taskDispatchName) { + switch (taskDispatchName) { + case "random": + return new RandomTaskDispatch(); + case "sessionId": + return new SessionIdTaskDispatch(); + case "consistent-hash": + return new ConsistentHashTaskDispatch(); + default: + throw new RuntimeException(StringUtils.format("没有找到对应的taskDispatch[{}]", taskDispatchName)); + } + } + +} diff --git a/net/src/main/java/com/zfoo/net/task/model/ConsistentHashTaskDispatch.java b/net/src/main/java/com/zfoo/net/task/model/ConsistentHashTaskDispatch.java new file mode 100644 index 00000000..683c590b --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/ConsistentHashTaskDispatch.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task.model; + +import com.zfoo.net.task.TaskManager; + +import java.util.concurrent.ExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConsistentHashTaskDispatch extends AbstractTaskDispatch { + + private static ConsistentHashTaskDispatch instance = new ConsistentHashTaskDispatch(); + + public static ConsistentHashTaskDispatch getInstance() { + return instance; + } + + @Override + public ExecutorService getExecutor(ReceiveTask receiveTask) { + var packetAttachment = receiveTask.getPacketAttachment(); + + if (packetAttachment == null) { + return SessionIdTaskDispatch.getInstance().getExecutor(receiveTask); + } + + return TaskManager.getInstance().getExecutorByConsistentHash(packetAttachment.executorConsistentHash()); + } + +} diff --git a/net/src/main/java/com/zfoo/net/task/model/ITaskDispatch.java b/net/src/main/java/com/zfoo/net/task/model/ITaskDispatch.java new file mode 100644 index 00000000..ca8d36ad --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/ITaskDispatch.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task.model; + +import java.util.concurrent.ExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ITaskDispatch { + + ExecutorService getExecutor(ReceiveTask receiveTask); + +} diff --git a/net/src/main/java/com/zfoo/net/task/model/RandomTaskDispatch.java b/net/src/main/java/com/zfoo/net/task/model/RandomTaskDispatch.java new file mode 100644 index 00000000..9c4ea2a0 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/RandomTaskDispatch.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task.model; + +import com.zfoo.net.task.TaskManager; + +import java.util.concurrent.ExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RandomTaskDispatch extends AbstractTaskDispatch { + + private static final RandomTaskDispatch INSTANCE = new RandomTaskDispatch(); + + public static RandomTaskDispatch getInstance() { + return INSTANCE; + } + + @Override + public ExecutorService getExecutor(ReceiveTask receiveTask) { + return TaskManager.getInstance().getExecutorByConsistentHash(-1); + } + +} diff --git a/net/src/main/java/com/zfoo/net/task/model/ReceiveTask.java b/net/src/main/java/com/zfoo/net/task/model/ReceiveTask.java new file mode 100644 index 00000000..0145d967 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/ReceiveTask.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task.model; + +import com.zfoo.net.NetContext; +import com.zfoo.net.packet.model.IPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public final class ReceiveTask implements Runnable { + + private Session session; + private IPacket packet; + private IPacketAttachment packetAttachment; + + public ReceiveTask(Session session, IPacket packet, IPacketAttachment packetAttachment) { + this.session = session; + this.packet = packet; + this.packetAttachment = packetAttachment; + } + + @Override + public void run() { + NetContext.getDispatcher().doReceive(session, packet, packetAttachment); + } + + public Session getSession() { + return session; + } + + public void setSession(Session session) { + this.session = session; + } + + public IPacket getPacket() { + return packet; + } + + public void setPacket(IPacket packet) { + this.packet = packet; + } + + public IPacketAttachment getPacketAttachment() { + return packetAttachment; + } + + public void setPacketAttachment(IPacketAttachment packetAttachment) { + this.packetAttachment = packetAttachment; + } +} diff --git a/net/src/main/java/com/zfoo/net/task/model/SafeRunnable.java b/net/src/main/java/com/zfoo/net/task/model/SafeRunnable.java new file mode 100644 index 00000000..998485e8 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/SafeRunnable.java @@ -0,0 +1,27 @@ +package com.zfoo.net.task.model; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class SafeRunnable implements Runnable { + + private static final Logger logger = LoggerFactory.getLogger(SafeRunnable.class); + + @Override + public void run() { + try { + doRun(); + } catch (Exception e) { + logger.error("未知exception异常", e); + } catch (Throwable t) { + logger.error("未知throwable异常", t); + } + } + + public abstract void doRun(); + +} diff --git a/net/src/main/java/com/zfoo/net/task/model/SessionIdTaskDispatch.java b/net/src/main/java/com/zfoo/net/task/model/SessionIdTaskDispatch.java new file mode 100644 index 00000000..83b05413 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/task/model/SessionIdTaskDispatch.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.task.model; + +import com.zfoo.net.task.TaskManager; +import com.zfoo.util.math.HashUtils; + +import java.util.concurrent.ExecutorService; + +/** + * 同一个session总是分配到同一个线程池执行 + * + * @author jaysunxiao + * @version 3.0 + */ +public class SessionIdTaskDispatch extends AbstractTaskDispatch { + + private static final SessionIdTaskDispatch INSTANCE = new SessionIdTaskDispatch(); + + public static SessionIdTaskDispatch getInstance() { + return INSTANCE; + } + + @Override + public ExecutorService getExecutor(ReceiveTask receiveTask) { + var session = receiveTask.getSession(); + return TaskManager.getInstance().getExecutorByConsistentHash(HashUtils.fnvHash(session.getSid())); + } + +} diff --git a/net/src/main/java/com/zfoo/net/util/SessionUtils.java b/net/src/main/java/com/zfoo/net/util/SessionUtils.java new file mode 100644 index 00000000..68bab9a7 --- /dev/null +++ b/net/src/main/java/com/zfoo/net/util/SessionUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.util; + +import com.zfoo.net.session.model.AttributeType; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.StringUtils; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + +import static com.zfoo.net.handler.BaseDispatcherHandler.SESSION_KEY; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class SessionUtils { + + private static final String CHANNEL_INFO_TEMPLATE = "[ip:{}][sid:{}][uid:{}]"; + + + public static boolean isActive(Session session) { + return session != null && session.getChannel().isActive(); + } + + public static boolean isActive(Channel session) { + return session != null && session.isActive(); + } + + public static Session getSession(ChannelHandlerContext ctx) { + var sessionAttr = ctx.channel().attr(SESSION_KEY); + return sessionAttr.get(); + } + + public static String sessionInfo(ChannelHandlerContext ctx) { + var session = SessionUtils.getSession(ctx); + if (session == null) { + return StringUtils.format(CHANNEL_INFO_TEMPLATE, ctx.channel()); + } + return sessionInfo(session); + } + + public static String sessionInfo(Session session) { + if (session == null) { + return CHANNEL_INFO_TEMPLATE; + } + var remoteAddress = session.getAttribute(AttributeType.CHANNEL_REMOTE_ADDRESS); + return StringUtils.format(CHANNEL_INFO_TEMPLATE, remoteAddress, session.getSid(), session.getAttribute(AttributeType.UID)); + } + +} diff --git a/net/src/main/java/com/zfoo/net/util/SimpleCache.java b/net/src/main/java/com/zfoo/net/util/SimpleCache.java new file mode 100644 index 00000000..923cd1aa --- /dev/null +++ b/net/src/main/java/com/zfoo/net/util/SimpleCache.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.util; + +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import com.zfoo.event.manager.EventBus; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.model.Pair; +import com.zfoo.scheduler.manager.SchedulerManager; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +/** + * guava的缓存都是单个存,单个更新,但是实际的项目很多都是批量查询,批量更新。 + *

+ * 优势: + * 1.支持批量查找,批量更新。 + * 2.可以防止缓存穿透,缓存击穿,缓存雪崩 + *

+ * 当查找的key不存在的时候,返回defaultValue,并且将defaultValue放入缓存。所以通过 + * + * @author jaysunxiao + * @version 3.0 + */ +public class SimpleCache { + + /** + * 每次最多更新1000条数据 + */ + private static final int BATCH_RELOAD_SIZE = 1000; + + private LoadingCache cache; + private ConcurrentLinkedQueue linkedQueue; + + private long expiredAccessDuration; + private long refreshDuration; + private Function, List>> batchReloadCallback; + private Function defaultValueBuilder; + + /** + * @param expiredAccessDuration 访问过期时间,毫秒;通常情况下,这个值比refreshDuration大会得到更好的缓存效果,一般是2倍 + * @param refreshDuration 刷新实际那,毫秒;因为是通过后台scheduler去更新缓存,所以更新的refresh可能是这个值的2倍 + * @param maxSize 缓存大小 + * @param batchReloadCallback 一组key取value的回调方法 + * @param defaultValueBuilder 默认值构建 + * @return 简单的缓存 + */ + public static SimpleCache build(long expiredAccessDuration, long refreshDuration, long maxSize + , Function, List>> batchReloadCallback + , Function defaultValueBuilder) { + + var linkedQueue = new ConcurrentLinkedQueue(); + + // 没有用guava的expireAfterWrite的原因是容易造成缓存击穿 + var cache = Caffeine.newBuilder() + .expireAfterAccess(expiredAccessDuration, TimeUnit.MILLISECONDS) + .refreshAfterWrite(refreshDuration, TimeUnit.MILLISECONDS) + .maximumSize(maxSize) + .recordStats() + .build(new CacheLoader() { + @Override + public @Nullable V load(@NonNull K key) { + // 因为通过SimpleCache封装过后,上层逻辑是不会调用guava的cache的get方法,所以理论上load不会执行 + var resultList = batchReloadCallback.apply(List.of(key)); + return CollectionUtils.isEmpty(resultList) ? defaultValueBuilder.apply(key) : resultList.get(0).getValue(); + } + + @Override + public @Nullable V reload(@NonNull K key, @NonNull V oldValue) { + linkedQueue.offer(key); + // 先返回老的值,等周期任务刷新新的值 + return oldValue; + } + }); + + + SchedulerManager.getInstance().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + // 不在任务调度线程中执行耗时任务,因为任务调度线程只有一个线程池 + EventBus.asyncExecute().execute(new Runnable() { + @Override + public void run() { + var list = new ArrayList(); + while (!linkedQueue.isEmpty()) { + var key = linkedQueue.poll(); + list.add(key); + if (list.size() >= BATCH_RELOAD_SIZE) { + var result = batchReloadCallback.apply(list); + result.forEach(it -> cache.put(it.getKey(), it.getValue())); + list.clear(); + } + } + + if (CollectionUtils.isNotEmpty(list)) { + var result = batchReloadCallback.apply(list); + result.forEach(it -> cache.put(it.getKey(), it.getValue())); + } + } + }); + } + }, refreshDuration, TimeUnit.MILLISECONDS); + + + var simpleCache = new SimpleCache(); + simpleCache.cache = cache; + simpleCache.linkedQueue = linkedQueue; + simpleCache.expiredAccessDuration = expiredAccessDuration; + simpleCache.refreshDuration = refreshDuration; + simpleCache.batchReloadCallback = batchReloadCallback; + simpleCache.defaultValueBuilder = defaultValueBuilder; + return simpleCache; + } + + /** + * 单个查找 + */ + public V get(K k) { + try { + return cache.get(k); + } catch (Exception e) { + var defaultValue = defaultValueBuilder.apply(k); + cache.put(k, defaultValue); + return defaultValue; + } + } + + /** + * 异步刷新缓存 + */ + public void invalidate(K k) { + cache.invalidate(k); + } + + public void put(K k, V v) { + cache.put(k, v); + } + + /** + * 批量查找 + */ + public Map batchGet(Collection list) { + if (CollectionUtils.isEmpty(list)) { + return Collections.emptyMap(); + } + var result = new HashMap(); + + var notPresentIdSet = new HashSet(); + for (var id : list) { + var value = cache.getIfPresent(id); + if (value != null) { + result.put(id, value); + } else { + notPresentIdSet.add(id); + } + } + + if (CollectionUtils.isNotEmpty(notPresentIdSet)) { + batchReloadCallback.apply(new ArrayList<>(notPresentIdSet)) + .forEach(it -> { + result.put(it.getKey(), it.getValue()); + cache.put(it.getKey(), it.getValue()); + notPresentIdSet.remove(it.getKey()); + }); + } + + // 防止缓存穿透,将数据库查询不到的键存入到缓存,value给一个默认值 + if (CollectionUtils.isNotEmpty(notPresentIdSet)) { + notPresentIdSet.forEach(it -> { + var defaultValue = defaultValueBuilder.apply(it); + result.put(it, defaultValue); + cache.put(it, defaultValue); + }); + } + + return result; + } + +} diff --git a/net/src/main/java/com/zfoo/net/util/SingleCache.java b/net/src/main/java/com/zfoo/net/util/SingleCache.java new file mode 100644 index 00000000..95ae4fde --- /dev/null +++ b/net/src/main/java/com/zfoo/net/util/SingleCache.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.util; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.scheduler.util.TimeUtils; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Supplier; + +/** + * 单值缓存,会隔一段时间在后台刷新一下缓存 + * + * @author jaysunxiao + * @version 3.0 + */ +public class SingleCache { + + private long refreshDuration; + + private Supplier supplier; + + + private volatile V cache; + private volatile long refreshTime; + private Lock lock = new ReentrantLock(); + + + /** + * @param refreshDuration 刷新实际那,毫秒 + * @param supplier 缓存提供者 + * @return 简单的缓存 + */ + public static SingleCache build(long refreshDuration, Supplier supplier) { + var cache = new SingleCache(); + cache.refreshDuration = refreshDuration; + cache.supplier = supplier; + cache.cache = supplier.get(); + cache.refreshTime = TimeUtils.now() + refreshDuration; + return cache; + } + + + public V get() { + var now = TimeUtils.now(); + // 使用双重检测锁的方式 + if (now > refreshTime) { + lock.lock(); + try { + if (now > refreshTime) { + refreshTime = now + refreshDuration; + EventBus.asyncExecute().execute(new Runnable() { + @Override + public void run() { + cache = supplier.get(); + } + }); + } + } finally { + lock.unlock(); + } + } + return cache; + } + +} diff --git a/net/src/main/resources/META-INF/spring.handlers b/net/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..332ca81a --- /dev/null +++ b/net/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/net=com.zfoo.net.schema.NamespaceHandler \ No newline at end of file diff --git a/net/src/main/resources/META-INF/spring.schemas b/net/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..e6fa789f --- /dev/null +++ b/net/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/net-1.0.xsd=net-1.0.xsd diff --git a/net/src/main/resources/net-1.0.xsd b/net/src/main/resources/net-1.0.xsd new file mode 100644 index 00000000..89a1056c --- /dev/null +++ b/net/src/main/resources/net-1.0.xsd @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/java/com/zfoo/net/config/RegistryTest.java b/net/src/test/java/com/zfoo/net/config/RegistryTest.java new file mode 100644 index 00000000..0b8a6f43 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/config/RegistryTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.config; + +import com.zfoo.net.config.model.ConsumerConfig; +import com.zfoo.net.config.model.ProviderConfig; +import com.zfoo.net.consumer.registry.RegisterVO; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.util.net.HostAndPort; +import io.netty.util.NetUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RegistryTest { + + + @Test + public void registerVoTest() { + var modules = List.of(new ProtocolModule((byte) 100, "aaa", "1.0.0") + , new ProtocolModule((byte) 120, "bbb", "1.0.0")); + var providerConfig = ProviderConfig.valueOf(HostAndPort.valueOf("127.0.0.1", 80).toHostAndPortStr(), modules); + var consumerConfig = ConsumerConfig.valueOf(modules); + + var vo = RegisterVO.valueOf("test", providerConfig, consumerConfig); + var voStr = vo.toString(); + System.out.println(voStr); + var newVo = RegisterVO.parseString(voStr); + Assert.assertEquals(vo, newVo); + + System.out.println(NetUtil.LOCALHOST); + System.out.println(NetUtil.LOCALHOST4); + System.out.println(NetUtil.LOCALHOST6); + System.out.println(NetUtil.SOMAXCONN); + System.out.println(NetUtil.LOOPBACK_IF); + } + + @Test + public void moduleTest() { + var moduleId = (byte) 1; + var str = "1.0.0"; + var module = new ProtocolModule(moduleId, "", str); + + str = "0.0.0"; + module = new ProtocolModule(moduleId, "", str); + + str = "10.0.0"; + module = new ProtocolModule(moduleId, "", str); + + str = "10.11.10"; + module = new ProtocolModule(moduleId, "", str); + + str = "10.11.101"; + module = new ProtocolModule(moduleId, "", str); + + str = "99.99.999"; + module = new ProtocolModule(moduleId, "", str); + + module = null; + + str = "1.00.001"; + try { + module = new ProtocolModule(moduleId, "", str); + } catch (Exception e) { + } + Assert.assertNull(module); + + str = "1.00.111"; + try { + module = new ProtocolModule(moduleId, "", str); + } catch (Exception e) { + } + Assert.assertNull(module); + + str = "1.001.0"; + try { + module = new ProtocolModule(moduleId, "", str); + } catch (Exception e) { + } + Assert.assertNull(module); + + str = "99.999.999"; + try { + module = new ProtocolModule(moduleId, "", str); + } catch (Exception e) { + } + Assert.assertNotNull(module); + + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/csharp/ServerPacketController.java b/net/src/test/java/com/zfoo/net/core/csharp/ServerPacketController.java new file mode 100644 index 00000000..bf7877da --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/csharp/ServerPacketController.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.csharp; + +import com.zfoo.net.NetContext; +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.packet.csharp.CM_CSharpRequest; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class ServerPacketController { + + @PacketReceiver + public void atCM_CSharpRequest(Session session, CM_CSharpRequest cm) { + System.out.println("receive packet from client:"); + System.out.println(JsonUtils.object2String(cm)); + + NetContext.getDispatcher().send(session, cm); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/csharp/ServerTest.java b/net/src/test/java/com/zfoo/net/core/csharp/ServerTest.java new file mode 100644 index 00000000..52bba27d --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/csharp/ServerTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.csharp; + +import com.zfoo.net.NetContext; +import com.zfoo.net.core.tcp.TcpServer; +import com.zfoo.net.session.SessionUtils; +import com.zfoo.util.ThreadUtils; +import com.zfoo.util.net.HostAndPort; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class ServerTest { + + private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("server_config.xml"); + + @Test + public void startServer() { + SessionUtils.printSessionInfo(); +// startClient1(); + + var server0 = new TcpServer(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0"))); + server0.start(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + +} diff --git a/net/src/test/java/com/zfoo/net/core/gateway/GatewayTest.java b/net/src/test/java/com/zfoo/net/core/gateway/GatewayTest.java new file mode 100644 index 00000000..518fbb1e --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/gateway/GatewayTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway; + +import com.zfoo.net.NetContext; +import com.zfoo.net.core.tcp.TcpClient; +import com.zfoo.net.packet.gateway.CM_GatewayProvider; +import com.zfoo.net.packet.gateway.SM_GatewayProvider; +import com.zfoo.net.session.SessionUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.util.ThreadUtils; +import com.zfoo.util.net.HostAndPort; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.Executors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class GatewayTest { + + /** + * 启动zookeeper,依次运行下面的测试方法启动服务提供者,网关,然后运行clientTest,消息会通过网关转发到服务提供者 + */ + @Test + public void startProvider0() { + var context = new ClassPathXmlApplicationContext("provider/provider_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startProvider1() { + var context = new ClassPathXmlApplicationContext("provider/provider_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startProvider2() { + var context = new ClassPathXmlApplicationContext("provider/provider_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startGateway() { + var context = new ClassPathXmlApplicationContext("gateway/gateway_consistent_session_config.xml"); + SessionUtils.printSessionInfo(); + var gatewayServer = new GatewayServer(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0")), null); + gatewayServer.start(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void clientTest() { + var context = new ClassPathXmlApplicationContext("gateway/gateway_client_config.xml"); + var executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + SessionUtils.printSessionInfo(); + var client = new TcpClient(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0"))); + var session = client.start(); + + for (int i = 0; i <= 100; i++) { + int finalI = i; + var thread = new Thread(() -> { + var cm = new CM_GatewayProvider(); + cm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + cm.setB(finalI); + try { + System.out.println("客户端发送消息:" + JsonUtils.object2String(cm)); + var sm = NetContext.getDispatcher().syncAsk(session, cm, SM_GatewayProvider.class, null).packet(); + System.out.println("客户端收到消息:" + JsonUtils.object2String(sm)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + executor.execute(thread); + } + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/gateway/controller/GatewayProviderController.java b/net/src/test/java/com/zfoo/net/core/gateway/controller/GatewayProviderController.java new file mode 100644 index 00000000..fdf59be0 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/gateway/controller/GatewayProviderController.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.gateway.controller; + +import com.zfoo.net.NetContext; +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.packet.gateway.CM_GatewayProvider; +import com.zfoo.net.packet.gateway.SM_GatewayProvider; +import com.zfoo.net.packet.model.GatewayPacketAttachment; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class GatewayProviderController { + + @PacketReceiver + public void atCM_GatewayProvider(Session session, CM_GatewayProvider cm, GatewayPacketAttachment gatewayAttachment) { + System.out.println("服务器收到消息:" + JsonUtils.object2String(cm)); + + var sm = new SM_GatewayProvider(); + sm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + sm.setB(cm.getB()); + + System.out.println("服务器返回消息:" + JsonUtils.object2String(sm)); + NetContext.getDispatcher().send(session, sm, gatewayAttachment); + } +} diff --git a/net/src/test/java/com/zfoo/net/core/provider/ProviderTest.java b/net/src/test/java/com/zfoo/net/core/provider/ProviderTest.java new file mode 100644 index 00000000..ad2bd328 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/provider/ProviderTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.provider; + +import com.zfoo.net.NetContext; +import com.zfoo.net.packet.provider.CM_Provider; +import com.zfoo.net.packet.provider.SM_Provider; +import com.zfoo.net.session.SessionUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class ProviderTest { + + /** + * RPC教程: + * 1.首先必须保证启动zookeeper + * 2.启动服务提供者,startProvider0,startProvider1,startProvider2 + * 3.启动服务消费者,startSyncRandomConsumer,startAsyncRandomConsumer,startConsistentSessionConsumer,startShortestTimeConsumer + * 4.每个消费者都是通过不同的策略消费,注意区别 + */ + @Test + public void startProvider0() { + var context = new ClassPathXmlApplicationContext("provider/provider_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startProvider1() { + var context = new ClassPathXmlApplicationContext("provider/provider_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startProvider2() { + var context = new ClassPathXmlApplicationContext("provider/provider_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + /** + * 随机消费,同步请求的方式 + */ + @Test + public void startSyncRandomConsumer() { + var context = new ClassPathXmlApplicationContext("provider/consumer_random_config.xml"); + SessionUtils.printSessionInfo(); + + for (int i = 0; i < 1000; i++) { + ThreadUtils.sleep(3000); + var cm = new CM_Provider(); + cm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + try { + System.out.println("客户端发送消息:" + JsonUtils.object2String(cm)); + var sm = NetContext.getConsumer().syncAsk(cm, SM_Provider.class, null).packet(); + System.out.println("客户端收到消息:" + JsonUtils.object2String(sm)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + /** + * 随机消费,异步请求的方式 + */ + @Test + public void startAsyncRandomConsumer() { + var context = new ClassPathXmlApplicationContext("provider/consumer_random_config.xml"); + SessionUtils.printSessionInfo(); + + for (int i = 0; i < 1000; i++) { + ThreadUtils.sleep(3000); + var cm = new CM_Provider(); + cm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + System.out.println("客户端发送消息:" + JsonUtils.object2String(cm)); + NetContext.getConsumer().asyncAsk(cm, SM_Provider.class, null).whenComplete(sm -> { + System.out.println("客户端收到消息:" + JsonUtils.object2String(sm)); + }); + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + /** + * 一致性hash算法消费方式 + */ + @Test + public void startConsistentSessionConsumer() { + var context = new ClassPathXmlApplicationContext("provider/consumer_consistent_session_config.xml"); + SessionUtils.printSessionInfo(); + ThreadUtils.sleep(3000); + for (int i = 0; i < 1000; i++) { + ThreadUtils.sleep(3000); + var cm = new CM_Provider(); + cm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + System.out.println("客户端发送消息:" + JsonUtils.object2String(cm)); + NetContext.getConsumer().asyncAsk(cm, SM_Provider.class, 100).whenComplete(sm -> { + System.out.println("客户端收到消息:" + JsonUtils.object2String(sm)); + }); + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + /** + * 最短时间的消费方式 + */ + @Test + public void startShortestTimeConsumer() { + var context = new ClassPathXmlApplicationContext("provider/consumer_shortest_time_config.xml"); + SessionUtils.printSessionInfo(); + for (int i = 0; i < 1000; i++) { + ThreadUtils.sleep(3000); + var cm = new CM_Provider(); + cm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + System.out.println("客户端发送消息:" + JsonUtils.object2String(cm)); + NetContext.getConsumer().asyncAsk(cm, SM_Provider.class, null).whenComplete(sm -> { + System.out.println("客户端收到消息:" + JsonUtils.object2String(sm)); + }); + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + +} diff --git a/net/src/test/java/com/zfoo/net/core/provider/controller/ProviderController.java b/net/src/test/java/com/zfoo/net/core/provider/controller/ProviderController.java new file mode 100644 index 00000000..4d565611 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/provider/controller/ProviderController.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.provider.controller; + +import com.zfoo.net.NetContext; +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.packet.provider.CM_Provider; +import com.zfoo.net.packet.provider.SM_Provider; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class ProviderController { + + @PacketReceiver + public void atCM_Provider(Session session, CM_Provider cm) { + System.out.println("服务器收到消息:" + JsonUtils.object2String(cm)); + + var sm = new SM_Provider(); + sm.setA(NetContext.getConfigManager().getLocalConfig().toLocalRegisterVO().toString()); + sm.setB(cm.getB()); + + System.out.println("服务器返回消息:" + JsonUtils.object2String(sm)); + NetContext.getDispatcher().send(session, sm); + } +} diff --git a/net/src/test/java/com/zfoo/net/core/tcp/client/TcpClientTest.java b/net/src/test/java/com/zfoo/net/core/tcp/client/TcpClientTest.java new file mode 100644 index 00000000..7c07518d --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/tcp/client/TcpClientTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp.client; + +import com.zfoo.net.NetContext; +import com.zfoo.net.core.tcp.TcpClient; +import com.zfoo.net.packet.*; +import com.zfoo.net.session.SessionUtils; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.ThreadUtils; +import com.zfoo.util.net.HostAndPort; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class TcpClientTest { + private static final Logger logger = LoggerFactory.getLogger(TcpClientTest.class); + private static final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); + + @Test + public void startClientTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("client_config.xml"); + SessionUtils.printSessionInfo(); + var client = new TcpClient(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0"))); + var session = client.start(); + + new Thread(() -> { + CM_Int cm = new CM_Int(); + cm.setFlag(false); + cm.setA(Byte.MIN_VALUE); + cm.setB(Short.MIN_VALUE); + cm.setC(Integer.MIN_VALUE); + cm.setD(Long.MIN_VALUE); + cm.setE('e'); + cm.setF("Hello,this is the client!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + + for (int i = 0; i < 1000; i++) { + ThreadUtils.sleep(2000); + NetContext.getDispatcher().send(session, cm); + } + }).start(); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + + @Test + public void syncClientTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("client_config.xml"); + + SessionUtils.printSessionInfo(); + var client = new TcpClient(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server1"))); + var session = client.start(); + + for (int i = 0; i < 10000; i++) { + int it = i; + var thread = new Thread(() -> { + try { + CM_SyncMess cm = new CM_SyncMess(); + cm.setA("Hello, this is client!"); + cm.setId(it); + SM_SyncMess sm = NetContext.getDispatcher().syncAsk(session, cm, SM_SyncMess.class, null).packet(); + var info = StringUtils.MULTIPLE_HYPHENS + FileUtils.LS + + JsonUtils.object2String(cm) + FileUtils.LS + + JsonUtils.object2String(sm) + FileUtils.LS; + System.out.println(info); + } catch (Exception e) { + logger.error(ExceptionUtils.getMessage(e)); + } + }); + executor.execute(thread); + } + ThreadUtils.sleep(Long.MAX_VALUE); + } + + + @Test + public void asyncClientTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("client_config.xml"); + var client = new TcpClient(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0"))); + var session = client.start(); + + for (int i = 0; i < 10000; i++) { + Thread thread = new Thread(() -> { + try { + CM_AsyncMess0 cm = new CM_AsyncMess0(); + cm.setA("Hello, client0 -> server0!"); + + var asyncResponse = NetContext.getDispatcher().asyncAsk(session, cm, SM_AsyncMess0.class, null); + asyncResponse.whenComplete(sm -> { + var info = StringUtils.MULTIPLE_HYPHENS + FileUtils.LS + + JsonUtils.object2String(cm) + FileUtils.LS + + JsonUtils.object2String(sm) + FileUtils.LS; + System.out.println(info); + } + ); + } catch (Exception e) { + e.printStackTrace(); + } + }); + executor.submit(thread); + } + SessionUtils.printSessionInfo(); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/tcp/client/controller/TcpClientPacketController.java b/net/src/test/java/com/zfoo/net/core/tcp/client/controller/TcpClientPacketController.java new file mode 100644 index 00000000..b6cd262e --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/tcp/client/controller/TcpClientPacketController.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp.client.controller; + +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.packet.SM_Int; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class TcpClientPacketController { + + + @PacketReceiver + public void atSM_Int(Session session, SM_Int sm) { + System.out.println("receive packet from server:"); + System.out.println(JsonUtils.object2String(sm)); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/tcp/server/TcpServerTest.java b/net/src/test/java/com/zfoo/net/core/tcp/server/TcpServerTest.java new file mode 100644 index 00000000..96052d04 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/tcp/server/TcpServerTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp.server; + +import com.zfoo.net.NetContext; +import com.zfoo.net.core.tcp.TcpClient; +import com.zfoo.net.core.tcp.TcpServer; +import com.zfoo.net.session.SessionUtils; +import com.zfoo.util.ThreadUtils; +import com.zfoo.util.net.HostAndPort; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class TcpServerTest { + + private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("server_config.xml"); + private static final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + /** + * 单机服务器教程,启动成功过后在com.zfoo.net.core.tcp.client.TcpClientTest中运行startClientTest + *

+ * startClientTest连接服务器成功过后,会不断的发消息给服务器 + */ + @Test + public void startServer0() { + SessionUtils.printSessionInfo(); + + var server0 = new TcpServer(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0"))); + server0.start(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startServer1() { + SessionUtils.printSessionInfo(); + + connectServer1(); + + var server1 = new TcpServer(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server1"))); + server1.start(); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + private void connectServer1() { + executor.execute(() -> { + while (true) { + try { + var client1 = new TcpClient(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("client0"))); + client1.start(); + break; + } catch (Exception e) { + System.out.println("连接失败,开始重新连接"); + ThreadUtils.sleep(3000); + e.printStackTrace(); + } + } + }); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/tcp/server/controller/TcpServerPacketController.java b/net/src/test/java/com/zfoo/net/core/tcp/server/controller/TcpServerPacketController.java new file mode 100644 index 00000000..1a212a8c --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/tcp/server/controller/TcpServerPacketController.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.tcp.server.controller; + +import com.zfoo.net.NetContext; +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.packet.*; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class TcpServerPacketController { + + @PacketReceiver + public void atCM_Int(Session session, CM_Int cm) { + System.out.println("receive packet from client:"); + System.out.println(JsonUtils.object2String(cm)); + + SM_Int sm = new SM_Int(); + sm.setFlag(false); + sm.setA(Byte.MIN_VALUE); + sm.setB(Short.MIN_VALUE); + sm.setC(Integer.MIN_VALUE); + sm.setD(Long.MIN_VALUE); + sm.setE('e'); + sm.setF("Hello,this is the Server!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + NetContext.getDispatcher().send(session, sm); + } + + @PacketReceiver + public void atCM_SyncMess(Session session, CM_SyncMess cm) { + // 测试超时 + // ThreadUtils.sleep(Integer.MAX_VALUE); + + // 测试正常返回 + SM_SyncMess sm = new SM_SyncMess(); + sm.setA("Hello, this is server!"); + sm.setId(cm.getId()); + + // 测试返回不是预期的消息 + // SM_Int sm = new SM_Int(); + + // 测试错误返回 + // var sm = ErrorResponse.valueOf(1, 1, "this is error response"); + + + NetContext.getDispatcher().send(session, sm); + + var info = StringUtils.MULTIPLE_HYPHENS + FileUtils.LS + + JsonUtils.object2String(cm) + FileUtils.LS + + JsonUtils.object2String(sm) + FileUtils.LS; + + System.out.println(info); + } + + + // client0->server0->server1->server0->client0 + @PacketReceiver + public void atCM_AsyncMess0(Session session, CM_AsyncMess0 cm0) { + CM_AsyncMess1 cm1 = new CM_AsyncMess1(); + cm1.setA("Hello, server0 -> server1"); + + var server1 = NetContext.getSessionManager().getClientSession(0L); + NetContext.getDispatcher().asyncAsk(server1, cm1, SM_AsyncMess1.class, null) + .whenComplete(sm_asyncMess0 -> { + + SM_AsyncMess0 sm = new SM_AsyncMess0(); + sm.setA("Hello, server0 -> client0!"); + + NetContext.getDispatcher().send(session, sm); + var info = StringUtils.MULTIPLE_HYPHENS + FileUtils.LS + + JsonUtils.object2String(cm0) + FileUtils.LS + + JsonUtils.object2String(sm) + FileUtils.LS; + + System.out.println(info); + + }); + } + + @PacketReceiver + public void atCM_AsyncMess1(Session session, CM_AsyncMess1 cm) { + // 测试超时 + // ThreadUtils.sleep(Integer.MAX_VALUE); + + // 测试正常返回 + SM_AsyncMess1 sm = new SM_AsyncMess1(); + sm.setA("Hello, server1 -> server0!"); + + // 测试返回不是预期的消息 + // SM_Int sm = new SM_Int(); + + // 测试错误返回 + // var sm = ErrorResponse.valueOf(1, 1, "this is error response"); + + NetContext.getDispatcher().send(session, sm); + + var info = StringUtils.MULTIPLE_HYPHENS + FileUtils.LS + + JsonUtils.object2String(cm) + FileUtils.LS + + JsonUtils.object2String(sm) + FileUtils.LS; + + System.out.println(info); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/websocket/client/client.html b/net/src/test/java/com/zfoo/net/core/websocket/client/client.html new file mode 100644 index 00000000..9ae293a0 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/websocket/client/client.html @@ -0,0 +1,24 @@ + + + + + + + + websocket client test + + +

请使用zweb测试

+ + diff --git a/net/src/test/java/com/zfoo/net/core/websocket/server/WebsocketServerTest.java b/net/src/test/java/com/zfoo/net/core/websocket/server/WebsocketServerTest.java new file mode 100644 index 00000000..e1783cde --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/websocket/server/WebsocketServerTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.websocket.server; + +import com.zfoo.net.NetContext; +import com.zfoo.net.core.websocket.WebsocketServer; +import com.zfoo.util.ThreadUtils; +import com.zfoo.util.net.HostAndPort; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class WebsocketServerTest { + + private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("server_config.xml"); + private static final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + + @Test + public void startServer() { + var server = new WebsocketServer(HostAndPort.valueOf(NetContext.getConfigManager().getLocalConfig().getHostConfig().getAddressMap().get("server0"))); + server.start(); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/net/src/test/java/com/zfoo/net/core/websocket/server/controller/WebsocketPacketController.java b/net/src/test/java/com/zfoo/net/core/websocket/server/controller/WebsocketPacketController.java new file mode 100644 index 00000000..7ea77fea --- /dev/null +++ b/net/src/test/java/com/zfoo/net/core/websocket/server/controller/WebsocketPacketController.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.core.websocket.server.controller; + +import com.zfoo.net.NetContext; +import com.zfoo.net.dispatcher.model.anno.PacketReceiver; +import com.zfoo.net.packet.websocket.CM_WebSocketPacket; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class WebsocketPacketController { + + @PacketReceiver + public void atCM_WebSocketPacket(Session session, CM_WebSocketPacket cm) { + System.out.println("receive packet from client:"); + System.out.println(JsonUtils.object2String(cm)); + + + NetContext.getDispatcher().send(session, cm); + } + +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_Array.java b/net/src/test/java/com/zfoo/net/packet/CM_Array.java new file mode 100644 index 00000000..d1b1abcb --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_Array.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Arrays; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Array implements IPacket { + + public static final transient short PROTOCOL_ID = 1119; + + private int[] a; + + private ObjectA[] b; + + + @Override + public String toString() { + return "CM_Array{" + "a=" + Arrays.toString(a) + ", b=" + Arrays.toString(b) + '}'; + } + + public int[] getA() { + return a; + } + + public void setA(int[] a) { + this.a = a; + } + + public ObjectA[] getB() { + return b; + } + + public void setB(ObjectA[] b) { + this.b = b; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_Array cm_array = (CM_Array) o; + return Arrays.equals(a, cm_array.a) && Arrays.equals(b, cm_array.b); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(a); + result = 31 * result + Arrays.hashCode(b); + return result; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_AsyncMess0.java b/net/src/test/java/com/zfoo/net/packet/CM_AsyncMess0.java new file mode 100644 index 00000000..8135d9d4 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_AsyncMess0.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_AsyncMess0 implements IPacket { + + public static final transient short PROTOCOL_ID = 1152; + + private String a; + + private int id; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_AsyncMess1.java b/net/src/test/java/com/zfoo/net/packet/CM_AsyncMess1.java new file mode 100644 index 00000000..6081ba04 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_AsyncMess1.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_AsyncMess1 implements IPacket { + + public static final transient short PROTOCOL_ID = 1154; + + private String a; + + private int id; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_Float.java b/net/src/test/java/com/zfoo/net/packet/CM_Float.java new file mode 100644 index 00000000..b8185bc6 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_Float.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Float implements IPacket { + + public static final transient short PROTOCOL_ID = 1112; + + private float a; + + private Float b; + + private double c; + + private Double d; + + @Override + public String toString() { + return "CM_Float{" + "a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + '}'; + } + + public float getA() { + return a; + } + + public void setA(float a) { + this.a = a; + } + + public Float getB() { + return b; + } + + public void setB(Float b) { + this.b = b; + } + + public double getC() { + return c; + } + + public void setC(double c) { + this.c = c; + } + + public Double getD() { + return d; + } + + public void setD(Double d) { + this.d = d; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_Float cm_float = (CM_Float) o; + return Float.compare(cm_float.a, a) == 0 && + Double.compare(cm_float.c, c) == 0 && + Objects.equals(b, cm_float.b) && + Objects.equals(d, cm_float.d); + } + + @Override + public int hashCode() { + + return Objects.hash(a, b, c, d); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_Int.java b/net/src/test/java/com/zfoo/net/packet/CM_Int.java new file mode 100644 index 00000000..8c4027fd --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_Int.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Int implements IPacket { + + public static final transient short PROTOCOL_ID = 1110; + + private boolean flag; + + private byte a; + + private short b; + + private int c; + + private long d; + + private char e; + + private String f; + + public String getF() { + return f; + } + + public void setF(String f) { + this.f = f; + } + + public boolean isFlag() { + return flag; + } + + public void setFlag(boolean flag) { + this.flag = flag; + } + + public byte getA() { + return a; + } + + public char getE() { + return e; + } + + public void setE(char e) { + this.e = e; + } + + public void setA(byte a) { + this.a = a; + } + + public short getB() { + return b; + } + + public void setB(short b) { + this.b = b; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public long getD() { + return d; + } + + public void setD(long d) { + this.d = d; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_Int cm_int = (CM_Int) o; + return flag == cm_int.flag && + a == cm_int.a && + b == cm_int.b && + c == cm_int.c && + d == cm_int.d && + e == cm_int.e && + f.equals(cm_int.f); + } + + @Override + public int hashCode() { + return Objects.hash(flag, a, b, c, d, e, f); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_List.java b/net/src/test/java/com/zfoo/net/packet/CM_List.java new file mode 100644 index 00000000..c7e3fa2c --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_List.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_List implements IPacket { + + public static final transient short PROTOCOL_ID = 1118; + + private List list; + + private List> listWitList; + + private List objs; + + private List>> listWithObject; + + private List> listWithMap; + + private List>> listListWithMap; + + + public List getObjs() { + return objs; + } + + public void setObjs(List objs) { + this.objs = objs; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public static short getProtocolId() { + return PROTOCOL_ID; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public List> getListWitList() { + return listWitList; + } + + public void setListWitList(List> listWitList) { + this.listWitList = listWitList; + } + + public List>> getListWithObject() { + return listWithObject; + } + + public void setListWithObject(List>> listWithObject) { + this.listWithObject = listWithObject; + } + + public List> getListWithMap() { + return listWithMap; + } + + public void setListWithMap(List> listWithMap) { + this.listWithMap = listWithMap; + } + + public List>> getListListWithMap() { + return listListWithMap; + } + + public void setListListWithMap(List>> listListWithMap) { + this.listListWithMap = listListWithMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_List cm_list = (CM_List) o; + return Objects.equals(list, cm_list.list) && + Objects.equals(listWitList, cm_list.listWitList) && + Objects.equals(objs, cm_list.objs) && + Objects.equals(listWithObject, cm_list.listWithObject) && + Objects.equals(listWithMap, cm_list.listWithMap) && + Objects.equals(listListWithMap, cm_list.listListWithMap); + } + + @Override + public int hashCode() { + return Objects.hash(list, listWitList, objs, listWithObject, listWithMap, listListWithMap); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_Map.java b/net/src/test/java/com/zfoo/net/packet/CM_Map.java new file mode 100644 index 00000000..8415a666 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_Map.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Map implements IPacket { + + public static final transient short PROTOCOL_ID = 1120; + + private Map map; + + private Map mapA; + + private Map mapB; + + private Map mapC; + + private Map, Map, ObjectA>> mapWithListAndMap; + + public static short getProtocolId() { + return PROTOCOL_ID; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + @Override + public String toString() { + return "CM_Map{" + "map=" + map + ", mapA=" + mapA + ", mapB=" + mapB + ", mapC=" + mapC + '}'; + } + + public Map getMapA() { + return mapA; + } + + public void setMapA(Map mapA) { + this.mapA = mapA; + } + + public Map getMapB() { + return mapB; + } + + public void setMapB(Map mapB) { + this.mapB = mapB; + } + + public Map getMapC() { + return mapC; + } + + public void setMapC(Map mapC) { + this.mapC = mapC; + } + + public Map, Map, ObjectA>> getMapWithListAndMap() { + return mapWithListAndMap; + } + + public void setMapWithListAndMap(Map, Map, ObjectA>> mapWithListAndMap) { + this.mapWithListAndMap = mapWithListAndMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_Map cm_map = (CM_Map) o; + return Objects.equals(map, cm_map.map) && + Objects.equals(mapA, cm_map.mapA) && + Objects.equals(mapB, cm_map.mapB) && + Objects.equals(mapC, cm_map.mapC) && + Objects.equals(mapWithListAndMap, cm_map.mapWithListAndMap); + } + + @Override + public int hashCode() { + return Objects.hash(map, mapA, mapB, mapC, mapWithListAndMap); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_Object.java b/net/src/test/java/com/zfoo/net/packet/CM_Object.java new file mode 100644 index 00000000..eb5e8fcc --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_Object.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Object implements IPacket { + + public static final transient short PROTOCOL_ID = 1114; + + private int a; + + private ObjectA b; + + @Override + public String toString() { + return "CM_Object{" + "a=" + a + ", b=" + b + '}'; + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public ObjectA getB() { + return b; + } + + public void setB(ObjectA b) { + this.b = b; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_Object cm_object = (CM_Object) o; + return a == cm_object.a && + Objects.equals(b, cm_object.b); + } + + @Override + public int hashCode() { + + return Objects.hash(a, b); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_Set.java b/net/src/test/java/com/zfoo/net/packet/CM_Set.java new file mode 100644 index 00000000..3b308a99 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_Set.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Set implements IPacket { + + public static final transient short PROTOCOL_ID = 1121; + + private Set a; + private Set b; + private Set> c; + + private Set>> setSetSetWithObject; + private Set> setWithMap; + private Set>> setListWithMap; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public Set getA() { + return a; + } + + public void setA(Set a) { + this.a = a; + } + + public Set getB() { + return b; + } + + public void setB(Set b) { + this.b = b; + } + + public Set> getC() { + return c; + } + + public void setC(Set> c) { + this.c = c; + } + + public Set>> getSetSetSetWithObject() { + return setSetSetWithObject; + } + + public void setSetSetSetWithObject(Set>> setSetSetWithObject) { + this.setSetSetWithObject = setSetSetWithObject; + } + + public Set> getSetWithMap() { + return setWithMap; + } + + public void setSetWithMap(Set> setWithMap) { + this.setWithMap = setWithMap; + } + + public Set>> getSetListWithMap() { + return setListWithMap; + } + + public void setSetListWithMap(Set>> setListWithMap) { + this.setListWithMap = setListWithMap; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CM_Set cm_set = (CM_Set) o; + return Objects.equals(a, cm_set.a) && + Objects.equals(b, cm_set.b) && + Objects.equals(c, cm_set.c) && + Objects.equals(setSetSetWithObject, cm_set.setSetSetWithObject) && + Objects.equals(setWithMap, cm_set.setWithMap) && + Objects.equals(setListWithMap, cm_set.setListWithMap); + } + + @Override + public int hashCode() { + return Objects.hash(a, b, c, setSetSetWithObject, setWithMap, setListWithMap); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/CM_SyncMess.java b/net/src/test/java/com/zfoo/net/packet/CM_SyncMess.java new file mode 100644 index 00000000..6a546d35 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/CM_SyncMess.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_SyncMess implements IPacket { + + public static final transient short PROTOCOL_ID = 1150; + + private String a; + + private int id; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/ObjectA.java b/net/src/test/java/com/zfoo/net/packet/ObjectA.java new file mode 100644 index 00000000..1409a0c0 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/ObjectA.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ObjectA implements IPacket { + + public static final transient short PROTOCOL_ID = 1116; + + private int a; + + + private ObjectB objectB; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + + public ObjectB getObjectB() { + return objectB; + } + + public void setObjectB(ObjectB objectB) { + this.objectB = objectB; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ObjectA objectA = (ObjectA) o; + return a == objectA.a && + Objects.equals(objectB, objectA.objectB); + } + + @Override + public int hashCode() { + return Objects.hash(a, objectB); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/ObjectB.java b/net/src/test/java/com/zfoo/net/packet/ObjectB.java new file mode 100644 index 00000000..16e45e1c --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/ObjectB.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ObjectB implements IPacket { + + public static final transient short PROTOCOL_ID = 1117; + + private boolean flag; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public boolean isFlag() { + return flag; + } + + public void setFlag(boolean flag) { + this.flag = flag; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ObjectB objectB = (ObjectB) o; + return flag == objectB.flag; + } + + @Override + public int hashCode() { + return Objects.hash(flag); + } +} + diff --git a/net/src/test/java/com/zfoo/net/packet/SM_AsyncMess0.java b/net/src/test/java/com/zfoo/net/packet/SM_AsyncMess0.java new file mode 100644 index 00000000..a6339053 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/SM_AsyncMess0.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_AsyncMess0 implements IPacket { + + public static final transient short PROTOCOL_ID = 1153; + + private String a; + + private int id; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/SM_AsyncMess1.java b/net/src/test/java/com/zfoo/net/packet/SM_AsyncMess1.java new file mode 100644 index 00000000..c08dc848 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/SM_AsyncMess1.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_AsyncMess1 implements IPacket { + + public static final transient short PROTOCOL_ID = 1155; + + private String a; + + private int id; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/SM_Float.java b/net/src/test/java/com/zfoo/net/packet/SM_Float.java new file mode 100644 index 00000000..3f45fd49 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/SM_Float.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_Float implements IPacket { + + public static final transient short PROTOCOL_ID = 1113; + + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + +} + diff --git a/net/src/test/java/com/zfoo/net/packet/SM_Int.java b/net/src/test/java/com/zfoo/net/packet/SM_Int.java new file mode 100644 index 00000000..480de845 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/SM_Int.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_Int implements IPacket { + + public static final transient short PROTOCOL_ID = 1111; + + private Boolean flag; + + private Byte a; + + private Short b; + + private Integer c; + + private Long d; + + private char e; + + private String f; + + public Boolean getFlag() { + return flag; + } + + public void setFlag(Boolean flag) { + this.flag = flag; + } + + public Byte getA() { + return a; + } + + public void setA(Byte a) { + this.a = a; + } + + public Short getB() { + return b; + } + + public void setB(Short b) { + this.b = b; + } + + public Integer getC() { + return c; + } + + public void setC(Integer c) { + this.c = c; + } + + public Long getD() { + return d; + } + + public void setD(Long d) { + this.d = d; + } + + public char getE() { + return e; + } + + public void setE(char e) { + this.e = e; + } + + public String getF() { + return f; + } + + public void setF(String f) { + this.f = f; + } + + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SM_Int sm_int = (SM_Int) o; + return e == sm_int.e && + flag.equals(sm_int.flag) && + a.equals(sm_int.a) && + b.equals(sm_int.b) && + c.equals(sm_int.c) && + d.equals(sm_int.d) && + f.equals(sm_int.f); + } + + @Override + public int hashCode() { + return Objects.hash(flag, a, b, c, d, e, f); + } +} + diff --git a/net/src/test/java/com/zfoo/net/packet/SM_Object.java b/net/src/test/java/com/zfoo/net/packet/SM_Object.java new file mode 100644 index 00000000..7245a7f4 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/SM_Object.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_Object implements IPacket { + + public static final transient short PROTOCOL_ID = 1115; + + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} + diff --git a/net/src/test/java/com/zfoo/net/packet/SM_SyncMess.java b/net/src/test/java/com/zfoo/net/packet/SM_SyncMess.java new file mode 100644 index 00000000..4b524d00 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/SM_SyncMess.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_SyncMess implements IPacket { + + public static final transient short PROTOCOL_ID = 1151; + + private String a; + + private int id; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/csharp/CM_CSharpRequest.java b/net/src/test/java/com/zfoo/net/packet/csharp/CM_CSharpRequest.java new file mode 100644 index 00000000..b02a62a4 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/csharp/CM_CSharpRequest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.csharp; + +import com.zfoo.protocol.IPacket; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_CSharpRequest implements IPacket { + + public static final transient short PROTOCOL_ID = 1165; + + // 注释1 + public byte a; + /** + * 注释2 + * 附加注释 + */ + public Byte aa; + public byte[] aaa; + public Byte[] aaaa; + + public short b; + public Short bb; + public short[] bbb; + public Short[] bbbb; + + public int c; + public Integer cc; + public int[] ccc; + public Integer[] cccc; + + public long d; + public Long dd; + public long[] ddd; + public Long[] dddd; + + public float e; + public Float ee; + public float[] eee; + public Float[] eeee; + + public double f; + public Double ff; + public double[] fff; + public Double[] ffff; + + public boolean g; + public Boolean gg; + public boolean[] ggg; + public Boolean[] gggg; + + // 注释行 + public char h; + public Character hh; + public char[] hhh; + public Character[] hhhh; + + public String jj; + public String[] jjj; + + public CSharpObjectA objectA; + public CSharpObjectA[] objectArray; + + public List l; + public List>> ll; + public List> lll; + public List llll; + public List> lllll; + + public Map m; + public Map mm; + public Map> mmm; + public Map>, List>>> mmmm; + + public Set s; + public Set>> ss; + public Set> sss; + public Set ssss; + /** + * 注释z + * 附加注释 + */ + public Set> sssss; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + +} diff --git a/net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectA.java b/net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectA.java new file mode 100644 index 00000000..ae2849ec --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectA.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.csharp; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CSharpObjectA implements IPacket { + + public static final transient short PROTOCOL_ID = 1166; + + public int value; + public CSharpObjectB objectB; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectB.java b/net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectB.java new file mode 100644 index 00000000..1361ada2 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/csharp/CSharpObjectB.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.csharp; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CSharpObjectB implements IPacket { + + public static final transient short PROTOCOL_ID = 1167; + + public boolean flag; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/gateway/CM_GatewayProvider.java b/net/src/test/java/com/zfoo/net/packet/gateway/CM_GatewayProvider.java new file mode 100644 index 00000000..71bd8815 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/gateway/CM_GatewayProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.gateway; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_GatewayProvider implements IPacket { + + public static final transient short PROTOCOL_ID = 4102; + + private String a; + + private int b; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/gateway/SM_GatewayProvider.java b/net/src/test/java/com/zfoo/net/packet/gateway/SM_GatewayProvider.java new file mode 100644 index 00000000..013a3004 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/gateway/SM_GatewayProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.gateway; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_GatewayProvider implements IPacket { + + public static final transient short PROTOCOL_ID = 4103; + + private String a; + private int b; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/provider/CM_Provider.java b/net/src/test/java/com/zfoo/net/packet/provider/CM_Provider.java new file mode 100644 index 00000000..ba0039f3 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/provider/CM_Provider.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.provider; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_Provider implements IPacket { + + public static final transient short PROTOCOL_ID = 4100; + + private String a; + + private int b; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/provider/SM_Provider.java b/net/src/test/java/com/zfoo/net/packet/provider/SM_Provider.java new file mode 100644 index 00000000..3edc6014 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/provider/SM_Provider.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.provider; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SM_Provider implements IPacket { + + public static final transient short PROTOCOL_ID = 4101; + + private String a; + private int b; + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/websocket/CM_WebSocketPacket.java b/net/src/test/java/com/zfoo/net/packet/websocket/CM_WebSocketPacket.java new file mode 100644 index 00000000..8eb23e79 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/websocket/CM_WebSocketPacket.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.websocket; + +import com.zfoo.protocol.IPacket; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CM_WebSocketPacket implements IPacket { + + public static final transient short PROTOCOL_ID = 2070; + + private byte a; + private Byte aa; + private byte[] aaa; + private Byte[] aaaa; + + private short b; + private Short bb; + private short[] bbb; + private Short[] bbbb; + + private int c; + private Integer cc; + private int[] ccc; + private Integer[] cccc; + + private long d; + private long[] dd; + + private float e; + private Float ee; + private float[] eee; + private Float[] eeee; + + private double f; + private Double ff; + private double[] fff; + private Double[] ffff; + + private char g; + private char[] gg; + private List ggg; + + private String jj; + private String[] jjj; + + private WebSocketObjectA kk; + private WebSocketObjectA[] kkk; + + private List l; + private List>> ll; + private List> lll; + private List llll; + private List> lllll; + + private Map m; + private Map mm; + private Map> mmm; + private Map>, List>>> mmmm; + + private Set s; + private Set>> ss; + private Set> sss; + private Set ssss; + private Set> sssss; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public byte getA() { + return a; + } + + public void setA(byte a) { + this.a = a; + } + + public Byte getAa() { + return aa; + } + + public void setAa(Byte aa) { + this.aa = aa; + } + + public byte[] getAaa() { + return aaa; + } + + public void setAaa(byte[] aaa) { + this.aaa = aaa; + } + + public Byte[] getAaaa() { + return aaaa; + } + + public void setAaaa(Byte[] aaaa) { + this.aaaa = aaaa; + } + + public short getB() { + return b; + } + + public void setB(short b) { + this.b = b; + } + + public Short getBb() { + return bb; + } + + public void setBb(Short bb) { + this.bb = bb; + } + + public short[] getBbb() { + return bbb; + } + + public void setBbb(short[] bbb) { + this.bbb = bbb; + } + + public Short[] getBbbb() { + return bbbb; + } + + public void setBbbb(Short[] bbbb) { + this.bbbb = bbbb; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public Integer getCc() { + return cc; + } + + public void setCc(Integer cc) { + this.cc = cc; + } + + public int[] getCcc() { + return ccc; + } + + public void setCcc(int[] ccc) { + this.ccc = ccc; + } + + public Integer[] getCccc() { + return cccc; + } + + public void setCccc(Integer[] cccc) { + this.cccc = cccc; + } + + public long getD() { + return d; + } + + public void setD(long d) { + this.d = d; + } + + public long[] getDd() { + return dd; + } + + public void setDd(long[] dd) { + this.dd = dd; + } + + public float getE() { + return e; + } + + public void setE(float e) { + this.e = e; + } + + public Float getEe() { + return ee; + } + + public void setEe(Float ee) { + this.ee = ee; + } + + public float[] getEee() { + return eee; + } + + public void setEee(float[] eee) { + this.eee = eee; + } + + public Float[] getEeee() { + return eeee; + } + + public void setEeee(Float[] eeee) { + this.eeee = eeee; + } + + public double getF() { + return f; + } + + public void setF(double f) { + this.f = f; + } + + public Double getFf() { + return ff; + } + + public void setFf(Double ff) { + this.ff = ff; + } + + public double[] getFff() { + return fff; + } + + public void setFff(double[] fff) { + this.fff = fff; + } + + public Double[] getFfff() { + return ffff; + } + + public void setFfff(Double[] ffff) { + this.ffff = ffff; + } + + public char getG() { + return g; + } + + public void setG(char g) { + this.g = g; + } + + public char[] getGg() { + return gg; + } + + public void setGg(char[] gg) { + this.gg = gg; + } + + public List getGgg() { + return ggg; + } + + public void setGgg(List ggg) { + this.ggg = ggg; + } + + public String getJj() { + return jj; + } + + public void setJj(String jj) { + this.jj = jj; + } + + public String[] getJjj() { + return jjj; + } + + public void setJjj(String[] jjj) { + this.jjj = jjj; + } + + public WebSocketObjectA getKk() { + return kk; + } + + public void setKk(WebSocketObjectA kk) { + this.kk = kk; + } + + public WebSocketObjectA[] getKkk() { + return kkk; + } + + public void setKkk(WebSocketObjectA[] kkk) { + this.kkk = kkk; + } + + public List getL() { + return l; + } + + public void setL(List l) { + this.l = l; + } + + public List>> getLl() { + return ll; + } + + public void setLl(List>> ll) { + this.ll = ll; + } + + public List> getLll() { + return lll; + } + + public void setLll(List> lll) { + this.lll = lll; + } + + public List getLlll() { + return llll; + } + + public void setLlll(List llll) { + this.llll = llll; + } + + public List> getLllll() { + return lllll; + } + + public void setLllll(List> lllll) { + this.lllll = lllll; + } + + public Map getM() { + return m; + } + + public void setM(Map m) { + this.m = m; + } + + public Map getMm() { + return mm; + } + + public void setMm(Map mm) { + this.mm = mm; + } + + public Map> getMmm() { + return mmm; + } + + public void setMmm(Map> mmm) { + this.mmm = mmm; + } + + public Map>, List>>> getMmmm() { + return mmmm; + } + + public void setMmmm(Map>, List>>> mmmm) { + this.mmmm = mmmm; + } + + public Set getS() { + return s; + } + + public void setS(Set s) { + this.s = s; + } + + public Set>> getSs() { + return ss; + } + + public void setSs(Set>> ss) { + this.ss = ss; + } + + public Set> getSss() { + return sss; + } + + public void setSss(Set> sss) { + this.sss = sss; + } + + public Set getSsss() { + return ssss; + } + + public void setSsss(Set ssss) { + this.ssss = ssss; + } + + public Set> getSssss() { + return sssss; + } + + public void setSssss(Set> sssss) { + this.sssss = sssss; + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectA.java b/net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectA.java new file mode 100644 index 00000000..4b8682bd --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectA.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.websocket; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class WebSocketObjectA implements IPacket { + + public static final transient short PROTOCOL_ID = 2071; + + private int a; + + private WebSocketObjectB objectB; + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public WebSocketObjectB getObjectB() { + return objectB; + } + + public void setObjectB(WebSocketObjectB objectB) { + this.objectB = objectB; + } + + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebSocketObjectA that = (WebSocketObjectA) o; + return a == that.a && + Objects.equals(objectB, that.objectB); + } + + @Override + public int hashCode() { + return Objects.hash(a, objectB); + } +} diff --git a/net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectB.java b/net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectB.java new file mode 100644 index 00000000..ed635219 --- /dev/null +++ b/net/src/test/java/com/zfoo/net/packet/websocket/WebSocketObjectB.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.packet.websocket; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class WebSocketObjectB implements IPacket { + + public static final transient short PROTOCOL_ID = 2072; + + private boolean flag; + + @Override + public String toString() { + return "ObjectB{" + "flag=" + flag + '}'; + } + + public boolean isFlag() { + return flag; + } + + public void setFlag(boolean flag) { + this.flag = flag; + } + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + WebSocketObjectB that = (WebSocketObjectB) o; + return flag == that.flag; + } + + @Override + public int hashCode() { + return Objects.hash(flag); + } +} + diff --git a/net/src/test/java/com/zfoo/net/protocol/ProtocolTest.java b/net/src/test/java/com/zfoo/net/protocol/ProtocolTest.java new file mode 100644 index 00000000..eb50f83e --- /dev/null +++ b/net/src/test/java/com/zfoo/net/protocol/ProtocolTest.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.protocol; + +import com.zfoo.net.NetContext; +import com.zfoo.net.packet.*; +import com.zfoo.net.packet.model.DecodedPacketInfo; +import com.zfoo.net.packet.model.SignalPacketAttachment; +import com.zfoo.net.packet.service.IPacketService; +import com.zfoo.protocol.ProtocolManager; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ProtocolTest { + + private static final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("server_config.xml"); + private static final IPacketService packetService = NetContext.getPacketService(); + + private static SignalPacketAttachment attachment = new SignalPacketAttachment(); + + private static ObjectA objectA0 = new ObjectA(); + private static ObjectA objectA1 = new ObjectA(); + private static ObjectB objectB = new ObjectB(); + + static { + objectA0.setA(Integer.MAX_VALUE); + objectA0.setObjectB(objectB); + objectA1.setA(Integer.MIN_VALUE); + objectA1.setObjectB(objectB); + objectB.setFlag(false); + + attachment.setPacketId(Integer.MAX_VALUE); + } + + private static List list = List.of(Integer.MIN_VALUE, -99, 0, 99, Integer.MAX_VALUE); + + private static Map mapWithIntegerAndString = Map.of(Integer.MIN_VALUE, "min", -99, "-99", 0, "0", 99, "99", Integer.MAX_VALUE, "max"); + private static Map mapWithObject = Map.of(Integer.MIN_VALUE, objectA0, -99, objectA0, 0, objectA0, 99, objectA0, Integer.MAX_VALUE, objectA0); + private static List> listWithMapWithObject = List.of(mapWithObject, mapWithObject, mapWithObject); + + + @Test + public void testCMInt() { + CM_Int cm = new CM_Int(); + cm.setFlag(false); + cm.setA(Byte.MIN_VALUE); + cm.setB(Short.MIN_VALUE); + cm.setC(Integer.MIN_VALUE); + cm.setD(Long.MIN_VALUE); + cm.setE('e'); + cm.setF("Hello Jaysunxiao,this is the World!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + ByteBuf writeBuff = Unpooled.directBuffer(); + packetService.write(writeBuff, cm, attachment); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertEquals(packetInfo.getPacketAttachment(), attachment); + } + + @Test + public void testCmObject() { + CM_Object cm = new CM_Object(); + cm.setA(Integer.MIN_VALUE); + cm.setB(objectA0); + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + + @Test + public void testCMFloatMin() { + CM_Float cm = new CM_Float(); + cm.setA(Float.MIN_VALUE); + cm.setB(Float.MIN_VALUE); + cm.setC(Double.MIN_VALUE); + cm.setD(Double.MIN_VALUE); + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + + @Test + public void testCMFloatNormal() { + CM_Float cm = new CM_Float(); + cm.setA((float) 0.1); + cm.setB((float) 0.2); + cm.setC(1.1); + cm.setD(100.1); + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + + @Test + public void testCMList() { + CM_List cm = new CM_List(); + + List> listWithList = new ArrayList<>(); + listWithList.add(list); + listWithList.add(list); + listWithList.add(list); + cm.setListWitList(listWithList); + + List listWithObject = List.of(objectA0, objectA0, objectA0); + cm.setObjs(listWithObject); + cm.setList(list); + + List> listListWithObject = List.of(listWithObject, listWithObject, listWithObject); + List>> listListListWithObject = List.of(listListWithObject, listListWithObject, listListWithObject); + cm.setListWithObject(listListListWithObject); + + List> listWithMap = List.of(mapWithIntegerAndString, mapWithIntegerAndString, mapWithIntegerAndString); + cm.setListWithMap(listWithMap); + + List>> listListWithMap = List.of(listWithMapWithObject, listWithMapWithObject, listWithMapWithObject); + cm.setListListWithMap(listListWithMap); + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + + + @Test + public void testCM_Set() { + CM_Set cm = new CM_Set(); + + Set integerSet = Set.of(Integer.MIN_VALUE, 0, Integer.MAX_VALUE); + cm.setA(integerSet); + + Set setObject = Set.of(objectA0, objectA1); + cm.setB(setObject); + cm.setC(Set.of(list)); + + Set> setSetObject = Set.of(setObject); + cm.setSetSetSetWithObject(Set.of(setSetObject)); + + cm.setSetWithMap(Set.of(mapWithIntegerAndString)); + cm.setSetListWithMap(Set.of(listWithMapWithObject)); + + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + + @Test + public void testCMArray() { + CM_Array cm = new CM_Array(); + int[] a = new int[]{1, 2, 34, 5}; + cm.setA(a); + ObjectA[] array = new ObjectA[]{objectA0, objectA0, objectA0}; + cm.setB(array); + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + + @Test + public void testCMMap() { + CM_Map cm = new CM_Map(); + + Map map = Map.of(Integer.MIN_VALUE, Integer.MIN_VALUE, -99, -99, 0, 0, 99, 99, Integer.MAX_VALUE, Integer.MAX_VALUE); + Map mapA = Map.of(Integer.MIN_VALUE, objectA0, -99, objectA0, 0, objectA0, 99, objectA0, Integer.MAX_VALUE, objectA0); + Map mapB = Map.of(objectA0, "objectA0", objectA1, "objectA1"); + Map mapC = Map.of(objectA0, objectA0, objectA1, objectA1); + cm.setMap(map); + cm.setMapA(mapA); + cm.setMapB(mapB); + cm.setMapC(mapC); + + Map, ObjectA> mapWithList = Map.of(list, objectA0); + Map, Map, ObjectA>> mapWithListAndMap = Map.of(list, mapWithList); + cm.setMapWithListAndMap(mapWithListAndMap); + + ByteBuf writeBuff = Unpooled.buffer(); + packetService.write(writeBuff, cm, null); + + writeBuff.readerIndex(ProtocolManager.PROTOCOL_HEAD_LENGTH);// 信息头的长度 + + DecodedPacketInfo packetInfo = packetService.read(writeBuff); + + Assert.assertEquals(packetInfo.getPacket(), cm); + Assert.assertNull(packetInfo.getPacketAttachment()); + } + +} diff --git a/net/src/test/java/com/zfoo/net/session/SessionUtils.java b/net/src/test/java/com/zfoo/net/session/SessionUtils.java new file mode 100644 index 00000000..0080158f --- /dev/null +++ b/net/src/test/java/com/zfoo/net/session/SessionUtils.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.session; + +import com.zfoo.net.NetContext; +import com.zfoo.net.session.model.Session; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.ThreadUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class SessionUtils { + + public static void printSessionInfo() { + Thread thread = new Thread(() -> { + while (true) { + ThreadUtils.sleep(10_000); + var builder = new StringBuilder(); + builder.append(StringUtils.format("clientSession总数:[{}]", NetContext.getSessionManager().getClientSessionMap().size())); + builder.append(FileUtils.LS); + for (Session session : NetContext.getSessionManager().getClientSessionMap().values()) { + builder.append(StringUtils.format("[session:{}],[attachment:{}]" + , session.getChannel().remoteAddress(), JsonUtils.object2String(session.getClientSignalPacketAttachmentMap()))); + builder.append(FileUtils.LS); + } + + builder.append(StringUtils.format("serverSession总数:[{}]", NetContext.getSessionManager().getServerSessionMap().size())); + builder.append(FileUtils.LS); + for (Session session : NetContext.getSessionManager().getServerSessionMap().values()) { + builder.append(StringUtils.format("[session:{}],[attachment:{}]" + , session.getChannel().remoteAddress(), JsonUtils.object2String(session.getClientSignalPacketAttachmentMap()))); + builder.append(FileUtils.LS); + } + + System.out.println(builder.toString()); + + } + }); + thread.start(); + } + +} diff --git a/net/src/test/java/com/zfoo/net/util/SimpleCacheTest.java b/net/src/test/java/com/zfoo/net/util/SimpleCacheTest.java new file mode 100644 index 00000000..fbbe3a8d --- /dev/null +++ b/net/src/test/java/com/zfoo/net/util/SimpleCacheTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.net.util; + +import com.zfoo.protocol.model.Pair; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class SimpleCacheTest { + + @Test + public void test() { + var cache = SimpleCache.build(3000, 6000, 100, new Function, List>>() { + @Override + public List> apply(List keyList) { + return keyList.stream().map(it -> new Pair<>(it, "new-" + it + "-value")).collect(Collectors.toList()); + } + }, key -> "empty"); + + cache.put("a", "b"); + cache.get("a"); + ThreadUtils.sleep(3000); + System.out.println(cache.get("a")); + ThreadUtils.sleep(1000); + System.out.println(cache.get("a")); + ThreadUtils.sleep(1000); + System.out.println(cache.get("a")); + ThreadUtils.sleep(1000); + System.out.println(cache.get("a")); + ThreadUtils.sleep(1000); + System.out.println(cache.get("a")); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/net/src/test/resources/client_config.xml b/net/src/test/resources/client_config.xml new file mode 100644 index 00000000..fd09a2f1 --- /dev/null +++ b/net/src/test/resources/client_config.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/deploy-dev.properties b/net/src/test/resources/deploy-dev.properties new file mode 100644 index 00000000..a73b66b7 --- /dev/null +++ b/net/src/test/resources/deploy-dev.properties @@ -0,0 +1,5 @@ +registry.center=zookeeper +registry.user= +registry.password= +registry.address.name=firstZookeeper +registry.address.url=127.0.0.1:2181 \ No newline at end of file diff --git a/net/src/test/resources/gateway/gateway_client_config.xml b/net/src/test/resources/gateway/gateway_client_config.xml new file mode 100644 index 00000000..7605c7f6 --- /dev/null +++ b/net/src/test/resources/gateway/gateway_client_config.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/gateway/gateway_consistent_session_config.xml b/net/src/test/resources/gateway/gateway_consistent_session_config.xml new file mode 100644 index 00000000..82b56014 --- /dev/null +++ b/net/src/test/resources/gateway/gateway_consistent_session_config.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/logback-test.xml b/net/src/test/resources/logback-test.xml new file mode 100644 index 00000000..101dfd90 --- /dev/null +++ b/net/src/test/resources/logback-test.xml @@ -0,0 +1,43 @@ + + + + + com.zfoo.net + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/protocol.xml b/net/src/test/resources/protocol.xml new file mode 100644 index 00000000..6df92a89 --- /dev/null +++ b/net/src/test/resources/protocol.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/provider/consumer_consistent_session_config.xml b/net/src/test/resources/provider/consumer_consistent_session_config.xml new file mode 100644 index 00000000..08d9f85a --- /dev/null +++ b/net/src/test/resources/provider/consumer_consistent_session_config.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/provider/consumer_random_config.xml b/net/src/test/resources/provider/consumer_random_config.xml new file mode 100644 index 00000000..f06f53db --- /dev/null +++ b/net/src/test/resources/provider/consumer_random_config.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/provider/consumer_shortest_time_config.xml b/net/src/test/resources/provider/consumer_shortest_time_config.xml new file mode 100644 index 00000000..193269a2 --- /dev/null +++ b/net/src/test/resources/provider/consumer_shortest_time_config.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/provider/provider_config.xml b/net/src/test/resources/provider/provider_config.xml new file mode 100644 index 00000000..97e8c79c --- /dev/null +++ b/net/src/test/resources/provider/provider_config.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/net/src/test/resources/server_config.xml b/net/src/test/resources/server_config.xml new file mode 100644 index 00000000..70a5c43c --- /dev/null +++ b/net/src/test/resources/server_config.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orm/README.md b/orm/README.md new file mode 100644 index 00000000..93c80f36 --- /dev/null +++ b/orm/README.md @@ -0,0 +1,87 @@ +### Ⅰ. 简介 + +- 基于MongoDB的orm框架,提供POJO对象和MongoDB数据库之间的映射 + +### Ⅱ. 注意事项 + +- POJO对象的属性必须提供get和set方法,否则无法映射 +- 不支持泛型 +- 如果不想映射某属性,直接加上transient关键字 +- 目前支持基本数据属性(byte,short,int,long,float,double,boolean),字符串String,List,Set集合属性的映射,不支持Map +- 数据库主键能用整数尽量用整数,因为MongoDB默认的主键是一个字符串,比较占空间 +- 数据库使用自研的orm框架,比如一个实体类UserEntity,映射到数据库中的集合为user,首字母小写,去掉Entity + +### Ⅲ. 使用方法 + +#### 1. IAccessor接口,为数据访问接口 + +- 插入数据到数据库,会以对象的id()方法的返回值作为主键 + +``` +OrmContext.getAccessor().insert(obj) +``` + +- 删除数据库中的数据,会以对象的id()方法的返回值作为查找关键字,删除以这个id()为主键的数据 + +``` +OrmContext.getAccessor().delete(obj); +``` + +- 修改数据库中的数据 + +``` +OrmContext.getAccessor().update(obj); +``` + +#### 2. IQuery接口,为数据复杂查询接口 + +#### 3. 缓存使用方法 + +- 例如有下列配置 + +``` + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +- 有下列注解 + +``` +@EntityCaches(cacheStrategy = "tenThousand", persister = @Persister("time30s")) +public class UserEntity implements IEntity { +} +``` + +- database表示操作哪个数据库 +- address表示数据库的地址,支持分片的配置 +- caches中的strategy表示一个缓存的策略,即将数据库中的数据先读入Orm中的EntityCaches缓存,如hundred这个策略表示,缓存数据库中1000条数据,10分钟过期 +- persisters中的strategy表示一个持久化的策略,如3s这个策略表示,将EntityCaches中的缓存数据每3s写入到数据库中一次,即使中途宕机,也只损失3秒的数据 +- EntityCaches这个注解表示将会被Orm管理,使用hundred策略,缓存的持久化策略为3s + +### Ⅳ. 教程 + +- test下中包含了所有增删改查的教程,运行之前请先安装MongoDB \ No newline at end of file diff --git a/orm/pom.xml b/orm/pom.xml new file mode 100644 index 00000000..8fb983e5 --- /dev/null +++ b/orm/pom.xml @@ -0,0 +1,278 @@ + + + 4.0.0 + + com.zfoo + orm + 3.0 + + jar + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + + com.zfoo + util + ${zfoo.util.version} + + + + com.zfoo + event + ${zfoo.event.version} + + + + com.zfoo + scheduler + ${zfoo.scheduler.version} + + + + org.mongodb + mongodb-driver-sync + ${mongodb-driver-sync.version} + + + + + com.google.guava + guava + ${google.guava.version} + + + error_prone_annotations + com.google.errorprone + + + checker-qual + org.checkerframework + + + + + + + com.github.ben-manes.caffeine + caffeine + ${caffeine.version} + + + + + org.javassist + javassist + ${javassist.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + diff --git a/orm/src/main/java/com/zfoo/orm/OrmContext.java b/orm/src/main/java/com/zfoo/orm/OrmContext.java new file mode 100644 index 00000000..f6d3c7d2 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/OrmContext.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm; + +import com.mongodb.client.MongoClient; +import com.zfoo.orm.manager.IOrmManager; +import com.zfoo.orm.manager.OrmManager; +import com.zfoo.orm.model.accessor.IAccessor; +import com.zfoo.orm.model.query.IQuery; +import com.zfoo.orm.schema.OrmProcessor; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.scheduler.SchedulerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class OrmContext implements ApplicationListener, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(OrmContext.class); + + private static OrmContext instance; + + private ApplicationContext applicationContext; + + private IAccessor accessor; + + private IQuery query; + + private IOrmManager ormManager; + + private boolean stop = false; + + public static ApplicationContext getApplicationContext() { + return instance.applicationContext; + } + + public static OrmContext getOrmContext() { + return instance; + } + + public static IAccessor getAccessor() { + return instance.accessor; + } + + public static IQuery getQuery() { + return instance.query; + } + + public static IOrmManager getOrmManager() { + return instance.ormManager; + } + + public static boolean isStop() { + return instance.stop; + } + + @Override + public void onApplicationEvent(ApplicationContextEvent event) { + if (event instanceof ContextRefreshedEvent) { + if (instance != null) { + return; + } + OrmContext.instance = this; + instance.applicationContext = event.getApplicationContext(); + + instance.accessor = applicationContext.getBean(IAccessor.class); + instance.query = applicationContext.getBean(IQuery.class); + instance.ormManager = applicationContext.getBean(IOrmManager.class); + + instance.ormManager.initBefore(); + + var beanNames = applicationContext.getBeanDefinitionNames(); + var processor = applicationContext.getBean(OrmProcessor.class); + for (var beanName : beanNames) { + processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName); + } + + instance.ormManager.initAfter(); + } else if (event instanceof ContextClosedEvent) { + shutdownBefore(); + shutdownBetween(); + shutdownAfter(); + } + } + + @Override + public int getOrder() { + return 1; + } + + public static synchronized void shutdownBefore() { + SchedulerContext.shutdown(); + } + + public static synchronized void shutdownBetween() { + instance.stop = true; + try { + instance.ormManager + .getAllEntityCaches() + .forEach(it -> it.persistAll()); + } catch (Exception e) { + logger.error("关闭服务器时,持久化缓存数据异常", e); + } finally { + instance.stop = true; + } + } + + public static synchronized void shutdownAfter() { + try { + var field = OrmManager.class.getDeclaredField("mongoClient"); + ReflectionUtils.makeAccessible(field); + var mongoClient = (MongoClient) ReflectionUtils.getField(field, instance.ormManager); + mongoClient.close(); + } catch (Exception e) { + logger.error("关闭MongoClient数据库连接失败", e); + return; + } + + logger.info("Orm shutdown gracefully."); + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/manager/IOrmManager.java b/orm/src/main/java/com/zfoo/orm/manager/IOrmManager.java new file mode 100644 index 00000000..3176fe1a --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/manager/IOrmManager.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.manager; + +import com.mongodb.client.ClientSession; +import com.mongodb.client.MongoCollection; +import com.zfoo.orm.model.cache.IEntityCaches; +import com.zfoo.orm.model.entity.IEntity; +import org.bson.Document; + +import java.util.Collection; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IOrmManager { + + void initBefore(); + + void initAfter(); + + + > IEntityCaches getEntityCaches(Class clazz); + + Collection> getAllEntityCaches(); + + /** + * 获取一个会话 + */ + ClientSession getClientSession(); + + /** + * 基于对象的orm操作 + */ + > MongoCollection getCollection(Class entityClazz); + + /** + * 更加细粒度的操作 + */ + MongoCollection getCollection(String collection); + +} diff --git a/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java b/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java new file mode 100644 index 00000000..4f9cf168 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/manager/OrmManager.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.manager; + +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCredential; +import com.mongodb.ServerAddress; +import com.mongodb.client.*; +import com.mongodb.client.model.IndexOptions; +import com.mongodb.client.model.Indexes; +import com.zfoo.orm.model.anno.EntityCache; +import com.zfoo.orm.model.cache.EntityCaches; +import com.zfoo.orm.model.cache.IEntityCaches; +import com.zfoo.orm.model.config.OrmConfig; +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.orm.model.vo.EntityDef; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.net.HostAndPort; +import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistries; +import org.bson.codecs.configuration.CodecRegistry; +import org.bson.codecs.pojo.PojoCodecProvider; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class OrmManager implements IOrmManager { + + private static final Map>, IEntityCaches> entityCachesMap = new HashMap<>(); + + private OrmConfig ormConfig; + + private MongoClient mongoClient; + private MongoDatabase mongodbDatabase; + + private final Map>, String> collectionNameMap = new ConcurrentHashMap<>(); + + public OrmConfig getOrmConfig() { + return ormConfig; + } + + public void setOrmConfig(OrmConfig ormConfig) { + this.ormConfig = ormConfig; + } + + @Override + public void initBefore() { + var entityDefMap = scanEntity(); + + for (var entityDef : entityDefMap.values()) { + var entityCaches = new EntityCaches(entityDef); + entityCachesMap.put(entityDef.getClazz(), entityCaches); + } + + CodecRegistry pojoCodecRegistry = CodecRegistries.fromRegistries( + MongoClientSettings.getDefaultCodecRegistry(), + CodecRegistries.fromProviders(PojoCodecProvider.builder().automatic(true).build())); + + var mongoBuilder = MongoClientSettings + .builder() + .codecRegistry(pojoCodecRegistry); + + // 设置数据库地址 + var hostConfig = ormConfig.getHostConfig(); + if (CollectionUtils.isNotEmpty(hostConfig.getAddressMap())) { + var hostList = HostAndPort.toHostAndPortList(hostConfig.getAddressMap().values()) + .stream() + .map(it -> new ServerAddress(it.getHost(), it.getPort())) + .collect(Collectors.toList()); + mongoBuilder.applyToClusterSettings(builder -> builder.hosts(hostList)); + } + + // 设置数据库账号密码 + if (!StringUtils.isBlank(hostConfig.getUser()) && !StringUtils.isBlank(hostConfig.getPassword())) { + mongoBuilder.credential(MongoCredential.createCredential(hostConfig.getUser(), "admin", hostConfig.getPassword().toCharArray())); + } + + // 设置连接池的大小 + var maxConnection = Runtime.getRuntime().availableProcessors() * 3 + 1; + mongoBuilder.applyToConnectionPoolSettings(builder -> builder.maxSize(maxConnection).minSize(1)); + + mongoClient = MongoClients.create(mongoBuilder.build()); + mongodbDatabase = mongoClient.getDatabase(hostConfig.getDatabase()); + + // 创建索引 + for (var entityDef : entityDefMap.values()) { + var indexDefMap = entityDef.getIndexDefMap(); + if (CollectionUtils.isNotEmpty(indexDefMap)) { + var collection = getCollection(entityDef.getClazz()); + for (var indexDef : indexDefMap.entrySet()) { + var fieldName = indexDef.getKey(); + var index = indexDef.getValue(); + var hasIndex = false; + for (var document : collection.listIndexes()) { + var keyDocument = (Document) document.get("key"); + if (keyDocument.containsKey(fieldName)) { + hasIndex = true; + } + } + if (!hasIndex) { + var indexOptions = new IndexOptions(); + indexOptions.unique(index.isUnique()); + if (index.isAscending()) { + collection.createIndex(Indexes.ascending(fieldName), indexOptions); + } else { + collection.createIndex(Indexes.descending(fieldName), indexOptions); + } + } + } + } + + var indexTextDefMap = entityDef.getIndexTextDefMap(); + if (CollectionUtils.isNotEmpty(indexTextDefMap)) { + AssertionUtils.isTrue(indexTextDefMap.size() == 1 + , StringUtils.format("一个集合的text索引[{}]只能有一个", JsonUtils.object2String(indexTextDefMap.keySet()))); + var collection = getCollection(entityDef.getClazz()); + for (var indexTextDef : indexTextDefMap.entrySet()) { + var fieldName = indexTextDef.getKey(); + var hasIndex = false; + for (var document : collection.listIndexes()) { + var keyDocument = (Document) document.get("key"); + if (keyDocument.containsKey(fieldName)) { + hasIndex = true; + } + } + if (!hasIndex) { + collection.createIndex(Indexes.text(fieldName)); + } + } + } + } + } + + private Map>, EntityDef> scanEntity() { + var cacheDefMap = new HashMap>, EntityDef>(); + var entityPackage = ormConfig.getEntityPackage(); + var cacheStrategies = ormConfig.getCachesConfig().getCacheStrategies(); + var persisterStrategies = ormConfig.getPersistersConfig().getPersisterStrategies(); + + var locationSet = getEntityLocation(entityPackage); + for (var location : locationSet) { + Class entityClazz; + try { + entityClazz = Class.forName(location); + } catch (ClassNotFoundException e) { + throw new RuntimeException(StringUtils.format("无法获取实体类[{}]", location)); + } + var cacheDef = EntityDef.valueOf(entityClazz, cacheStrategies, persisterStrategies); + var previousCacheDef = cacheDefMap.putIfAbsent((Class>) entityClazz, cacheDef); + AssertionUtils.isNull(previousCacheDef, "缓存实体不能包含重复的[class:{}]", entityClazz.getSimpleName()); + } + return cacheDefMap; + } + + private Set getEntityLocation(String scanLocation) { + var prefixPattern = "classpath*:"; + var suffixPattern = "**/*.class"; + + + var resourcePatternResolver = new PathMatchingResourcePatternResolver(); + var metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + try { + String packageSearchPath = prefixPattern + scanLocation.replace(StringUtils.PERIOD, StringUtils.SLASH) + StringUtils.SLASH + suffixPattern; + Resource[] resources = resourcePatternResolver.getResources(packageSearchPath); + Set result = new HashSet<>(); + String name = EntityCache.class.getName(); + for (Resource resource : resources) { + if (resource.isReadable()) { + MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource); + AnnotationMetadata annoMeta = metadataReader.getAnnotationMetadata(); + if (annoMeta.hasAnnotation(name)) { + ClassMetadata clazzMeta = metadataReader.getClassMetadata(); + result.add(clazzMeta.getClassName()); + } + } + } + return result; + } catch (IOException e) { + throw new RuntimeException("无法读取实体信息:" + e); + } + } + + @Override + public void initAfter() { + var unusableStorageClassList = entityCachesMap.entrySet().stream() + .filter(it -> !it.getValue().isUsable()) + .map(it -> it.getKey()) + .collect(Collectors.toList()); + + unusableStorageClassList.forEach(it -> { + entityCachesMap.remove(it); + }); + } + + @Override + public > IEntityCaches getEntityCaches(Class clazz) { + return (IEntityCaches) entityCachesMap.get(clazz); + } + + @Override + public Collection> getAllEntityCaches() { + return Collections.unmodifiableCollection(entityCachesMap.values()); + } + + @Override + public ClientSession getClientSession() { + return mongoClient.startSession(); + } + + @Override + public > MongoCollection getCollection(Class entityClazz) { + var collectionName = collectionNameMap.get(entityClazz); + if (collectionName == null) { + collectionName = StringUtils.substringBeforeLast(StringUtils.uncapitalize(entityClazz.getSimpleName()), "Entity"); + collectionNameMap.put(entityClazz, collectionName); + } + + return mongodbDatabase.getCollection(collectionName, entityClazz); + } + + + @Override + public MongoCollection getCollection(String collection) { + return mongodbDatabase.getCollection(collection); + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/accessor/IAccessor.java b/orm/src/main/java/com/zfoo/orm/model/accessor/IAccessor.java new file mode 100644 index 00000000..5001c6dd --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/accessor/IAccessor.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.accessor; + +import com.zfoo.orm.model.entity.IEntity; +import org.springframework.lang.Nullable; + +import java.util.List; + +/** + * 对数据库进行(增,删,改)的相关方法 + * + * @author jaysunxiao + * @version 3.0 + */ +public interface IAccessor { + + > boolean insert(E entity); + + > void batchInsert(List entities); + + > boolean update(E entity); + + > void batchUpdate(List entities); + + > boolean delete(E entity); + + > boolean delete(Object pk, Class entityClazz); + + > void batchDelete(List entities); + + > void batchDelete(List pks, Class entityClazz); + + @Nullable + > E load(Object pk, Class entityClazz); + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/accessor/MongodbAccessor.java b/orm/src/main/java/com/zfoo/orm/model/accessor/MongodbAccessor.java new file mode 100644 index 00000000..80c51b95 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/accessor/MongodbAccessor.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.accessor; + +import com.mongodb.client.model.BulkWriteOptions; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.ReplaceOneModel; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.protocol.collection.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.in; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MongodbAccessor implements IAccessor { + + private static final Logger logger = LoggerFactory.getLogger(MongodbAccessor.class); + + + @Override + public > boolean insert(E entity) { + var entityClazz = (Class) entity.getClass(); + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var result = collection.insertOne(entity); + return result.getInsertedId() != null; + } + + @Override + public > void batchInsert(List entities) { + if (CollectionUtils.isEmpty(entities)) { + return; + } + var entityClazz = (Class) entities.get(0).getClass(); + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + collection.insertMany(entities); + } + + @Override + public > boolean update(E entity) { + try { + var entityClazz = (Class) entity.getClass(); + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + + var filter = Filters.eq("_id", entity.id()); + + var result = collection.replaceOne(filter, entity); + if (result.getModifiedCount() <= 0) { + logger.error("数据库[{}]中没有[id:{}]的字段,或者需要更新的数据和数据库中的相同", entityClazz.getSimpleName(), entity.id()); + return false; + } + return true; + } catch (Throwable t) { + logger.error("更新update未知异常", t); + } + return false; + } + + @Override + public > void batchUpdate(List entities) { + if (CollectionUtils.isEmpty(entities)) { + return; + } + + try { + var entityClazz = (Class) entities.get(0).getClass(); + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + + var batchList = entities.stream() + .map(it -> new ReplaceOneModel(Filters.eq("_id", it.id()), it)) + .collect(Collectors.toList()); + + var result = collection.bulkWrite(batchList, new BulkWriteOptions().ordered(false)); + if (result.getModifiedCount() != entities.size()) { + logger.error("在数据库[{}]的批量更新操作中需要更新的数量[{}]和最终更新的数量[{}]不相同" + , entityClazz.getSimpleName(), entities.size(), result.getModifiedCount()); + } + } catch (Throwable t) { + logger.error("批量更新batchUpdate未知异常", t); + } + } + + @Override + public > boolean delete(E entity) { + var entityClazz = (Class) entity.getClass(); + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var result = collection.deleteOne(eq("_id", entity.id())); + return result.getDeletedCount() > 0; + } + + @Override + public > boolean delete(Object pk, Class entityClazz) { + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var result = collection.deleteOne(eq("_id", pk)); + return result.getDeletedCount() > 0; + } + + @Override + public > void batchDelete(List entities) { + if (CollectionUtils.isEmpty(entities)) { + return; + } + var entityClazz = (Class) entities.get(0).getClass(); + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var ids = entities.stream().map(it -> (it).id()).collect(Collectors.toList()); + collection.deleteMany(in("_id", ids)); + } + + @Override + public > void batchDelete(List pks, Class entityClazz) { + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + collection.deleteMany(in("_id", pks)); + } + + @Override + public > E load(Object pk, Class entityClazz) { + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var result = new ArrayList(1); + collection.find(eq("_id", pk)).forEach((Consumer) document -> result.add(document)); + if (CollectionUtils.isEmpty(result)) { + return null; + } + return result.get(0); + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/anno/EntityCache.java b/orm/src/main/java/com/zfoo/orm/model/anno/EntityCache.java new file mode 100644 index 00000000..49b97f59 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/anno/EntityCache.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface EntityCache { + + String cacheStrategy(); + + Persister persister() default @Persister; + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/anno/EntityCachesInjection.java b/orm/src/main/java/com/zfoo/orm/model/anno/EntityCachesInjection.java new file mode 100644 index 00000000..0c4553f9 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/anno/EntityCachesInjection.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface EntityCachesInjection { +} diff --git a/orm/src/main/java/com/zfoo/orm/model/anno/Id.java b/orm/src/main/java/com/zfoo/orm/model/anno/Id.java new file mode 100644 index 00000000..c8eb900c --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/anno/Id.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Id { +} diff --git a/orm/src/main/java/com/zfoo/orm/model/anno/Index.java b/orm/src/main/java/com/zfoo/orm/model/anno/Index.java new file mode 100644 index 00000000..d6b29583 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/anno/Index.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Index { + boolean ascending(); + + boolean unique(); +} diff --git a/orm/src/main/java/com/zfoo/orm/model/anno/IndexText.java b/orm/src/main/java/com/zfoo/orm/model/anno/IndexText.java new file mode 100644 index 00000000..f75d48a1 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/anno/IndexText.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.anno; + +import java.lang.annotation.*; + +/** + * Mongodb不支持中文分词 + * + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface IndexText { + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/anno/Persister.java b/orm/src/main/java/com/zfoo/orm/model/anno/Persister.java new file mode 100644 index 00000000..11b6e343 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/anno/Persister.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Persister { + + String value() default "default"; + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/cache/EntityCaches.java b/orm/src/main/java/com/zfoo/orm/model/cache/EntityCaches.java new file mode 100644 index 00000000..41523b20 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/cache/EntityCaches.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.cache; + +import com.github.benmanes.caffeine.cache.*; +import com.mongodb.WriteConcern; +import com.mongodb.client.model.BulkWriteOptions; +import com.mongodb.client.model.Filters; +import com.mongodb.client.model.ReplaceOneModel; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.orm.model.persister.IOrmPersister; +import com.zfoo.orm.model.persister.PNode; +import com.zfoo.orm.model.query.Page; +import com.zfoo.orm.model.vo.EntityDef; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.util.TimeUtils; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EntityCaches, E extends IEntity> implements IEntityCaches { + + private static final Logger logger = LoggerFactory.getLogger(EntityCaches.class); + + private static final int BATCH_SIZE = 512; + + private EntityDef entityDef; + + private LoadingCache> cache; + + private boolean usable; + + public EntityCaches(EntityDef entityDef) { + this.entityDef = entityDef; + + this.cache = Caffeine.newBuilder() + .expireAfterAccess(entityDef.getExpireMillisecond(), TimeUnit.MILLISECONDS) + .maximumSize(entityDef.getCacheSize()) + .initialCapacity(CollectionUtils.comfortableCapacity(entityDef.getCacheSize())) + .recordStats() // 开启统计信息开关,cache.stats()获取统计信息 + .removalListener(new RemovalListener>() { + @Override + public void onRemoval(@Nullable PK pk, @Nullable PNode pnode, @NonNull RemovalCause removalCause) { + if (pnode.getWriteToDbTime() == pnode.getModifiedTime()) { + return; + } + + // 缓存失效之前,将数据写入数据库 + var entity = pnode.getEntity(); + var collection = OrmContext.getOrmManager().getCollection((Class) entityDef.getClazz()); + + var version = entity.gvs(); + entity.svs(version + 1); + + var filter = entity.gvs() > 0 + ? Filters.and(Filters.eq("_id", entity.id()), Filters.eq("vs", version)) + : Filters.eq("_id", entity.id()); + var result = collection.replaceOne(filter, entity); + if (result.getModifiedCount() <= 0) { + logger.error("移除[removalCause:{}]缓存时,更新数据库[{}]中的实体主键[pk:{}]的文档异常" + , removalCause, entityDef.getClazz().getSimpleName(), entity.id()); + } + } + }) + .build(new CacheLoader>() { + @Override + public @Nullable PNode load(@NonNull PK pk) { + var entity = (E) OrmContext.getAccessor().load(pk, (Class>) entityDef.getClazz()); + + // 如果数据库中不存在则给一个默认值 + if (entity == null) { + entity = (E) entityDef.newEntity(pk); + logger.error("数据库[{}]没有包含主键[pk:{}]的文档,返回默认值", entityDef.getClazz().getSimpleName(), pk); + } + + return new PNode(entity); + } + }); + + if (CollectionUtils.isNotEmpty(entityDef.getIndexDefMap())) { + // indexMap + } + + if (CollectionUtils.isNotEmpty(entityDef.getIndexTextDefMap())) { + // indexText + } + + var persisterDef = entityDef.getPersisterStrategy(); + IOrmPersister persister = persisterDef.getType().createPersister(entityDef, this); + persister.start(); + } + + + @Override + public E load(PK pk) { + AssertionUtils.notNull(pk); + try { + return cache.get(pk).getEntity(); + } catch (Exception e) { + logger.error("数据库[{}]缓存[pk:{}]加载发生exception异常", entityDef.getClazz().getSimpleName(), pk, e); + } catch (Throwable t) { + logger.error("数据库[{}]缓存[pk:{}]加载发生error异常", entityDef.getClazz().getSimpleName(), pk, t); + } + + logger.error("数据库[{}]无法加载缓存[pk:{}],返回默认值", entityDef.getClazz().getSimpleName(), pk); + var entity = (E) entityDef.newEntity(pk); + var pnode = new PNode(entity); + cache.put(pk, pnode); + return entity; + } + + @Override + public void update(E entity) { + AssertionUtils.notNull(entity); + + var currentPnode = cache.getIfPresent(entity.id()); + + if (currentPnode == null) { + currentPnode = new PNode<>(entity); + cache.put(entity.id(), currentPnode); + } + + // 加100以防止,立刻加载并且立刻修改数据的情况发生时,服务器取到的时间戳相同 + currentPnode.setModifiedTime(TimeUtils.now() + 100); + } + + @Override + public void invalidate(PK pk) { + // 游戏业务中,操作最频繁的是update,不是insert,delete,query + // 所以这边并不考虑 + AssertionUtils.notNull(pk); + cache.invalidate(pk); + } + + // 游戏中80%都是执行更新的操作,这样做会极大的提高更新速度 + @Override + public void persistAll() { + try { + var allPnodes = cache.asMap().values(); + + if (allPnodes.isEmpty()) { + return; + } + + var updateList = new ArrayList(); + var currentTime = TimeUtils.currentTimeMillis(); + for (var pnode : allPnodes) { + var entity = pnode.getEntity(); + if (pnode.getModifiedTime() != pnode.getWriteToDbTime()) { + pnode.setWriteToDbTime(currentTime); + pnode.setModifiedTime(currentTime); + updateList.add(entity); + continue; + } + + if (currentTime - pnode.getModifiedTime() >= entityDef.getExpireMillisecond()) { + invalidate(pnode.getEntity().id()); + } + } + + // 执行更新 + if (updateList.isEmpty()) { + return; + } + + var page = Page.valueOf(1, BATCH_SIZE, updateList.size()); + var maxPageSize = page.totalPage(); + + for (var currentPage = 1; currentPage <= maxPageSize; currentPage++) { + page.setPage(currentPage); + var currentUpdateList = page.currentPageList(updateList); + try { + var collection = OrmContext.getOrmManager().getCollection((Class) entityDef.getClazz()).withWriteConcern(WriteConcern.ACKNOWLEDGED); + + var batchList = currentUpdateList.stream() + .map(it -> { + var version = it.gvs(); + it.svs(version + 1); + + var filter = it.gvs() > 0 + ? Filters.and(Filters.eq("_id", it.id()), Filters.eq("vs", version)) + : Filters.eq("_id", it.id()); + + return new ReplaceOneModel<>(filter, it); + }) + .collect(Collectors.toList()); + + var result = collection.bulkWrite(batchList, new BulkWriteOptions().ordered(false)); + if (result.getModifiedCount() == batchList.size()) { + continue; + } + + logger.error("在数据库[{}]的批量更新操作中需要更新的数量[{}]和最终更新的数量[{}]不相同,开始执行容错操作" + , entityDef.getClazz().getSimpleName(), currentUpdateList.size(), result.getModifiedCount()); + persistAllAndCompare(currentUpdateList); + } catch (Throwable t) { + logger.error("数据库[{}]批量更新操作未知异常,开始执行容错操作", entityDef.getClazz().getSimpleName(), t); + persistAllAndCompare(currentUpdateList); + } + } + + updateList.clear(); + + } catch (Exception e) { + logger.error("数据库持久化器[{}]的持久化过程中exception异常退出[e:{}]", entityDef.getClazz().getSimpleName(), e); + } catch (Throwable t) { + logger.error("数据库持久化器[{}]的持久化过程中throwable异常退出[t:{}]", entityDef.getClazz().getSimpleName(), t); + } finally { + } + } + + private void persistAllAndCompare(List updateList) { + if (CollectionUtils.isEmpty(updateList)) { + return; + } + + var ids = updateList.stream().map(it -> it.id()).collect(Collectors.toList()); + + try { + var dbList = OrmContext.getQuery().queryFieldIn("_id", ids, (Class) entityDef.getClazz()); + var dbMap = dbList.stream().collect(Collectors.toMap(key -> key.id(), value -> value)); + for (var entity : updateList) { + var dbEntity = dbMap.get(entity.id()); + + if (dbEntity == null) { + cache.invalidate(entity.id()); + continue; + } + + // 如果没有版本号,则写入数据库并清除缓存 + if (entity.gvs() <= 0) { + OrmContext.getAccessor().update(entity); + cache.invalidate(entity.id()); + continue; + } + + // 如果版本号相同,说明已经更新到 + if (dbEntity.gvs() == entity.gvs()) { + cache.invalidate(entity.id()); + continue; + } + + // 如果数据库版本号较大,说明缓存的数据不是最新的,直接清除缓存,下次重新加载 + if (dbEntity.gvs() > entity.gvs()) { + cache.invalidate(entity.id()); + continue; + } + + // 如果数据库版本号较小,说明缓存的数据是最新的,直接写入数据库 + if (dbEntity.gvs() < entity.gvs()) { + OrmContext.getAccessor().update(entity); + cache.invalidate(entity.id()); + continue; + } + } + } catch (Throwable t) { + logger.error("数据库[{}]容错操作异常,", entityDef.getClazz().getSimpleName(), t); + } + } + + @Override + public List allPresentCaches() { + var allPnodes = cache.asMap().values(); + + if (allPnodes.isEmpty()) { + return Collections.emptyList(); + } + return allPnodes.stream().map(it -> it.getEntity()).collect(Collectors.toList()); + } + + @Override + public void forEach(BiConsumer biConsumer) { + cache.asMap().forEach((pk, pNode) -> biConsumer.accept(pk, pNode.getEntity())); + } + + @Override + public long size() { + return cache.estimatedSize(); + } + + @Override + public boolean isUsable() { + return this.usable; + } + + @Override + public void setUsable(boolean usable) { + this.usable = usable; + } + + @Override + public String recordStatus() { + var stats = cache.stats(); + return StringUtils.format("数据库[{}]缓存命中率[hitRate:{}],命中次数[hitCount:{}],加载次数[loadCount:{}],加载新值的平均时间秒[averageLoadPenalty:{}],缓存项被回收的总数[evictionCount:{}]" + , entityDef.getClazz().getSimpleName(), stats.hitRate(), stats.hitCount(), stats.loadCount(), stats.averageLoadPenalty() / TimeUtils.NANO_PER_SECOND, stats.evictionCount()); + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/cache/IEntityCaches.java b/orm/src/main/java/com/zfoo/orm/model/cache/IEntityCaches.java new file mode 100644 index 00000000..38ed1e1f --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/cache/IEntityCaches.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.cache; + +import com.zfoo.orm.model.entity.IEntity; + +import java.util.List; +import java.util.function.BiConsumer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IEntityCaches, E extends IEntity> { + + /** + * 从数据库中加载数据到缓存,如果数据库不存在则返回一个id为空的默认值,并且将这个默认值加入缓存 + */ + E load(PK pk); + + /** + * 更新缓存中的数据,只更新缓存的时间戳,并通过一定策略写入到数据库 + */ + void update(E entity); + + /** + * 不会删除数据库中的数据,只会删除缓存数据 + * + * @param pk 组要删除的主键 + */ + void invalidate(PK pk); + + /** + * 持久化所有缓存数据 + */ + void persistAll(); + + /** + * 获取所有存在的缓存对象 + */ + List allPresentCaches(); + + void forEach(BiConsumer biConsumer); + + long size(); + + boolean isUsable(); + + void setUsable(boolean usable); + + /** + * 统计缓存命中率 + */ + String recordStatus(); + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/CacheStrategy.java b/orm/src/main/java/com/zfoo/orm/model/config/CacheStrategy.java new file mode 100644 index 00000000..5db86767 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/CacheStrategy.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CacheStrategy { + + private String strategy; + private int size; + private long expireMillisecond; + + public CacheStrategy(String strategy, int cacheSize, long expireMillisecond) { + this.strategy = strategy; + this.size = cacheSize; + this.expireMillisecond = expireMillisecond; + } + + + public String getStrategy() { + return strategy; + } + + public void setStrategy(String strategy) { + this.strategy = strategy; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public long getExpireMillisecond() { + return expireMillisecond; + } + + public void setExpireMillisecond(long expireMillisecond) { + this.expireMillisecond = expireMillisecond; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/CachesConfig.java b/orm/src/main/java/com/zfoo/orm/model/config/CachesConfig.java new file mode 100644 index 00000000..cf725af9 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/CachesConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CachesConfig { + + private List cacheStrategies; + + public List getCacheStrategies() { + return cacheStrategies; + } + + public void setCacheStrategies(List cacheStrategies) { + this.cacheStrategies = cacheStrategies; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/HostConfig.java b/orm/src/main/java/com/zfoo/orm/model/config/HostConfig.java new file mode 100644 index 00000000..e7e2997c --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/HostConfig.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class HostConfig { + + private String database; + private String user; + private String password; + private Map addressMap; + + public static HostConfig valueOf(String database, String user, String password, Map addressMap) { + HostConfig config = new HostConfig(); + config.database = database; + config.user = user; + config.password = password; + config.addressMap = addressMap; + return config; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Map getAddressMap() { + return addressMap; + } + + public void setAddressMap(Map addressMap) { + this.addressMap = addressMap; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/OrmConfig.java b/orm/src/main/java/com/zfoo/orm/model/config/OrmConfig.java new file mode 100644 index 00000000..4dfabb47 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/OrmConfig.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class OrmConfig { + + private String id; + + private String entityPackage; + + private HostConfig hostConfig; + + private CachesConfig cachesConfig; + + private PersistersConfig persistersConfig; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEntityPackage() { + return entityPackage; + } + + public void setEntityPackage(String entityPackage) { + this.entityPackage = entityPackage; + } + + public HostConfig getHostConfig() { + return hostConfig; + } + + public void setHostConfig(HostConfig hostConfig) { + this.hostConfig = hostConfig; + } + + public CachesConfig getCachesConfig() { + return cachesConfig; + } + + public void setCachesConfig(CachesConfig cachesConfig) { + this.cachesConfig = cachesConfig; + } + + public PersistersConfig getPersistersConfig() { + return persistersConfig; + } + + public void setPersistersConfig(PersistersConfig persistersConfig) { + this.persistersConfig = persistersConfig; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/PersisterStrategy.java b/orm/src/main/java/com/zfoo/orm/model/config/PersisterStrategy.java new file mode 100644 index 00000000..2459ebe7 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/PersisterStrategy.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PersisterStrategy { + + private String strategy; + + private PersisterTypeEnum type; + + private String config; + + public PersisterStrategy(String strategy, String type, String config) { + this.strategy = strategy; + this.config = config; + this.type = PersisterTypeEnum.getPersisterType(type); + } + + + public String getStrategy() { + return strategy; + } + + public void setStrategy(String strategy) { + this.strategy = strategy; + } + + public PersisterTypeEnum getType() { + return type; + } + + public void setType(PersisterTypeEnum type) { + this.type = type; + } + + public String getConfig() { + return config; + } + + public void setConfig(String config) { + this.config = config; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/PersisterTypeEnum.java b/orm/src/main/java/com/zfoo/orm/model/config/PersisterTypeEnum.java new file mode 100644 index 00000000..24fa0307 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/PersisterTypeEnum.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +import com.zfoo.orm.model.cache.EntityCaches; +import com.zfoo.orm.model.persister.CronOrmPersister; +import com.zfoo.orm.model.persister.IOrmPersister; +import com.zfoo.orm.model.persister.TimeOrmPersister; +import com.zfoo.orm.model.vo.EntityDef; +import com.zfoo.protocol.util.StringUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public enum PersisterTypeEnum { + + QUEUE { + @Override + public IOrmPersister createPersister(EntityDef entityDef, EntityCaches entityCaches) { + return null; + } + }, + CRON { + @Override + public IOrmPersister createPersister(EntityDef entityDef, EntityCaches entityCaches) { + return new CronOrmPersister(entityDef, entityCaches); + } + }, + TIME { + @Override + public IOrmPersister createPersister(EntityDef entityDef, EntityCaches entityCaches) { + return new TimeOrmPersister(entityDef, entityCaches); + } + }; + + + public static PersisterTypeEnum getPersisterType(String persisterType) { + for (PersisterTypeEnum persister : values()) { + if (persister.name().equalsIgnoreCase(persisterType)) { + return persister; + } + } + throw new IllegalArgumentException(StringUtils.format("无效的持久化类型[persisterType:{}]", persisterType)); + } + + public abstract IOrmPersister createPersister(EntityDef entityDef, EntityCaches entityCaches); + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/config/PersistersConfig.java b/orm/src/main/java/com/zfoo/orm/model/config/PersistersConfig.java new file mode 100644 index 00000000..a887127f --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/config/PersistersConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.config; + +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PersistersConfig { + + private List persisterStrategies; + + public List getPersisterStrategies() { + return persisterStrategies; + } + + public void setPersisterStrategies(List persisterStrategies) { + this.persisterStrategies = persisterStrategies; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/entity/IEntity.java b/orm/src/main/java/com/zfoo/orm/model/entity/IEntity.java new file mode 100644 index 00000000..c5ed85f4 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/entity/IEntity.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.entity; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IEntity> { + + /** + * 一个文档的主键 + */ + PK id(); + + + /** + * 一个文档的写入到数据库的version版本,version的get和set方法 + */ + default long gvs() { + return 0L; + } + + default void svs(long vs) { + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/persister/AbstractOrmPersister.java b/orm/src/main/java/com/zfoo/orm/model/persister/AbstractOrmPersister.java new file mode 100644 index 00000000..0c1b01bb --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/persister/AbstractOrmPersister.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.persister; + +import com.zfoo.orm.model.cache.EntityCaches; +import com.zfoo.orm.model.vo.EntityDef; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AbstractOrmPersister implements IOrmPersister { + + protected EntityDef entityDef; + + protected EntityCaches entityCaches; + + + public AbstractOrmPersister(EntityDef entityDef, EntityCaches entityCaches) { + this.entityDef = entityDef; + this.entityCaches = entityCaches; + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/persister/CronOrmPersister.java b/orm/src/main/java/com/zfoo/orm/model/persister/CronOrmPersister.java new file mode 100644 index 00000000..186c45f6 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/persister/CronOrmPersister.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.persister; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.model.cache.EntityCaches; +import com.zfoo.orm.model.vo.EntityDef; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.scheduler.SchedulerContext; +import com.zfoo.scheduler.util.TimeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.support.CronExpression; + +import java.util.concurrent.TimeUnit; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CronOrmPersister extends AbstractOrmPersister { + + private static final Logger logger = LoggerFactory.getLogger(CronOrmPersister.class); + + /** + * 持久化默认的延迟时间 + */ + private static final long DEFAULT_DELAY = 30 * TimeUtils.MILLIS_PER_SECOND; + + /** + * cron表达式 + */ + private CronExpression cronExpression; + + + public CronOrmPersister(EntityDef entityDef, EntityCaches entityCaches) { + super(entityDef, entityCaches); + this.cronExpression = CronExpression.parse(entityDef.getPersisterStrategy().getConfig()); + } + + + @Override + public void start() { + schedulePersist(); + } + + private void schedulePersist() { + var delay = 0L; + try { + var now = TimeUtils.now(); + var nextTimestamp = TimeUtils.getNextTimestampByCronExpression(cronExpression, now); + delay = nextTimestamp - now; + + if (delay < 0) { + delay = DEFAULT_DELAY; + logger.error("计算[cron:{}]表达式发生错误,当前时间[now:{}],计算出来的时间[nextTimestamp:{}],两者之差小于0" + , cronExpression.toString(), now, nextTimestamp); + } + } catch (Exception e) { + delay = DEFAULT_DELAY; + logger.error(ExceptionUtils.getMessage(e)); + } + + if (!OrmContext.isStop()) { + SchedulerContext.getSchedulerManager().schedule(new Runnable() { + @Override + public void run() { + if (!OrmContext.isStop()) { + EventBus.asyncExecute().execute(new Runnable() { + @Override + public void run() { + entityCaches.persistAll(); + schedulePersist(); + } + }); + } + } + }, delay, TimeUnit.MILLISECONDS); + } + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/persister/IOrmPersister.java b/orm/src/main/java/com/zfoo/orm/model/persister/IOrmPersister.java new file mode 100644 index 00000000..91e48703 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/persister/IOrmPersister.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.persister; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IOrmPersister { + + void start(); + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/persister/PNode.java b/orm/src/main/java/com/zfoo/orm/model/persister/PNode.java new file mode 100644 index 00000000..23615c18 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/persister/PNode.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.persister; + +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.scheduler.util.TimeUtils; + +/** + * Persister Node + *

+ * 需要持久化的一个节点 + * + * @author jaysunxiao + * @version 3.0 + */ +public class PNode> { + + // 写入数据库的时间 + private volatile long writeToDbTime; + // 修改数据的时间 + private volatile long modifiedTime; + + + private volatile E entity; + + public PNode(E entity) { + this.entity = entity; + + var currentTime = TimeUtils.now(); + this.writeToDbTime = currentTime; + this.modifiedTime = currentTime; + } + + public E getEntity() { + return entity; + } + + public void setEntity(E entity) { + this.entity = entity; + } + + public long getWriteToDbTime() { + return writeToDbTime; + } + + public void setWriteToDbTime(long writeToDbTime) { + this.writeToDbTime = writeToDbTime; + } + + public long getModifiedTime() { + return modifiedTime; + } + + public void setModifiedTime(long modifiedTime) { + this.modifiedTime = modifiedTime; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/persister/TimeOrmPersister.java b/orm/src/main/java/com/zfoo/orm/model/persister/TimeOrmPersister.java new file mode 100644 index 00000000..6c00d907 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/persister/TimeOrmPersister.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.persister; + +import com.zfoo.event.manager.EventBus; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.model.cache.EntityCaches; +import com.zfoo.orm.model.vo.EntityDef; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.SchedulerContext; + +import java.util.concurrent.TimeUnit; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TimeOrmPersister extends AbstractOrmPersister { + + /** + * 执行的频率 + */ + private long rate; + + public TimeOrmPersister(EntityDef entityDef, EntityCaches entityCaches) { + super(entityDef, entityCaches); + this.rate = Long.parseLong(entityDef.getPersisterStrategy().getConfig()); + if (this.rate <= 0) { + throw new RuntimeException(StringUtils.format("刷新频率[{}]不能小于0", rate)); + } + } + + @Override + public void start() { + SchedulerContext.getSchedulerManager().scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + if (!OrmContext.isStop()) { + EventBus.asyncExecute().execute(new Runnable() { + @Override + public void run() { + entityCaches.persistAll(); + } + }); + } + } + }, rate, TimeUnit.MILLISECONDS); + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/query/IQuery.java b/orm/src/main/java/com/zfoo/orm/model/query/IQuery.java new file mode 100644 index 00000000..55880980 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/query/IQuery.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.query; + +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.protocol.model.Pair; + +import java.util.List; + +/** + * 对数据库进行(查找)的相关方法 + * + * @author jaysunxiao + * @version 3.0 + */ +public interface IQuery { + + > List queryFieldLike(String fieldName, String fieldValue, Class entityClazz); + + > List queryAll(Class entityClazz); + + > List queryFieldEqual(String fieldName, Object fieldValue, Class entityClazz); + + > List queryFieldIn(String fieldName, List fieldValueList, Class entityClazz); + + /** + * 分页查询,默认按照id排序 + * + * @param page 第几页 + * @param itemsPerPage 每页容量 + * @param entityClazz 实体类 + */ + > Pair> pageQuery(int page, int itemsPerPage, Class entityClazz); + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/query/MongodbQuery.java b/orm/src/main/java/com/zfoo/orm/model/query/MongodbQuery.java new file mode 100644 index 00000000..b691fa22 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/query/MongodbQuery.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.query; + +import com.mongodb.client.model.Filters; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MongodbQuery implements IQuery { + + @Override + public > List queryAll(Class entityClazz) { + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var list = new ArrayList(); + collection.find().forEach(new Consumer>() { + @Override + public void accept(IEntity entity) { + list.add((E) entity); + } + }); + return list; + } + + @Override + public > List queryFieldEqual(String fieldName, Object fieldValue, Class entityClazz) { + if (fieldValue == null) { + return Collections.emptyList(); + } + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var list = new ArrayList(); + collection.find(Filters.eq(fieldName, fieldValue)).forEach(new Consumer>() { + @Override + public void accept(IEntity entity) { + list.add((E) entity); + } + }); + return list; + } + + @Override + public > List queryFieldLike(String fieldName, String fieldValue, Class entityClazz) { + if (StringUtils.isBlank(fieldValue)) { + return Collections.emptyList(); + } + var collection = OrmContext.getOrmManager().getCollection((Class>) entityClazz); + var list = new ArrayList(); + var regex = StringUtils.format("^{}.*", fieldValue); + collection.find(Filters.regex(fieldName, regex)).forEach((Consumer>) entity -> list.add((E) entity)); + return list; + } + + @Override + public > List queryFieldIn(String fieldName, List fieldValueList, Class entityClazz) { + if (CollectionUtils.isEmpty(fieldValueList)) { + return Collections.emptyList(); + } + + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + var list = new ArrayList(); + collection.find(Filters.in(fieldName, fieldValueList)).forEach(new Consumer>() { + @Override + public void accept(IEntity entity) { + list.add((E) entity); + } + }); + return list; + } + + @Override + public > Pair> pageQuery(int page, int itemsPerPage, Class entityClazz) { + var collection = OrmContext.getOrmManager().getCollection(entityClazz); + + var p = Page.valueOf(page, itemsPerPage, collection.countDocuments()); + + var list = new ArrayList(); + collection.find() + .skip(p.skipNum()) + .limit(p.getItemsPerPage()) + .forEach(new Consumer>() { + @Override + public void accept(IEntity entity) { + list.add((E) entity); + } + }); + + return new Pair<>(p, list); + } + + +} diff --git a/orm/src/main/java/com/zfoo/orm/model/query/Page.java b/orm/src/main/java/com/zfoo/orm/model/query/Page.java new file mode 100644 index 00000000..8e2d5bfc --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/query/Page.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.query; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.util.Collections; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Page { + + /** + * 第几页 + */ + private int page; + /** + * 页容量 + */ + private int itemsPerPage; + + /** + * 数据库记录总数量 + */ + private long totalSize; + + public static Page valueOf(int page, int itemsPerPage, long totalSize) { + if (page <= 0) { + throw new IllegalArgumentException(StringUtils.format("分页数必须大于0,[page:{}]", page)); + } + if (itemsPerPage <= 0) { + throw new IllegalArgumentException(StringUtils.format("页容量必须大于0,[size:{}]", itemsPerPage)); + } + if (totalSize < 0) { + throw new IllegalArgumentException(StringUtils.format("总数两必须大于等于0,[size:{}]", totalSize)); + } + var p = new Page(); + p.page = page; + p.totalSize = totalSize; + p.itemsPerPage = itemsPerPage; + return p; + } + + + private Page() { + } + + public List currentPageList(List list) { + if (CollectionUtils.isEmpty(list)) { + return Collections.emptyList(); + } + + var skip = skipNum(); + var to = skip + itemsPerPage; + + if (skip >= list.size()) { + return Collections.emptyList(); + } + + if (to >= list.size()) { + to = list.size(); + } + + return list.subList(skip, to); + } + + public int skipNum() { + return (page - 1) * itemsPerPage; + } + + public int totalPage() { + return (int) ((totalSize + itemsPerPage - 1) / itemsPerPage); + } + + public int getPage() { + return page; + } + + public void setPage(int page) { + this.page = page; + } + + public int getItemsPerPage() { + return itemsPerPage; + } + + public void setItemsPerPage(int itemsPerPage) { + this.itemsPerPage = itemsPerPage; + } + + public long getTotalSize() { + return totalSize; + } + + public void setTotalSize(long totalSize) { + this.totalSize = totalSize; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/vo/EntityDef.java b/orm/src/main/java/com/zfoo/orm/model/vo/EntityDef.java new file mode 100644 index 00000000..fa9b5b87 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/vo/EntityDef.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.vo; + +import com.zfoo.orm.model.anno.EntityCache; +import com.zfoo.orm.model.anno.Id; +import com.zfoo.orm.model.anno.Index; +import com.zfoo.orm.model.anno.IndexText; +import com.zfoo.orm.model.config.CacheStrategy; +import com.zfoo.orm.model.config.PersisterStrategy; +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EntityDef { + + private Field idField; + + private Class> clazz; + + private int cacheSize; + + private long expireMillisecond; + + private PersisterStrategy persisterStrategy; + + private Map indexDefMap; + + private Map indexTextDefMap; + + private EntityDef() { + } + + public static EntityDef valueOf(Class clazz, List cacheStrategies, List persisterStrategies) { + if (!IEntity.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException(StringUtils.format("被[{}]注解标注的实体类[{}]必须继承[{}]", EntityCache.class.getName(), clazz.getName(), IEntity.class.getName())); + } + + var entityDef = new EntityDef(); + entityDef.clazz = (Class>) clazz; + try { + ReflectionUtils.makeAccessible(clazz.getDeclaredConstructor()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(StringUtils.format("实体类Entity[{}]必须包含一个默认的构造器", clazz.getSimpleName())); + } + + var cache = clazz.getAnnotation(EntityCache.class); + AssertionUtils.notNull(cache); + var cacheStrategyOptional = cacheStrategies.stream().filter(it -> it.getStrategy().equals(cache.cacheStrategy())).findFirst(); + if (cacheStrategyOptional.isEmpty()) { + throw new RuntimeException(StringUtils.format("实体类Entity[{}]没有找到缓存策略[{}]", clazz.getSimpleName(), cache.cacheStrategy())); + } + var cacheStrategy = cacheStrategyOptional.get(); + entityDef.cacheSize = cacheStrategy.getSize(); + entityDef.expireMillisecond = cacheStrategy.getExpireMillisecond(); + + var idFields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class); + AssertionUtils.isTrue(CollectionUtils.isNotEmpty(idFields) && idFields.length == 1, "实体类Entity[{}]必须只有且仅有一个Id注解", clazz.getSimpleName()); + entityDef.idField = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class)[0]; + ReflectionUtils.makeAccessible(entityDef.idField); + // idField必须用private修饰 + if (!Modifier.isPrivate(entityDef.idField.getModifiers())) { + throw new RuntimeException(StringUtils.format("实体类Entity[{}]的id必须是private私有的", clazz.getSimpleName())); + } + + // entity必须包含属性的get和set方法 + Arrays.stream(clazz.getDeclaredFields()) + .filter(it -> !Modifier.isTransient(it.getModifiers())) + .forEach(it -> { + ReflectionUtils.fieldToGetMethod(clazz, it); + ReflectionUtils.fieldToSetMethod(clazz, it); + }); + var persister = cache.persister(); + AssertionUtils.notNull(persister); + var persisterStrategyOptional = persisterStrategies.stream().filter(it -> it.getStrategy().equals(persister.value())).findFirst(); + if (persisterStrategyOptional.isEmpty()) { + throw new RuntimeException(StringUtils.format("实体类Entity[{}]没有找到持久化策略[{}]", clazz.getSimpleName(), persister)); + } + entityDef.persisterStrategy = persisterStrategyOptional.get(); + + var indexDefMap = new HashMap(); + var fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Index.class); + for (Field field : fields) { + var indexAnnotation = field.getAnnotation(Index.class); + IndexDef indexDef = new IndexDef(field, indexAnnotation.ascending(), indexAnnotation.unique()); + indexDefMap.put(field.getName(), indexDef); + } + + var indexTextDefMap = new HashMap(); + fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, IndexText.class); + for (Field field : fields) { + IndexTextDef indexTextDef = new IndexTextDef(field, field.getAnnotation(IndexText.class)); + indexTextDefMap.put(field.getName(), indexTextDef); + } + + entityDef.indexDefMap = indexDefMap; + entityDef.indexTextDefMap = indexTextDefMap; + + return entityDef; + } + + + public Class> getClazz() { + return clazz; + } + + public IEntity newEntity(Object id) { + var entity = ReflectionUtils.newInstance(clazz); + return entity; + } + + public int getCacheSize() { + return cacheSize; + } + + public long getExpireMillisecond() { + return expireMillisecond; + } + + public PersisterStrategy getPersisterStrategy() { + return persisterStrategy; + } + + public Map getIndexDefMap() { + return indexDefMap; + } + + public Map getIndexTextDefMap() { + return indexTextDefMap; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/vo/IndexDef.java b/orm/src/main/java/com/zfoo/orm/model/vo/IndexDef.java new file mode 100644 index 00000000..7bafeb28 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/vo/IndexDef.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.vo; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class IndexDef { + + private Field field; + private boolean ascending; + private boolean unique; + + public IndexDef(Field field, boolean ascending, boolean unique) { + this.field = field; + this.ascending = ascending; + this.unique = unique; + } + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + } + + public boolean isAscending() { + return ascending; + } + + public void setAscending(boolean ascending) { + this.ascending = ascending; + } + + public boolean isUnique() { + return unique; + } + + public void setUnique(boolean unique) { + this.unique = unique; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/model/vo/IndexTextDef.java b/orm/src/main/java/com/zfoo/orm/model/vo/IndexTextDef.java new file mode 100644 index 00000000..281a3c52 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/model/vo/IndexTextDef.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.model.vo; + +import com.zfoo.orm.model.anno.IndexText; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class IndexTextDef { + + private Field field; + + public IndexTextDef(Field field, IndexText indexText) { + AssertionUtils.notNull(field); + + // 是否被private修饰 + if (!Modifier.isPrivate(field.getModifiers())) { + throw new IllegalArgumentException(StringUtils.format("[{}]没有被private修饰", field.getName())); + } + + // 唯一索引不能有set方法,为了避免客户端改变javabean中的属性 + Class clazz = field.getDeclaringClass(); + AssertionUtils.notNull(clazz); + this.field = field; + ReflectionUtils.makeAccessible(field); + } + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/schema/NamespaceHandler.java b/orm/src/main/java/com/zfoo/orm/schema/NamespaceHandler.java new file mode 100644 index 00000000..96fec2d0 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/schema/NamespaceHandler.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.schema; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NamespaceHandler extends NamespaceHandlerSupport { + + private final String ORM = "config"; + + @Override + public void init() { + registerBeanDefinitionParser(ORM, new OrmDefinitionParser()); + } + +} diff --git a/orm/src/main/java/com/zfoo/orm/schema/OrmDefinitionParser.java b/orm/src/main/java/com/zfoo/orm/schema/OrmDefinitionParser.java new file mode 100644 index 00000000..c57eb6d3 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/schema/OrmDefinitionParser.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.schema; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.manager.OrmManager; +import com.zfoo.orm.model.accessor.MongodbAccessor; +import com.zfoo.orm.model.config.*; +import com.zfoo.orm.model.query.MongodbQuery; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.DomUtils; +import org.springframework.beans.factory.config.BeanDefinitionHolder; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.support.ManagedMap; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class OrmDefinitionParser implements BeanDefinitionParser { + + @Override + public AbstractBeanDefinition parse(Element element, ParserContext parserContext) { + + Class clazz; + String name; + BeanDefinitionBuilder builder; + + // 注册NetConfig + parseOrmConfig(element, parserContext); + + // 注册OrmSpringContext + clazz = OrmContext.class; + name = StringUtils.uncapitalize(clazz.getSimpleName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册OrmProcessor + clazz = OrmProcessor.class; + name = StringUtils.uncapitalize(clazz.getSimpleName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册OrmManager + clazz = OrmManager.class; + name = StringUtils.uncapitalize(clazz.getSimpleName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + builder.addPropertyReference("ormConfig", OrmConfig.class.getCanonicalName()); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册MongodbAccessor + clazz = MongodbAccessor.class; + name = StringUtils.uncapitalize(clazz.getSimpleName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册MongodbQuery + clazz = MongodbQuery.class; + name = StringUtils.uncapitalize(clazz.getSimpleName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + return builder.getBeanDefinition(); + } + + private void parseOrmConfig(Element element, ParserContext parserContext) { + var clazz = OrmConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("id", "id", builder, element, parserContext); + resolvePlaceholder("entity-package", "entityPackage", builder, element, parserContext); + + parseHostConfig(DomUtils.getFirstChildElementByTagName(element, "host"), parserContext); + builder.addPropertyReference("hostConfig", HostConfig.class.getCanonicalName()); + + parseCaches(DomUtils.getFirstChildElementByTagName(element, "caches"), parserContext); + builder.addPropertyReference("cachesConfig", CachesConfig.class.getCanonicalName()); + + parsePersisters(DomUtils.getFirstChildElementByTagName(element, "persisters"), parserContext); + builder.addPropertyReference("persistersConfig", PersistersConfig.class.getCanonicalName()); + + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + + private void parseHostConfig(Element element, ParserContext parserContext) { + // 解析host标签 + var clazz = HostConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + resolvePlaceholder("database", "database", builder, element, parserContext); + resolvePlaceholder("user", "user", builder, element, parserContext); + resolvePlaceholder("password", "password", builder, element, parserContext); + + var addressMap = parseAddress(element, parserContext); + builder.addPropertyValue("addressMap", addressMap); + + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + + private void parseCaches(Element element, ParserContext parserContext) { + // 解析caches标签 + var clazz = CachesConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + var cacheStrategies = parseCacheStrategies(element, parserContext); + builder.addPropertyValue("cacheStrategies", cacheStrategies); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + + private void parsePersisters(Element element, ParserContext parserContext) { + // 解析persisters标签 + var clazz = PersistersConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + var persisterStrategies = parsePersisterStrategies(element, parserContext); + builder.addPropertyValue("persisterStrategies", persisterStrategies); + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + + private ManagedList parseCacheStrategies(Element element, ParserContext parserContext) { + var cacheStrategiesElementList = DomUtils.getChildElementsByTagName(element, "cache"); + var cacheStrategies = new ManagedList(); + var environment = parserContext.getReaderContext().getEnvironment(); + for (var i = 0; i < cacheStrategiesElementList.size(); i++) { + var addressElement = cacheStrategiesElementList.get(i); + var clazz = CacheStrategy.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("strategy"))); + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("size"))); + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("expire-millisecond"))); + + cacheStrategies.add(new BeanDefinitionHolder(builder.getBeanDefinition(), StringUtils.format("{}.{}", clazz.getCanonicalName(), i))); + } + return cacheStrategies; + } + + private ManagedList parsePersisterStrategies(Element element, ParserContext parserContext) { + var persisterStrategiesElementList = DomUtils.getChildElementsByTagName(element, "persister"); + var persisterStrategies = new ManagedList(); + var environment = parserContext.getReaderContext().getEnvironment(); + for (var i = 0; i < persisterStrategiesElementList.size(); i++) { + var addressElement = persisterStrategiesElementList.get(i); + var clazz = PersisterStrategy.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("strategy"))); + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("type"))); + builder.addConstructorArgValue(environment.resolvePlaceholders(addressElement.getAttribute("config"))); + + persisterStrategies.add(new BeanDefinitionHolder(builder.getBeanDefinition(), StringUtils.format("{}.{}", clazz.getCanonicalName(), i))); + } + return persisterStrategies; + } + + private ManagedMap parseAddress(Element element, ParserContext parserContext) { + var addressElementList = DomUtils.getChildElementsByTagName(element, "address"); + var addressMap = new ManagedMap(); + + for (var addressElement : addressElementList) { + var name = addressElement.getAttribute("name"); + var urlAttribute = addressElement.getAttribute("url"); + var url = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(urlAttribute); + addressMap.put(name, url); + } + return addressMap; + } + + private void resolvePlaceholder(String attributeName, String fieldName, BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { + var attributeValue = element.getAttribute(attributeName); + var environment = parserContext.getReaderContext().getEnvironment(); + var placeholder = environment.resolvePlaceholders(attributeValue); + builder.addPropertyValue(fieldName, placeholder); + } +} diff --git a/orm/src/main/java/com/zfoo/orm/schema/OrmProcessor.java b/orm/src/main/java/com/zfoo/orm/schema/OrmProcessor.java new file mode 100644 index 00000000..af8a0e96 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/schema/OrmProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.schema; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.model.anno.EntityCachesInjection; +import com.zfoo.orm.model.cache.IEntityCaches; +import com.zfoo.orm.model.entity.IEntity; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * FactoryBean,在某些情况下,实例化Bean非常复杂,如果按照传统的方式,则需要在bean标签中配置大量的信息, + * 配置方式的灵活性是受到限制的,这时采用编码的方式可能会获得一个简单的方案 + * + * @author jaysunxiao + * @version 3.0 + */ +public class OrmProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (OrmContext.getOrmContext() == null) { + return bean; + } + + ReflectionUtils.filterFieldsInClass(bean.getClass() + , new Predicate() { + @Override + public boolean test(Field field) { + if (!field.isAnnotationPresent(EntityCachesInjection.class)) { + return false; + } + return true; + } + } + , new Consumer() { + @Override + public void accept(Field field) { + Type type = field.getGenericType(); + + if (!(type instanceof ParameterizedType)) { + throw new RuntimeException(StringUtils.format("变量[{}]的类型不是泛型类", field.getName())); + } + + Type[] types = ((ParameterizedType) type).getActualTypeArguments(); + Class> clazz = (Class>) types[1]; + IEntityCaches entityCaches = OrmContext.getOrmManager().getEntityCaches(clazz); + + if (entityCaches == null) { + throw new RuntimeException(StringUtils.format("实体缓存对象[entityCaches:{}]不存在", clazz)); + } + + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, bean, entityCaches); + entityCaches.setUsable(true); + } + }); + + return bean; + } +} diff --git a/orm/src/main/java/com/zfoo/orm/util/MongoIdUtils.java b/orm/src/main/java/com/zfoo/orm/util/MongoIdUtils.java new file mode 100644 index 00000000..f772c212 --- /dev/null +++ b/orm/src/main/java/com/zfoo/orm/util/MongoIdUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.util; + +import com.mongodb.client.model.Filters; +import com.zfoo.orm.OrmContext; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.StringUtils; +import org.bson.Document; + +import java.util.ArrayList; +import java.util.function.Consumer; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class MongoIdUtils { + + private static final long INIT_ID = 1L; + + private static final String COLLECTION_NAME = "uuid"; + + private static final String COUNT = "count"; + + + /** + * 分布式唯一Id生成器,利用MongoDB数据库存储自增的ID,可以保证原子性,一致性。 + *

+ * 线程安全,不同线程互不影响。 + *

+ * 进程安全,不同的应用进程也可以保证唯一id。 + * + * @param collectionName 存储的集合名称 + * @param documentName 文档id + * @return 增加后的唯一id + */ + public static long getIncrementIdFromMongo(String collectionName, String documentName) { + var collection = OrmContext.getOrmManager().getCollection(collectionName); + + var document = collection.findOneAndUpdate(Filters.eq("_id", documentName) + , new Document("$inc", new Document(COUNT, 1L))); + + if (document == null) { + collection.insertOne(new Document("_id", documentName).append(COUNT, INIT_ID)); + return INIT_ID; + } + + return document.getLong("count") + 1; + } + + public static long getIncrementIdFromMongoDefault(String documentName) { + return getIncrementIdFromMongo(COLLECTION_NAME, documentName); + } + + public static long getIncrementIdFromMongoDefault(Class clazz) { + return getIncrementIdFromMongo(COLLECTION_NAME, StringUtils.uncapitalize(clazz.getSimpleName())); + } + + /** + * 获取最大的id + * + * @param collectionName 存储的集合名称 + * @param documentName 文档id + * @return 增加后的唯一id + */ + public static long getMaxIdFromMongo(String collectionName, String documentName) { + var collection = OrmContext.getOrmManager().getCollection(collectionName); + + var list = new ArrayList(); + collection.find(Filters.eq("_id", documentName)).forEach(new Consumer() { + @Override + public void accept(Document document) { + list.add(document); + } + }); + + if (CollectionUtils.isEmpty(list)) { + return 0; + } + + return list.get(0).getLong("count"); + } + + public static long getMaxIdFromMongoDefault(String documentName) { + return getMaxIdFromMongo(COLLECTION_NAME, documentName); + } + + public static long getMaxIdFromMongoDefault(Class clazz) { + return getMaxIdFromMongo(COLLECTION_NAME, StringUtils.uncapitalize(clazz.getSimpleName())); + } + +} diff --git a/orm/src/main/resources/META-INF/spring.handlers b/orm/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..d12533f7 --- /dev/null +++ b/orm/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/orm=com.zfoo.orm.schema.NamespaceHandler \ No newline at end of file diff --git a/orm/src/main/resources/META-INF/spring.schemas b/orm/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..ea5ab7c5 --- /dev/null +++ b/orm/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/orm-1.0.xsd=orm-1.0.xsd \ No newline at end of file diff --git a/orm/src/main/resources/orm-1.0.xsd b/orm/src/main/resources/orm-1.0.xsd new file mode 100644 index 00000000..00ad0f51 --- /dev/null +++ b/orm/src/main/resources/orm-1.0.xsd @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orm/src/test/java/com/zfoo/orm/TestUnit.java b/orm/src/test/java/com/zfoo/orm/TestUnit.java new file mode 100644 index 00000000..859a5aab --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/TestUnit.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TestUnit { + + public static final String CONFIG_LOCATION = "application.xml"; + + + public static final int NUM = 10; + +} diff --git a/orm/src/test/java/com/zfoo/orm/accessor/AccessorTest.java b/orm/src/test/java/com/zfoo/orm/accessor/AccessorTest.java new file mode 100644 index 00000000..aa9ba125 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/accessor/AccessorTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.accessor; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class AccessorTest { + + @Test + public void testBatchInsert() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + List listUser = new ArrayList<>(); + for (int i = 0; i <= TestUnit.NUM; i++) { + UserEntity userEntity = new UserEntity(i, (byte) 1, (short) i, i, true, "helloOrm" + i, "helloOrm" + i); + listUser.add(userEntity); + } + OrmContext.getAccessor().batchInsert(listUser); + } + + @Test + public void testInsert() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + UserEntity userEntity = new UserEntity(1, (byte) 2, (short) 3, 5, true, "helloORM", "helloOrm"); + OrmContext.getAccessor().insert(userEntity); + } + + @Test + public void testUpdate() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + UserEntity userEntity = new UserEntity(1, (byte) 2, (short) 3, 5, true, "helloUpdate", "helloOrm"); + OrmContext.getAccessor().update(userEntity); + } + + @Test + public void testBatchUpdate() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + UserEntity userEntity = new UserEntity(1, (byte) 2, (short) 3, 5, true, "helloBatchUpdate", "helloOrm"); + userEntity.setC(222); + OrmContext.getAccessor().batchUpdate(Collections.singletonList(userEntity)); + } + + @Test + public void testLoad() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + UserEntity ent = (UserEntity) OrmContext.getAccessor().load(1L, UserEntity.class); + System.out.println(ent); + } + + @Test + public void testDelete() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + OrmContext.getAccessor().delete(1L, UserEntity.class); + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/accessor/DeleteTest.java b/orm/src/test/java/com/zfoo/orm/accessor/DeleteTest.java new file mode 100644 index 00000000..23bf2945 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/accessor/DeleteTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.accessor; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import com.zfoo.orm.model.cache.IEntityCaches; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class DeleteTest { + + private static final Logger logger = LoggerFactory.getLogger(InsertTest.class); + + + @Test + public void test() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + + IEntityCaches userEntityCaches = (IEntityCaches) OrmContext.getOrmManager().getEntityCaches(UserEntity.class); + + for (int i = 0; i < TestUnit.NUM; i++) { + UserEntity userEntity = new UserEntity(i, (byte) 1, (short) i, i, true, "helloOrm" + i, "helloOrm" + i); + + OrmContext.getAccessor().delete(userEntity); + logger.debug("thread2执行移除[{}]", i); + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + +} diff --git a/orm/src/test/java/com/zfoo/orm/accessor/IndexTextTest.java b/orm/src/test/java/com/zfoo/orm/accessor/IndexTextTest.java new file mode 100644 index 00000000..6dd82595 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/accessor/IndexTextTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.accessor; + +import com.mongodb.client.model.Filters; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class IndexTextTest { + + @Test + public void insertTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + List listUser = new ArrayList<>(); + var userEntity = new UserEntity(1, (byte) 1, (short) 1, 1, true, "两个黄鹂鸣翠柳,一行白鹭上青天。窗含西岭千秋雪,门泊东吴万里船。", null); + listUser.add(userEntity); + userEntity = new UserEntity(2, (byte) 2, (short) 2, 2, true, "床前明月光,疑是地上霜。 举头望明月,低头思故乡。", null); + listUser.add(userEntity); + userEntity = new UserEntity(3, (byte) 3, (short) 3, 3, true, "吴丝蜀桐张高秋,空山凝云颓不流。 江娥啼竹素女愁,李凭中国弹箜篌。 昆山玉碎凤凰叫,芙蓉泣露香兰笑。 十二门前融冷光,二十三丝动紫皇" + + "。 女娲炼石补天处,石破天惊逗秋雨。 梦入神山教神妪,老鱼跳波瘦蛟舞。 吴质不眠倚桂树,露脚斜飞湿寒兔。", null); + listUser.add(userEntity); + OrmContext.getAccessor().batchInsert(listUser); + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void queryTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + var collection = OrmContext.getOrmManager().getCollection(UserEntity.class); + collection.find(Filters.text("窗含西岭千秋雪")).forEach(new Consumer() { + @Override + public void accept(UserEntity userEntity) { + System.out.println(userEntity); + } + }); + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/accessor/InsertTest.java b/orm/src/test/java/com/zfoo/orm/accessor/InsertTest.java new file mode 100644 index 00000000..663f2b58 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/accessor/InsertTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.accessor; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import com.zfoo.orm.model.cache.IEntityCaches; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class InsertTest { + + @SuppressWarnings("unchecked") + @Test + public void test() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + + IEntityCaches userEntityCaches = (IEntityCaches) OrmContext.getOrmManager().getEntityCaches(UserEntity.class); + + for (int i = 0; i < TestUnit.NUM; i++) { + UserEntity userEntity = new UserEntity(i, (byte) 1, (short) i, i, true, "helloOrm" + i, "helloOrm" + i); + OrmContext.getAccessor().insert(userEntity); + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/accessor/TransactionTest.java b/orm/src/test/java/com/zfoo/orm/accessor/TransactionTest.java new file mode 100644 index 00000000..31d8c89b --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/accessor/TransactionTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.accessor; + +import com.mongodb.ReadConcern; +import com.mongodb.ReadPreference; +import com.mongodb.TransactionOptions; +import com.mongodb.WriteConcern; +import com.mongodb.client.TransactionBody; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * 必须要用复制集群才能使用事务 + * + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class TransactionTest { + + @Test + public void transactionTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + + /* Step 1: Start a client session. */ + var clientSession = OrmContext.getOrmManager().getClientSession(); + + /* Step 2: Optional. Define options to use for the transaction. */ + + var txnOptions = TransactionOptions.builder() + .readPreference(ReadPreference.primary()) + .readConcern(ReadConcern.LOCAL) + .writeConcern(WriteConcern.MAJORITY) + .build(); + + /* Step 3: Define the sequence of operations to perform inside the transactions. */ + + + var txnBody = new TransactionBody() { + public String execute() { + var collection = OrmContext.getOrmManager().getCollection(UserEntity.class); + var userEntity1 = new UserEntity(1, (byte) 2, (short) 3, 5, true, "helloORM", "helloOrm"); + var userEntity2 = new UserEntity(1, (byte) 2, (short) 3, 5, true, "helloORM", "helloOrm"); + + /* Important:: You must pass the session to the operations.. */ + + collection.insertOne(clientSession, userEntity1); + collection.insertOne(clientSession, userEntity2); + + return "Inserted into collections in different databases"; + } + }; + + /* Step 4: Use .withTransaction() to start a transaction, execute the callback, and commit (or abort on error). */ + try { + clientSession.withTransaction(txnBody, txnOptions); + } catch (RuntimeException e) { + // some error handling + } finally { + clientSession.close(); + } + } + + +} diff --git a/orm/src/test/java/com/zfoo/orm/accessor/UpdateTest.java b/orm/src/test/java/com/zfoo/orm/accessor/UpdateTest.java new file mode 100644 index 00000000..53e9cc20 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/accessor/UpdateTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.accessor; + +import com.mongodb.client.model.Filters; +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import com.zfoo.orm.model.cache.IEntityCaches; +import com.zfoo.util.ThreadUtils; +import org.bson.Document; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class UpdateTest { + + + @Test + public void test() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + + IEntityCaches userEntityCaches = (IEntityCaches) OrmContext.getOrmManager().getEntityCaches(UserEntity.class); + + for (int i = 0; i < TestUnit.NUM; i++) { + var userEnt = userEntityCaches.load((long) i); + userEnt.setE("update" + i); + userEnt.setC(i); + userEntityCaches.update(userEnt); + } + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + + @Test + public void collectionTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + + var collection = OrmContext.getOrmManager().getCollection(UserEntity.class); + var result = collection.updateOne(Filters.eq("_id", 1), new Document("$inc", new Document("c", 1L))); + System.out.println(result); + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/converter/MysqlToMongoDB.java b/orm/src/test/java/com/zfoo/orm/converter/MysqlToMongoDB.java new file mode 100644 index 00000000..12a788f7 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/converter/MysqlToMongoDB.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.converter; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.io.*; +import java.util.ArrayList; + +/** + * 将mysql导出的json数据导入到mongodb + * + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class MysqlToMongoDB { + + @Test + public void converter() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + + var file = new File("E:\\user.json"); + + FileInputStream fileInputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + var result = new ArrayList(); + try { + fileInputStream = FileUtils.openInputStream(file); + inputStreamReader = new InputStreamReader(fileInputStream, StringUtils.DEFAULT_CHARSET_NAME); + bufferedReader = new BufferedReader(inputStreamReader); + + var builder = new StringBuilder(); + var line = ""; + + bufferedReader.readLine(); + bufferedReader.readLine(); + + builder = new StringBuilder(); + + var index = 1; + while ((line = bufferedReader.readLine()) != null) { + if (line.equals("},") || line.equals("]")) { + builder.append("}"); + var entity = JsonUtils.string2Object(builder.toString(), UserEntity.class); + entity.setId(index); + index++; + result.add(entity); + builder = new StringBuilder(); + continue; + } + + builder.append(line); + } + + OrmContext.getAccessor().batchInsert(result); + } catch (IOException e) { + e.printStackTrace(); + } finally { + IOUtils.closeIO(bufferedReader, inputStreamReader, fileInputStream); + } + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/entity/MailEnt.java b/orm/src/test/java/com/zfoo/orm/entity/MailEnt.java new file mode 100644 index 00000000..b6e0427e --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/entity/MailEnt.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.entity; + +import com.zfoo.orm.model.anno.EntityCache; +import com.zfoo.orm.model.anno.Id; +import com.zfoo.orm.model.anno.Index; +import com.zfoo.orm.model.anno.Persister; +import com.zfoo.orm.model.entity.IEntity; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +@EntityCache(cacheStrategy = "tenThousand", persister = @Persister("time30s")) +public class MailEnt implements IEntity { + + @Id + private String mailId; + + @Index(ascending = true, unique = false) + private String userName; + + private String content; + + public MailEnt() { + } + + public MailEnt(String mailId, String userName, String content) { + this.mailId = mailId; + this.userName = userName; + this.content = content; + } + + @Override + public String id() { + return mailId; + } + + public String getMailId() { + return mailId; + } + + public void setMailId(String mailId) { + this.mailId = mailId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + @Override + public String toString() { + return "MailEnt{" + + "mailId='" + mailId + '\'' + + ", playName='" + userName + '\'' + + ", content='" + content + '\'' + + '}'; + } +} diff --git a/orm/src/test/java/com/zfoo/orm/entity/UserEntity.java b/orm/src/test/java/com/zfoo/orm/entity/UserEntity.java new file mode 100644 index 00000000..b9211de1 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/entity/UserEntity.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.entity; + +import com.zfoo.orm.model.anno.*; +import com.zfoo.orm.model.entity.IEntity; + +import java.util.List; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +@EntityCache(cacheStrategy = "tenThousand", persister = @Persister("time30s")) +public class UserEntity implements IEntity { + + @Id + private long id; + + private byte a; + + private short b; + + @Index(ascending = false, unique = true) + private int c; + + private boolean d; + + @IndexText + private String e; + + // @IndexText + private String f; + + @Index(ascending = false, unique = false) + private List l; + + public UserEntity() { + } + + public UserEntity(long id, byte a, short b, int c, boolean d, String e, String f) { + this.id = id; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } + + @Override + public Long id() { + return id; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public byte getA() { + return a; + } + + public void setA(byte a) { + this.a = a; + } + + public short getB() { + return b; + } + + public void setB(short b) { + this.b = b; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public boolean isD() { + return d; + } + + public void setD(boolean d) { + this.d = d; + } + + public String getE() { + return e; + } + + public void setE(String e) { + this.e = e; + } + + public String getF() { + return f; + } + + public void setF(String f) { + this.f = f; + } + + public List getL() { + return l; + } + + public void setL(List l) { + this.l = l; + } + + @Override + public String toString() { + return "UserEntity{" + + "id=" + id + + ", a=" + a + + ", b=" + b + + ", c=" + c + + ", d=" + d + + ", e='" + e + '\'' + + ", f='" + f + '\'' + + ", l=" + l + + '}'; + } +} diff --git a/orm/src/test/java/com/zfoo/orm/query/PageTest.java b/orm/src/test/java/com/zfoo/orm/query/PageTest.java new file mode 100644 index 00000000..fd8b56b7 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/query/PageTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.query; + +import com.zfoo.orm.model.query.Page; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class PageTest { + + @Test + public void pageTest() { + var list = new ArrayList(); + for (var i = 1; i <= 105; i++) { + list.add(i); + } + + var page = Page.valueOf(11, 10, list.size()); + var result = page.currentPageList(list).toArray(); + + Assert.assertArrayEquals(List.of(101, 102, 103, 104, 105).toArray(), result); + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/query/QueryTest.java b/orm/src/test/java/com/zfoo/orm/query/QueryTest.java new file mode 100644 index 00000000..3e68a175 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/query/QueryTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.query; + +import com.zfoo.orm.OrmContext; +import com.zfoo.orm.TestUnit; +import com.zfoo.orm.entity.UserEntity; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class QueryTest { + + @Test + public void queryAllTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + List list = OrmContext.getQuery().queryAll(UserEntity.class); + System.out.println(list); + } + + @Test + public void queryByFieldTest() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(TestUnit.CONFIG_LOCATION); + List list = OrmContext.getQuery().queryFieldEqual("a", 1, UserEntity.class); + System.out.println(list); + } + + +} diff --git a/orm/src/test/java/com/zfoo/orm/test/OrmTest.java b/orm/src/test/java/com/zfoo/orm/test/OrmTest.java new file mode 100644 index 00000000..72674246 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/test/OrmTest.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.test; + +import com.zfoo.orm.OrmContext; +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class OrmTest { + + @Test + public void test() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); + + UserManager mainOrm = context.getBean(UserManager.class); + + for (int i = 0; i <= 10; i++) { + var entity = mainOrm.getEntityCaches().load((long) i); + entity.setA((byte) i); + entity.setB((short) i); + entity.setC(i); + entity.setD(true); + entity.setE("helloOrm" + i); + mainOrm.getEntityCaches().update(entity); + } + + OrmContext.getOrmManager().getAllEntityCaches().forEach(it -> System.out.println(it.recordStatus())); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/test/UserManager.java b/orm/src/test/java/com/zfoo/orm/test/UserManager.java new file mode 100644 index 00000000..fdb0fa34 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/test/UserManager.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.test; + +import com.zfoo.orm.entity.UserEntity; +import com.zfoo.orm.model.anno.EntityCachesInjection; +import com.zfoo.orm.model.cache.IEntityCaches; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class UserManager { + + @EntityCachesInjection + private IEntityCaches entityCaches; + + public IEntityCaches getEntityCaches() { + return entityCaches; + } + +} diff --git a/orm/src/test/java/com/zfoo/orm/util/MongoIdUtilsTest.java b/orm/src/test/java/com/zfoo/orm/util/MongoIdUtilsTest.java new file mode 100644 index 00000000..0b083651 --- /dev/null +++ b/orm/src/test/java/com/zfoo/orm/util/MongoIdUtilsTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.orm.util; + +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class MongoIdUtilsTest { + + @Test + public void startApplication0() { + var context = new ClassPathXmlApplicationContext("application.xml"); + ThreadUtils.sleep(8000); + + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + new Thread(new Runnable() { + @Override + public void run() { + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + System.out.println(count); + } + }).start(); + System.out.println(count); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startApplication1() { + var context = new ClassPathXmlApplicationContext("application.xml"); + ThreadUtils.sleep(6000); + + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + new Thread(new Runnable() { + @Override + public void run() { + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + System.out.println(count); + } + }).start(); + System.out.println(count); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startApplication2() { + var context = new ClassPathXmlApplicationContext("application.xml"); + ThreadUtils.sleep(4000); + + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + new Thread(new Runnable() { + @Override + public void run() { + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + System.out.println(count); + } + }).start(); + System.out.println(count); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + + @Test + public void startApplication3() { + var context = new ClassPathXmlApplicationContext("application.xml"); + ThreadUtils.sleep(2000); + + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + new Thread(new Runnable() { + @Override + public void run() { + var count = 0L; + for (int i = 0; i < 10000_00; i++) { + count = MongoIdUtils.getIncrementIdFromMongoDefault("myDocument"); + } + System.out.println(count); + } + }).start(); + System.out.println(count); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/orm/src/test/resources/application.xml b/orm/src/test/resources/application.xml new file mode 100644 index 00000000..c97194bf --- /dev/null +++ b/orm/src/test/resources/application.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orm/src/test/resources/deploy-dev.properties b/orm/src/test/resources/deploy-dev.properties new file mode 100644 index 00000000..b80ea996 --- /dev/null +++ b/orm/src/test/resources/deploy-dev.properties @@ -0,0 +1,5 @@ +mongodb.database=test +mongodb.user= +mongodb.password= +mongodb.address.name=server0 +mongodb.address.url=127.0.0.1:27017 \ No newline at end of file diff --git a/orm/src/test/resources/logback-test.xml b/orm/src/test/resources/logback-test.xml new file mode 100644 index 00000000..2e967bd9 --- /dev/null +++ b/orm/src/test/resources/logback-test.xml @@ -0,0 +1,44 @@ + + + + + com.zfoo.orm + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..fa43aed5 --- /dev/null +++ b/pom.xml @@ -0,0 +1,249 @@ + + + 4.0.0 + + + com.zfoo + zfoo + 3.0 + + pom + + The root project of zfoo + https://github.com/zfoo-project/zfoo + 2016 + + + The ZFOO company + https://www.zfoo.com + + + + + + + event + hotswap + monitor + net + scheduler + storage + orm + protocol + util + + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + + + + + + src/main/java + src/test/java + + + + + src/main/resources + + **/*.* + + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + + package + + copy-resources + + + ${file.encoding} + + ${project.build.directory}/resource + + + + + src/main/resources/ + + + false + + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + + + + + org.codehaus.mojo + versions-maven-plugin + ${versions-maven-plugin.version} + + false + + + + + + + + diff --git a/protocol/README.md b/protocol/README.md new file mode 100644 index 00000000..58392ffb --- /dev/null +++ b/protocol/README.md @@ -0,0 +1,61 @@ +### 1. 简介 + +- zfoo protocol是目前的Java二进制序列化和反序列化最快的框架,协议跨平台支持Java Javascript C# Lua,并且线程安全 +- 主要是用Javassist字节码增强动态生成顺序执行的序列化和反序列化函数,顺序执行的函数可以轻易的被JIT编译以达到极致的性能 +- 单线程环境,在没有任何JVM参数调优的情况下速度比Kryo快40%,比Protobuf快110%,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java) +- 多线程环境,zfoo和Protobuf的性能不受任何影响,kryo因为线程不安全性能会有所损失,[参见性能测试](src/test/java/com/zfoo/protocol/SpeedTest.java) + +### 2. 环境要求 + +**JDK 11+**,可以在 **OpenJDK** 和 **Oracle JDK** 无缝切换 + +### 3. 快速使用 + +``` +// zfoo协议注册,只能初始化一次 +ProtocolManager.initProtocol(Set.of(ComplexObject.class, ObjectA.class, ObjectB.class)); + +// 序列化 +ProtocolManager.write(byteBuf, complexObject); + +// 反序列化 +var packet = ProtocolManager.read(buffer); +``` + +### 4. zfoo的优势 + +- 没有反射,没有装箱拆箱 +- 天生线程安全;对比kryo会出现性能损耗 +- 没有unsafe操作;对比kryo中存在大量unsafe,在Java11中运行会出现警告 +- 无漏洞注入风险,只有初始化时会进行字节码增强,后期不会再进行任何字节码的操作 +- 数据结构嵌套没有任何性能损失,如List>>;对比kryo和protobuf数据结构嵌套会出现性能损失 +- 数据压缩体积小,压缩体积比kryo和protobuf都要小 +- 跨平台可以轻易实现,目前已经原生支持Java,Javascript,C#,Lua,目前kryo无法跨平台,protobuf可以跨平台 +- 智能语法分析,错误的协议定义将无法启动程序并给出错误警告 +- 提升开发效率,完全支持POJO方式开发,使用非常简单 + +### 5. 待解决的问题 + +- 为了代码的优雅,zfoo protocol要求全部的协议都要继承IPacket,但是可以保证不损失性能的情况下支持不继承IPacket的设计,这个有待继续讨论。 + +### 6. 协议规范 + +- 协议类必须实现com.zfoo.protocol.model.packet.IPacket接口,协议类的的protocolId不能重复 +- 协议类必须有一个标识为:public static final transient short PROTOCOL_ID的"协议序列号",这个协议号的值必须和IPacket接口返回的值一样 +- 协议类必须是简单的javabean,不能继承任何其它的类,但是可以继承接口 + +- 默认的数据格式支持,无需用户手动注册,[参考类定义](src/test/java/com/zfoo/protocol/packet/ComplexObject.java) + - boolean,byte,short,int,long,float,double,char,String + - Boolean,Byte,Short,Integer,Long,Float,Double,Character,序列化的时候如果null,会给个默认值0(Character默认值为Character.MIN_VALUE) + - int[],Integer[],如果是null,则解析后的为new Integer[0],一个长度为0的数组 + - 原生泛型List,Set,Map,反序列化返回类型为HashSet,ArrayList,HashMap,并且空指针安全(返回大小为0的集合) + - List,必须指定泛型类,如果发送的是[1,1,null,1],接收到的是[1,1,0,1] + - List,如果发送的是[obj,obj,null,obj],接收到的是[obj,obj,null,obj],即引用类型序列化之前为null,序列化之后同样为null + +- 不支持的数据格式,因为zfoo会自动识别不支持的类型并且给出错误警告,所以用户不必太关心 + - int[][],二维以上数组,考虑到不是所有语言都支持多维数组 + - List[],Map[],Java语言本身就没有支持泛型类数组 + - List,Map,泛型里面套数组,这种写法看起来比较奇怪,实际使用的地方很少 + - 枚举类,考虑到很多其他语言不支持枚举类,可以用int或者string在代码层面做代替 + - 自定义泛型类XXXClass,泛型类在很多框架中都极易出现性能上和解析上的问题,而且并不是所有语言都支持 + - 循环引用,虽然底层支持循环引用,但是考虑到循环引用带来语义上难以理解,容易出现错误,所以就屏蔽了 diff --git a/protocol/pom.xml b/protocol/pom.xml new file mode 100644 index 00000000..f4ae404f --- /dev/null +++ b/protocol/pom.xml @@ -0,0 +1,275 @@ + + + 4.0.0 + + com.zfoo + protocol + 3.0 + + jar + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + io.netty + netty-all + ${netty.version} + + + + + org.javassist + javassist + ${javassist.version} + + + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + + + + + com.fasterxml.jackson.module + jackson-module-afterburner + ${jackson.version} + + + + + + com.google.protobuf + protobuf-java + ${google.protobuf.version} + + + com.google.guava + guava + + + test + + + com.google.guava + guava + ${google.guava.version} + + + error_prone_annotations + com.google.errorprone + + + checker-qual + org.checkerframework + + + test + + + + + + com.esotericsoftware + kryo + ${kryo.version} + test + + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + diff --git a/protocol/src/main/java/com/zfoo/protocol/IPacket.java b/protocol/src/main/java/com/zfoo/protocol/IPacket.java new file mode 100644 index 00000000..f6c9f476 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/IPacket.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol; + +/** + * 所有协议类都必须实现这个接口 + * + * @author jaysunxiao + * @version 3.0 + */ +public interface IPacket { + + /** + * 这个类的协议号 + * + * @return 协议号Id + */ + short protocolId(); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/ProtocolManager.java b/protocol/src/main/java/com/zfoo/protocol/ProtocolManager.java new file mode 100644 index 00000000..1c28ef11 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/ProtocolManager.java @@ -0,0 +1,574 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.exception.RunException; +import com.zfoo.protocol.exception.UnknownException; +import com.zfoo.protocol.generate.GenerateOperation; +import com.zfoo.protocol.generate.GenerateProtocolDocument; +import com.zfoo.protocol.generate.GenerateProtocolFile; +import com.zfoo.protocol.generate.GenerateProtocolPath; +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.registration.ProtocolModule; +import com.zfoo.protocol.registration.ProtocolRegistration; +import com.zfoo.protocol.registration.field.*; +import com.zfoo.protocol.serializer.*; +import com.zfoo.protocol.serializer.cs.GenerateCsUtils; +import com.zfoo.protocol.serializer.js.GenerateJsUtils; +import com.zfoo.protocol.serializer.lua.GenerateLuaUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.protocol.xml.XmlProtocols; +import io.netty.buffer.ByteBuf; +import javassist.CannotCompileException; +import javassist.NotFoundException; + +import java.io.IOException; +import java.lang.reflect.*; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ProtocolManager { + + + /** + * 包体的头部的长度,一个int字节长度 + */ + public static final int PROTOCOL_HEAD_LENGTH = 4; + public static final String PROTOCOL_ID = "PROTOCOL_ID"; + public static final short MAX_PROTOCOL_NUM = Short.MAX_VALUE; + public static final byte MAX_MODULE_NUM = Byte.MAX_VALUE; + private static final Comparator PACKET_FIELD_COMPARATOR = (a, b) -> a.getName().compareTo(b.getName()); + + private static final IProtocolRegistration[] protocols = new IProtocolRegistration[MAX_PROTOCOL_NUM]; + private static final ProtocolModule[] modules = new ProtocolModule[MAX_MODULE_NUM]; + + // 临时变量,启动完成就会销毁,协议名称保留字符,即协议的名称不能用以下名称命名 + private static Set tempProtocolReserved = Set.of("Buffer", "ByteBuf", "ByteBuffer", "LittleEndianByteBuffer", "NormalByteBuffer" + , "IPacket", "IProtocolRegistration", "ProtocolManager", "IFieldRegistration" + , "ByteBufUtils", "ArrayUtils", "CollectionUtils" + , "Boolean", "Byte", "Short", "Integer", "Long", "Float", "Double", "String", "Character", "Object"); + + // 临时变量,启动完成就会销毁,是一个基本类型序列化器 + private static Map, ISerializer> tempBaseSerializerMap = new HashMap<>(); + + // 临时变量,启动完成就会销毁,协议下包含的子协议,只包含一层子协议 + private static Map> tempSubProtocolIdMap = new HashMap<>(); + + + static { + // 初始化默认协议模块 + modules[0] = ProtocolModule.DEFAULT_PROTOCOL_MODULE; + + // 初始化基础类型序列化器 + tempBaseSerializerMap.put(boolean.class, BooleanSerializer.getInstance()); + tempBaseSerializerMap.put(Boolean.class, BooleanSerializer.getInstance()); + tempBaseSerializerMap.put(byte.class, ByteSerializer.getInstance()); + tempBaseSerializerMap.put(Byte.class, ByteSerializer.getInstance()); + tempBaseSerializerMap.put(short.class, ShortSerializer.getInstance()); + tempBaseSerializerMap.put(Short.class, ShortSerializer.getInstance()); + tempBaseSerializerMap.put(int.class, IntSerializer.getInstance()); + tempBaseSerializerMap.put(Integer.class, IntSerializer.getInstance()); + tempBaseSerializerMap.put(long.class, LongSerializer.getInstance()); + tempBaseSerializerMap.put(Long.class, LongSerializer.getInstance()); + tempBaseSerializerMap.put(float.class, FloatSerializer.getInstance()); + tempBaseSerializerMap.put(Float.class, FloatSerializer.getInstance()); + tempBaseSerializerMap.put(double.class, DoubleSerializer.getInstance()); + tempBaseSerializerMap.put(Double.class, DoubleSerializer.getInstance()); + tempBaseSerializerMap.put(char.class, CharSerializer.getInstance()); + tempBaseSerializerMap.put(Character.class, CharSerializer.getInstance()); + tempBaseSerializerMap.put(String.class, StringSerializer.getInstance()); + } + + public static void write(ByteBuf buffer, IPacket packet) { + var protocolId = packet.protocolId(); + // 写入协议号 + ByteBufUtils.writeShort(buffer, protocolId); + // 写入包体 + protocols[protocolId].write(buffer, packet); + } + + public static IPacket read(ByteBuf buffer) { + return (IPacket) protocols[ByteBufUtils.readShort(buffer)].read(buffer); + } + + public static IProtocolRegistration getProtocol(short id) { + var protocol = protocols[id]; + if (protocol == null) { + throw new RunException("[protocolId:{}]协议不存在", id); + } + return protocol; + } + + public static ProtocolModule moduleByProtocolId(short id) { + return modules[protocols[id].module()]; + } + + public static ProtocolModule moduleByModuleId(byte moduleId) { + var module = modules[moduleId]; + AssertionUtils.notNull(module, "[moduleId:{}]不存在", moduleId); + return module; + } + + public static ProtocolModule moduleByModuleName(String name) { + var moduleOptional = Arrays.stream(modules) + .filter(it -> Objects.nonNull(it)) + .filter(it -> it.getName().equals(name)) + .findFirst(); + if (moduleOptional.isEmpty()) { + return null; + } + return moduleOptional.get(); + } + + + public static synchronized void initProtocol(Set> protocolClassSet) { + initProtocol(protocolClassSet, GenerateOperation.NO_OPERATION); + } + + public static synchronized void initProtocol(Set> protocolClassSet, GenerateOperation generateOperation) { + AssertionUtils.notNull(tempSubProtocolIdMap, "[{}]已经初始完成,只能parseProtocol一次,请不要重复初始化", ProtocolManager.class.getSimpleName()); + try { + for (var protocolClass : protocolClassSet) { + try { + var registration = parseProtocolRegistration(protocolClass, ProtocolModule.DEFAULT_PROTOCOL_MODULE); + // 注册协议 + protocols[registration.protocolId()] = registration; + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("解析协议[class:{}]异常", protocolClass), e); + } + } + + enhanceProtocolBefore(generateOperation); + + // 通过指定类注册的协议,全部使用字节码增强 + enhanceProtocolRegistration(Arrays.stream(protocols).filter(it -> Objects.nonNull(it)).collect(Collectors.toList())); + + enhanceProtocolAfter(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + public static synchronized void initProtocol(XmlProtocols xmlProtocols, GenerateOperation generateOperation) { + AssertionUtils.notNull(tempSubProtocolIdMap, "[{}]已经初始完成,只能parseProtocol一次,请不要重复初始化", ProtocolManager.class.getSimpleName()); + try { + var enhanceList = new ArrayList(); + + for (var moduleDefinition : xmlProtocols.getModules()) { + var module = new ProtocolModule(moduleDefinition.getId(), moduleDefinition.getName(), moduleDefinition.getVersion()); + + AssertionUtils.isTrue(module.getId() > 0, "[module:{}] [id:{}] 模块必须大于等于1", module.getName(), module.getId()); + AssertionUtils.isNull(modules[module.getId()], "duplicate [module:{}] [id:{}] Exception!", module.getName(), module.getId()); + AssertionUtils.notNull(moduleDefinition.getProtocols(), "[module:{}] does not have any protocols", module.getName()); + + modules[module.getId()] = module; + + for (var protocolDefinition : moduleDefinition.getProtocols()) { + var id = protocolDefinition.getId(); + var location = protocolDefinition.getLocation(); + var clazz = Class.forName(location); + + AssertionUtils.isTrue(id >= moduleDefinition.getMinId(), "模块[{}]中的协议[{}]的协议号必须大于或者等于[{}]", moduleDefinition.getName(), clazz.getSimpleName(), moduleDefinition.getMinId()); + AssertionUtils.isTrue(id < moduleDefinition.getMaxId(), "模块[{}]中的协议[{}]的协议号必须小于[{}]", moduleDefinition.getName(), clazz.getSimpleName(), moduleDefinition.getMaxId()); + AssertionUtils.isNull(protocols[id], "duplicate definition [id:{}] Exception!", id); + + var packet = (IPacket) ReflectionUtils.newInstance(clazz); + + // 协议号是否和id是否相等 + AssertionUtils.isTrue(packet.protocolId() == id, "[class:{}]协议序列号[{}]和协议文件里的协议序列号不相等", clazz.getCanonicalName(), PROTOCOL_ID); + + try { + var registration = parseProtocolRegistration(clazz, module); + if (protocolDefinition.isEnhance()) { + enhanceList.add(registration); + } + // 注册协议 + protocols[id] = registration; + } catch (Exception e) { + throw new UnknownException(e, "解析协议[id:{}][class:{}]异常", id, clazz); + } + } + } + + enhanceProtocolBefore(generateOperation); + + enhanceProtocolRegistration(enhanceList); + + enhanceProtocolAfter(); + } catch (Exception e) { + throw new UnknownException(e); + } + } + + private static void enhanceProtocolRegistration(List enhanceList) throws NoSuchMethodException, IllegalAccessException, InstantiationException, CannotCompileException, NotFoundException, InvocationTargetException, NoSuchFieldException { + // 字节码增强 + for (var registration : enhanceList) { + protocols[registration.protocolId()] = EnhanceUtils.createProtocolRegistration((ProtocolRegistration) registration); + } + + // 字节码增强过后,初始化各个子协议成员变量 + for (var registration : enhanceList) { + var enhanceProtocolRegistration = protocols[registration.protocolId()]; + var subProtocolIds = getAllSubProtocolIds(registration.protocolId()); + for (var subProtocolId : subProtocolIds) { + var protocolRegistrationField = enhanceProtocolRegistration.getClass().getDeclaredField(EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(subProtocolId)); + ReflectionUtils.makeAccessible(protocolRegistrationField); + ReflectionUtils.setField(protocolRegistrationField, enhanceProtocolRegistration, protocols[subProtocolId]); + } + } + } + + private static void enhanceProtocolBefore(GenerateOperation generateOperation) throws IOException { + // 检查协议格式 + checkAllProtocolClass(); + + // 检查模块格式 + checkAllModules(); + + // 生成协议 + GenerateProtocolFile.generate(protocols, generateOperation); + } + + private static void enhanceProtocolAfter() { + tempSubProtocolIdMap.clear(); + tempSubProtocolIdMap = null; + + tempProtocolReserved = null; + + tempBaseSerializerMap.clear(); + tempBaseSerializerMap = null; + + GenerateProtocolDocument.clear(); + GenerateProtocolPath.clear(); + GenerateCsUtils.clear(); + GenerateJsUtils.clear(); + GenerateLuaUtils.clear(); + GenerateUtils.clear(); + + EnhanceUtils.clear(); + } + + + private static short checkProtocol(Class clazz) throws IllegalAccessException, InvocationTargetException, InstantiationException { + // 是否为一个简单的javabean + AssertionUtils.isTrue(clazz.getSuperclass().equals(Object.class), "[class:{}]不是简单的javabean,不能继承别的类", clazz.getCanonicalName()); + // 是否实现了IPacket接口 + AssertionUtils.isTrue(IPacket.class.isAssignableFrom(clazz), "[class:{}]没有实现接口[IPacket:{}]", clazz.getCanonicalName(), IPacket.class.getCanonicalName()); + // 不能是泛型类 + AssertionUtils.isTrue(CollectionUtils.isEmpty(clazz.getTypeParameters()), "[class:{}]不能是泛型类", clazz.getCanonicalName()); + + Field protocolIdField; + try { + protocolIdField = clazz.getDeclaredField(PROTOCOL_ID); + } catch (NoSuchFieldException e) { + throw new UnknownException(e, "[class:{}]没有[{}]协议序列号", clazz.getCanonicalName(), PROTOCOL_ID); + } + + // 是否被public修饰 + AssertionUtils.isTrue(Modifier.isPublic(protocolIdField.getModifiers()), "[class:{}]协议序列号[{}]没有被public修饰", clazz.getCanonicalName(), PROTOCOL_ID); + // 是否被static修饰 + AssertionUtils.isTrue(Modifier.isStatic(protocolIdField.getModifiers()), "[class:{}]协议序列号[{}]没有被static修饰", clazz.getCanonicalName(), PROTOCOL_ID); + // 是否被final修饰 + AssertionUtils.isTrue(Modifier.isFinal(protocolIdField.getModifiers()), "[class:{}]协议序列号[{}]没有被final修饰", clazz.getCanonicalName(), PROTOCOL_ID); + // 是否被transient修饰 + AssertionUtils.isTrue(Modifier.isTransient(protocolIdField.getModifiers()), "[class:{}]协议序列号[{}]没有被transient修饰", clazz.getCanonicalName(), PROTOCOL_ID); + // 命名只能包含字母,数字,下划线 + AssertionUtils.isTrue(clazz.getSimpleName().matches("[a-zA-Z0-9_]*"), "[class:{}]的命名只能包含字母,数字,下划线", clazz.getCanonicalName(), PROTOCOL_ID); + + // 必须要有一个空的构造器 + Constructor constructor; + try { + constructor = clazz.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new UnknownException(e, "[class:{}]协议序列号[{}]必须有一个空的构造器", clazz.getCanonicalName(), PROTOCOL_ID); + } + ReflectionUtils.makeAccessible(protocolIdField); + IPacket packet = (IPacket) constructor.newInstance(); + + // 验证protocol()方法的返回是否和PROTOCOL_ID相等 + AssertionUtils.isTrue(Short.valueOf(packet.protocolId()).equals(protocolIdField.get(null)), "[class:{}]的protocolId返回的值和协议号的静态变量[{}]不相等", clazz.getCanonicalName(), PROTOCOL_ID); + return packet.protocolId(); + } + + + public static short getProtocolIdByClass(Class clazz) { + var protocolIdField = ReflectionUtils.getFieldByNameInPOJOClass(clazz, PROTOCOL_ID); + ReflectionUtils.makeAccessible(protocolIdField); + return (short) ReflectionUtils.getField(protocolIdField, null); + } + + private static void checkAllModules() { + // 模块id不能重复 + var moduleIdSet = new HashSet(); + Arrays.stream(modules) + .filter(it -> Objects.nonNull(it)) + .peek(it -> AssertionUtils.isTrue(!moduleIdSet.contains(it.getId()), "模块[{}]存在重复的id,模块的id不能重复", it)) + .forEach(it -> moduleIdSet.add(it.getId())); + + // 模块名称不能重复 + var moduleNameSet = new HashSet(); + Arrays.stream(modules) + .filter(it -> Objects.nonNull(it)) + .peek(it -> AssertionUtils.isTrue(!moduleNameSet.contains(it.getName()), "模块[{}]存在重复的name,模块名称不能重复", it)) + .forEach(it -> moduleNameSet.add(it.getName())); + } + + private static void checkAllProtocolClass() { + // 检查协议格式 + + // 协议的名称不能重复 + var allProtocolNameMap = new HashMap>(); + for (var protocolRegistration : protocols) { + if (protocolRegistration == null) { + continue; + } + + var protocolClass = protocolRegistration.protocolConstructor().getDeclaringClass(); + var protocolName = protocolClass.getSimpleName(); + if (allProtocolNameMap.containsKey(protocolName)) { + throw new RunException("[class:{}]和[class:{}]协议名称重复,协议不能含有重复的名称", protocolClass.getCanonicalName(), allProtocolNameMap.get(protocolName).getCanonicalName()); + } + + if (tempProtocolReserved.stream().anyMatch(it -> it.equalsIgnoreCase(protocolName))) { + throw new RunException("协议的名称[class:{}]不能是保留名称[{}]", protocolClass.getCanonicalName(), protocolName); + } + + allProtocolNameMap.put(protocolName, protocolClass); + } + + + // 检查循环协议 + for (var protocolEntry : tempSubProtocolIdMap.entrySet()) { + var protocolId = protocolEntry.getKey(); + var subProtocolSet = protocolEntry.getValue(); + if (subProtocolSet.contains(protocolId)) { + var protocolClass = protocols[protocolId].protocolConstructor().getDeclaringClass(); + throw new RunException("[class:{}]在第一层包含循环引用协议[class:{}]", protocolClass.getSimpleName(), protocolClass.getSimpleName()); + } + + getAllSubProtocolIds(protocolId); + } + } + + private static ProtocolRegistration parseProtocolRegistration(Class clazz, ProtocolModule module) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { + var protocolId = checkProtocol(clazz); + + if (protocols[protocolId] != null) { + throw new RunException("[{}][{}]协议号[protocolId:{}]重复", protocols[protocolId].protocolConstructor().getDeclaringClass().getCanonicalName(), clazz.getCanonicalName(), protocolId); + } + + var fields = new ArrayList(); + for (var field : clazz.getDeclaredFields()) { + var modifiers = field.getModifiers(); + if (Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers)) { + continue; + } + + if (Modifier.isFinal(modifiers)) { + throw new RunException("[{}]协议号[protocolId:{}]中的[filed:{}]属性的访问修饰符不能为final" + , clazz.getCanonicalName(), protocolId, field.getName()); + } + + if (!Modifier.isPublic(modifiers) && !Modifier.isPrivate(modifiers)) { + throw new RunException("[{}]协议号[protocolId:{}]中的[filed:{}]属性的访问修饰符必须是public或者private" + , clazz.getCanonicalName(), protocolId, field.getName()); + } + + ReflectionUtils.makeAccessible(field); + fields.add(field); + } + + // 按变量名称从小到大排序 + fields.sort(PACKET_FIELD_COMPARATOR); + + var registrationList = new ArrayList(); + for (var field : fields) { + registrationList.add(toRegistration(clazz, field)); + } + + var constructor = clazz.getDeclaredConstructor(); + ReflectionUtils.makeAccessible(constructor); + var protocol = new ProtocolRegistration(); + protocol.setId(protocolId); + protocol.setConstructor(constructor); + protocol.setFields(fields.toArray(new Field[fields.size()])); + protocol.setFieldRegistrations(registrationList.toArray(new IFieldRegistration[registrationList.size()])); + protocol.setModule(module.getId()); + return protocol; + } + + private static IFieldRegistration toRegistration(Class clazz, Field field) throws NoSuchFieldException, IllegalAccessException { + Class fieldTypeClazz = field.getType(); + + ISerializer serializer = tempBaseSerializerMap.get(fieldTypeClazz); + + // 是一个基本类型变量 + if (serializer != null) { + return BaseField.valueOf(serializer); + } else if (fieldTypeClazz.getComponentType() != null) { + // 是一个数组 + Class arrayClazz = fieldTypeClazz.getComponentType(); + + IFieldRegistration registration = typeToRegistration(clazz, arrayClazz); + return ArrayField.valueOf(field, registration); + } else if (Set.class.isAssignableFrom(fieldTypeClazz)) { + if (!fieldTypeClazz.equals(Set.class)) { + throw new RunException("[class:{}]类型声明不正确,必须是Set接口类型", clazz.getCanonicalName()); + } + + Type type = field.getGenericType(); + + if (!(type instanceof ParameterizedType)) { + throw new RunException("[class:{}]类型声明不正确,不是泛型类[field:{}]", clazz.getCanonicalName(), field.getName()); + } + + Type[] types = ((ParameterizedType) type).getActualTypeArguments(); + + if (types.length != 1) { + throw new RunException("[class:{}]中Set类型声明不正确,[field:{}]必须声明泛型类", clazz.getCanonicalName(), field.getName()); + } + + IFieldRegistration registration = typeToRegistration(clazz, types[0]); + return SetField.valueOf(registration, type); + } else if (List.class.isAssignableFrom(fieldTypeClazz)) { + // 是一个List + if (!fieldTypeClazz.equals(List.class)) { + throw new RunException("[class:{}]类型声明不正确,必须是List接口类型", clazz.getCanonicalName()); + } + + Type type = field.getGenericType(); + + if (!(type instanceof ParameterizedType)) { + throw new RunException("[class:{}]类型声明不正确,不是泛型类[field:{}]", clazz.getCanonicalName(), field.getName()); + } + + Type[] types = ((ParameterizedType) type).getActualTypeArguments(); + + if (types.length != 1) { + throw new RunException("[class:{}]中List类型声明不正确,[field:{}]必须声明泛型类", clazz.getCanonicalName(), field.getName()); + } + + IFieldRegistration registration = typeToRegistration(clazz, types[0]); + return ListField.valueOf(registration, (ParameterizedType) type); + + } else if (Map.class.isAssignableFrom(fieldTypeClazz)) { + if (!fieldTypeClazz.equals(Map.class)) { + throw new RunException("[class:{}]类型声明不正确,必须是Map接口类型", clazz.getCanonicalName()); + } + + Type type = field.getGenericType(); + + if (!(type instanceof ParameterizedType)) { + throw new RunException("[class:{}]中数组类型声明不正确,[field:{}]不是泛型类", clazz.getCanonicalName(), field.getName()); + } + + Type[] types = ((ParameterizedType) type).getActualTypeArguments(); + + if (types.length != 2) { + throw new RunException("[class:{}]中数组类型声明不正确,[field:{}]必须声明泛型类", clazz.getCanonicalName(), field.getName()); + } + + IFieldRegistration keyRegistration = typeToRegistration(clazz, types[0]); + IFieldRegistration valueRegistration = typeToRegistration(clazz, types[1]); + + return MapField.valueOf(keyRegistration, valueRegistration, type); + } else { + // 是一个协议引用变量 + var referenceProtocolId = getProtocolIdByClass(field.getType()); + tempSubProtocolIdMap.computeIfAbsent(getProtocolIdByClass(clazz), it -> new HashSet<>()).add(referenceProtocolId); + return ObjectProtocolField.valueOf(referenceProtocolId); + } + } + + private static IFieldRegistration typeToRegistration(Class currentProtocolClass, Type type) { + if (type instanceof ParameterizedType) { + // 泛型类 + Class clazz = (Class) ((ParameterizedType) type).getRawType(); + if (Set.class.equals(clazz)) { + // Set> + IFieldRegistration registration = typeToRegistration(currentProtocolClass, ((ParameterizedType) type).getActualTypeArguments()[0]); + return SetField.valueOf(registration, type); + } else if (List.class.equals(clazz)) { + // List> + IFieldRegistration registration = typeToRegistration(currentProtocolClass, ((ParameterizedType) type).getActualTypeArguments()[0]); + return ListField.valueOf(registration, (ParameterizedType) type); + } else if (Map.class.equals(clazz)) { + // Map, List> + IFieldRegistration keyRegistration = typeToRegistration(currentProtocolClass, ((ParameterizedType) type).getActualTypeArguments()[0]); + IFieldRegistration valueRegistration = typeToRegistration(currentProtocolClass, ((ParameterizedType) type).getActualTypeArguments()[1]); + return MapField.valueOf(keyRegistration, valueRegistration, type); + } + } else if (type instanceof Class) { + Class clazz = ((Class) type); + ISerializer serializer = tempBaseSerializerMap.get(clazz); + if (serializer != null) { + // 基础类型 + return BaseField.valueOf(serializer); + } else if (clazz.getComponentType() != null) { + // 是一个二维以上数组 + throw new RunException("不支持多维数组或集合嵌套数组[type:{}]类型,仅支持一维数组", type); + } else if (clazz.equals(List.class) || clazz.equals(Set.class) || clazz.equals(Map.class)) { + throw new RunException("不支持数组和集合联合使用[type:{}]类型", type); + } else { + // 是一个协议引用变量 + var referenceProtocolId = getProtocolIdByClass(clazz); + tempSubProtocolIdMap.computeIfAbsent(getProtocolIdByClass(currentProtocolClass), it -> new HashSet<>()).add(referenceProtocolId); + return ObjectProtocolField.valueOf(referenceProtocolId); + } + } + throw new RunException("[type:{}]类型不正确", type); + } + + + /** + * 此方法仅在生成协议的时候调用,一旦运行,不能调用 + */ + public static Set getAllSubProtocolIds(short protocolId) { + AssertionUtils.notNull(tempSubProtocolIdMap, "[{}]已经初始完成,初始化完成过后不能调用getAllSubProtocolIds", ProtocolManager.class.getSimpleName()); + + if (!tempSubProtocolIdMap.containsKey(protocolId)) { + return Collections.emptySet(); + } + + var protocolClass = protocols[protocolId].protocolConstructor().getDeclaringClass(); + + var queue = new LinkedList<>(tempSubProtocolIdMap.get(protocolId)); + var allSubProtocolIdSet = new HashSet<>(queue); + while (!queue.isEmpty()) { + var firstSubProtocolId = queue.poll(); + if (tempSubProtocolIdMap.containsKey(firstSubProtocolId)) { + for (var subClassId : tempSubProtocolIdMap.get(firstSubProtocolId)) { + if (subClassId == protocolId) { + throw new RunException("[class:{}]在下层协议[class:{}]包含循环引用协议[class:{}]", protocolClass.getSimpleName(), protocols[firstSubProtocolId].protocolConstructor().getDeclaringClass(), protocolClass.getSimpleName()); + } + + if (!allSubProtocolIdSet.contains(subClassId)) { + allSubProtocolIdSet.add(subClassId); + queue.offer(subClassId); + } + } + } + } + return allSubProtocolIdSet; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/buffer/ByteBufUtils.java b/protocol/src/main/java/com/zfoo/protocol/buffer/ByteBufUtils.java new file mode 100644 index 00000000..86d2dd47 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/buffer/ByteBufUtils.java @@ -0,0 +1,1067 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.buffer; + +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.util.StringUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.util.ReferenceCountUtil; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * “可变长字节码算法”的压缩数据的算法,以达到压缩数据,减少磁盘IO。 + *

+ * google的ProtocolBuffer和Facebook的thrift底层的通信协议都是由这个算法实现 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class ByteBufUtils { + + + //---------------------------------boolean-------------------------------------- + public static void writeBoolean(ByteBuf byteBuf, boolean value) { + byteBuf.writeBoolean(value); + } + + public static boolean readBoolean(ByteBuf byteBuf) { + return byteBuf.readBoolean(); + } + + public static void writeBooleanBox(ByteBuf byteBuf, Boolean value) { + byteBuf.writeBoolean(value != null && value); + } + + public static Boolean readBooleanBox(ByteBuf byteBuf) { + return byteBuf.readBoolean(); + } + + //---------------------------------byte-------------------------------------- + public static void writeByte(ByteBuf byteBuf, byte value) { + byteBuf.writeByte(value); + } + + public static byte readByte(ByteBuf byteBuf) { + return byteBuf.readByte(); + } + + public static void writeByteBox(ByteBuf byteBuf, Byte value) { + byteBuf.writeByte(value == null ? 0 : value); + } + + public static Byte readByteBox(ByteBuf byteBuf) { + return byteBuf.readByte(); + } + + //---------------------------------short-------------------------------------- + public static void writeShort(ByteBuf byteBuf, short value) { + byteBuf.writeShort(value); + } + + public static short readShort(ByteBuf byteBuf) { + return byteBuf.readShort(); + } + + public static void writeShortBox(ByteBuf byteBuf, Short value) { + byteBuf.writeShort(value == null ? 0 : value); + } + + public static Short readShortBox(ByteBuf byteBuf) { + return byteBuf.readShort(); + } + + //---------------------------------int-------------------------------------- + // 用Zigzag算法压缩int和long的值,再用Varint紧凑算法表示数字的有效位 + public static int writeInt(ByteBuf byteBuf, int value) { + value = (value << 1) ^ (value >> 31); + + int a = value >>> 7; + if (a == 0) { + byteBuf.writeByte(value); + return 1; + } + + int writeIndex = byteBuf.writerIndex(); + byteBuf.ensureWritable(5); + + byteBuf.setByte(writeIndex++, value | 0x80); + int b = value >>> 14; + if (b == 0) { + byteBuf.setByte(writeIndex++, a); + byteBuf.writerIndex(writeIndex); + return 2; + } + + byteBuf.setByte(writeIndex++, a | 0x80); + a = value >>> 21; + if (a == 0) { + byteBuf.setByte(writeIndex++, b); + byteBuf.writerIndex(writeIndex); + return 3; + } + + byteBuf.setByte(writeIndex++, b | 0x80); + b = value >>> 28; + if (b == 0) { + byteBuf.setByte(writeIndex++, a); + byteBuf.writerIndex(writeIndex); + return 4; + } + + byteBuf.setByte(writeIndex++, a | 0x80); + byteBuf.setByte(writeIndex++, b); + byteBuf.writerIndex(writeIndex); + return 5; + } + + public static int readInt(ByteBuf byteBuf) { + int readIndex = byteBuf.readerIndex(); + int b = byteBuf.getByte(readIndex++); + int value = b; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x0000007F | b << 7; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00003FFF | b << 14; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x001FFFFF | b << 21; + if (b < 0) { + value = value & 0x0FFFFFFF | byteBuf.getByte(readIndex++) << 28; + } + } + } + } + byteBuf.readerIndex(readIndex); + return ((value >>> 1) ^ -(value & 1)); + } + + + private static int writeIntCount(int value) { + if (value >>> 7 == 0) { + return 1; + } + + if (value >>> 14 == 0) { + return 2; + } + + if (value >>> 21 == 0) { + return 3; + } + + if (value >>> 28 == 0) { + return 4; + } + + return 5; + } + + + public static void writeIntBox(ByteBuf byteBuf, Integer value) { + writeInt(byteBuf, value == null ? 0 : value); + } + + public static Integer readIntBox(ByteBuf byteBuf) { + return readInt(byteBuf); + } + + //---------------------------------long-------------------------------------- + public static void writeLong(ByteBuf byteBuf, long value) { + long mask = (value << 1) ^ (value >> 63); + + if (mask >>> 32 == 0) { + writeInt(byteBuf, (int) value); + return; + } + + byte[] bytes = new byte[9]; + int index = 0; + bytes[index++] = (byte) (mask | 0x80); + bytes[index++] = (byte) (mask >>> 7 | 0x80); + bytes[index++] = (byte) (mask >>> 14 | 0x80); + bytes[index++] = (byte) (mask >>> 21 | 0x80); + bytes[index++] = (byte) (mask >>> 28 | 0x80); + + + int a = (int) (mask >>> 35); + if (a == 0) { + byteBuf.writeBytes(bytes, 0, index); + return; + } + + int b = (int) (mask >>> 42); + if (b == 0) { + bytes[index++] = (byte) a; + byteBuf.writeBytes(bytes, 0, index); + return; + } + + bytes[index++] = (byte) (a | 0x80); + a = (int) (mask >>> 49); + if (a == 0) { + bytes[index++] = (byte) b; + byteBuf.writeBytes(bytes, 0, index); + return; + } + + bytes[index++] = (byte) (b | 0x80); + b = (int) (mask >>> 56); + if (b == 0) { + bytes[index++] = (byte) a; + byteBuf.writeBytes(bytes, 0, index); + return; + } + + bytes[index++] = (byte) (a | 0x80); + bytes[index++] = (byte) b; + byteBuf.writeBytes(bytes, 0, index); + } + + public static long readLong(ByteBuf byteBuf) { + int readIndex = byteBuf.readerIndex(); + long b = byteBuf.getByte(readIndex++); + long value = b; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00000000_0000007FL | b << 7; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00000000_00003FFFL | b << 14; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00000000_001FFFFFL | b << 21; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00000000_0FFFFFFFL | b << 28; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00000007_FFFFFFFFL | b << 35; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x000003FF_FFFFFFFFL | b << 42; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x0001FFFF_FFFFFFFFL | b << 49; + if (b < 0) { + b = byteBuf.getByte(readIndex++); + value = value & 0x00FFFFFF_FFFFFFFFL | b << 56; + } + } + } + } + } + } + } + } + byteBuf.readerIndex(readIndex); + return ((value >>> 1) ^ -(value & 1)); + } + + public static void writeLongBox(ByteBuf byteBuf, Long value) { + writeLong(byteBuf, value == null ? 0L : value); + } + + public static Long readLongBox(ByteBuf byteBuf) { + return readLong(byteBuf); + } + + //---------------------------------float-------------------------------------- + public static void writeFloat(ByteBuf byteBuf, float value) { + byteBuf.writeFloat(value); + } + + public static float readFloat(ByteBuf byteBuf) { + return byteBuf.readFloat(); + } + + public static void writeFloatBox(ByteBuf byteBuf, Float value) { + byteBuf.writeFloat(value == null ? 0F : value); + } + + public static Float readFloatBox(ByteBuf byteBuf) { + return byteBuf.readFloat(); + } + + //---------------------------------double-------------------------------------- + public static void writeDouble(ByteBuf byteBuf, double value) { + byteBuf.writeDouble(value); + } + + public static double readDouble(ByteBuf byteBuf) { + return byteBuf.readDouble(); + } + + public static void writeDoubleBox(ByteBuf byteBuf, Double value) { + byteBuf.writeDouble(value == null ? 0D : value); + } + + public static Double readDoubleBox(ByteBuf byteBuf) { + return byteBuf.readDouble(); + } + + //---------------------------------String-------------------------------------- + public static void writeString(ByteBuf byteBuf, String value) { + if (StringUtils.isEmpty(value)) { + writeInt(byteBuf, 0); + return; + } + + // 预估需要写入的字节数,并预留位置 + var maxLength = ByteBufUtil.utf8MaxBytes(value); + var writeIntCountByte = writeInt(byteBuf, maxLength); + + var length = byteBuf.writeCharSequence(value, StringUtils.DEFAULT_CHARSET); + + var currentWriteIndex = byteBuf.writerIndex(); + + // 因为写入的是可变长的int,如果预留的位置过多,则清除多余的位置 + var padding = writeIntCountByte - writeIntCount(length); + if (padding == 0) { + byteBuf.writerIndex(currentWriteIndex - length - writeIntCountByte); + writeInt(byteBuf, length); + byteBuf.writerIndex(currentWriteIndex); + } else { + var retainedByteBuf = byteBuf.retainedSlice(currentWriteIndex - length, length); + byteBuf.writerIndex(currentWriteIndex - length - writeIntCountByte); + writeInt(byteBuf, length); + byteBuf.writeBytes(retainedByteBuf); + ReferenceCountUtil.release(retainedByteBuf); + } + } + + public static String readString(ByteBuf byteBuf) { + int length = readInt(byteBuf); + if (length <= 0) { + return StringUtils.EMPTY; + } + return (String) byteBuf.readCharSequence(length, StringUtils.DEFAULT_CHARSET); + } + + + //---------------------------------char-------------------------------------- + // 很多脚本语言没有char,所以这里使用string代替 + public static void writeChar(ByteBuf byteBuf, char value) { + writeString(byteBuf, String.valueOf(value)); + } + + public static char readChar(ByteBuf byteBuf) { + return readString(byteBuf).charAt(0); + } + + public static void writeCharBox(ByteBuf byteBuf, Character value) { + writeString(byteBuf, String.valueOf(value == null ? Character.MIN_VALUE : value)); + } + + public static Character readCharBox(ByteBuf byteBuf) { + return readString(byteBuf).charAt(0); + } + + //----------------------------------------------------------------------- + //---------------------------------以下方法会被字节码生成的代码调用-------------------------------------- + public static boolean writePacketFlag(ByteBuf byteBuf, IPacket packet) { + boolean flag = packet == null; + byteBuf.writeBoolean(!flag); + return flag; + } + + public static void writePacketCollection(ByteBuf byteBuf, Collection collection, IProtocolRegistration protocolRegistration) { + if (collection == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, collection.size()); + for (var packet : collection) { + protocolRegistration.write(byteBuf, packet); + } + } + + public static void writePacketList(ByteBuf byteBuf, List list, IProtocolRegistration protocolRegistration) { + writePacketCollection(byteBuf, list, protocolRegistration); + } + + public static List readPacketList(ByteBuf byteBuf, IProtocolRegistration protocolRegistration) { + var length = readInt(byteBuf); + var list = (List) CollectionUtils.newFixedList(length); + for (var i = 0; i < length; i++) { + list.add((IPacket) protocolRegistration.read(byteBuf)); + } + return list; + } + + public static void writePacketSet(ByteBuf byteBuf, Set list, IProtocolRegistration protocolRegistration) { + writePacketCollection(byteBuf, list, protocolRegistration); + } + + public static Set readPacketSet(ByteBuf byteBuf, IProtocolRegistration protocolRegistration) { + var length = readInt(byteBuf); + var set = (Set) CollectionUtils.newFixedSet(length); + for (var i = 0; i < length; i++) { + set.add((IPacket) protocolRegistration.read(byteBuf)); + } + return set; + } + + public static void writeIntIntMap(ByteBuf byteBuf, Map map) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeIntBox(byteBuf, entry.getKey()); + writeIntBox(byteBuf, entry.getValue()); + } + } + + public static Map readIntIntMap(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readIntBox(byteBuf), readIntBox(byteBuf)); + } + return map; + } + + public static void writeIntLongMap(ByteBuf byteBuf, Map map) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeIntBox(byteBuf, entry.getKey()); + writeLongBox(byteBuf, entry.getValue()); + } + } + + public static Map readIntLongMap(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readIntBox(byteBuf), readLongBox(byteBuf)); + } + return map; + } + + public static void writeLongIntMap(ByteBuf byteBuf, Map map) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeLongBox(byteBuf, entry.getKey()); + writeIntBox(byteBuf, entry.getValue()); + } + } + + public static Map readLongIntMap(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readLongBox(byteBuf), readIntBox(byteBuf)); + } + return map; + } + + public static void writeLongLongMap(ByteBuf byteBuf, Map map) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeLongBox(byteBuf, entry.getKey()); + writeLongBox(byteBuf, entry.getValue()); + } + } + + public static Map readLongLongMap(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readLongBox(byteBuf), readLongBox(byteBuf)); + } + return map; + } + + public static void writeIntStringMap(ByteBuf byteBuf, Map map) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeIntBox(byteBuf, entry.getKey()); + writeString(byteBuf, entry.getValue()); + } + } + + public static Map readIntStringMap(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readIntBox(byteBuf), readString(byteBuf)); + } + return map; + } + + public static void writeIntPacketMap(ByteBuf byteBuf, Map map, IProtocolRegistration protocolRegistration) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeIntBox(byteBuf, entry.getKey()); + protocolRegistration.write(byteBuf, entry.getValue()); + } + } + + public static Map readIntPacketMap(ByteBuf byteBuf, IProtocolRegistration protocolRegistration) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readIntBox(byteBuf), (IPacket) protocolRegistration.read(byteBuf)); + } + return map; + } + + public static void writeLongPacketMap(ByteBuf byteBuf, Map map, IProtocolRegistration protocolRegistration) { + if (map == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, map.size()); + for (var entry : map.entrySet()) { + writeLongBox(byteBuf, entry.getKey()); + protocolRegistration.write(byteBuf, entry.getValue()); + } + } + + public static Map readLongPacketMap(ByteBuf byteBuf, IProtocolRegistration protocolRegistration) { + var length = readInt(byteBuf); + var map = (Map) CollectionUtils.newFixedMap(length); + for (var i = 0; i < length; i++) { + map.put(readLongBox(byteBuf), (IPacket) protocolRegistration.read(byteBuf)); + } + return map; + } + + + //---------------------------------boolean-------------------------------------- + public static void writeBooleanArray(ByteBuf byteBuf, boolean[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + var length = array.length; + writeInt(byteBuf, length); + var bytes = new byte[length]; + for (var i = 0; i < length; i++) { + bytes[i] = (byte) (array[i] ? 1 : 0); + } + byteBuf.writeBytes(bytes); + } + + public static boolean[] readBooleanArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var bytes = new byte[length]; + var array = new boolean[length]; + byteBuf.readBytes(bytes); + for (var i = 0; i < length; i++) { + array[i] = bytes[i] != 0; + } + return array; + } + + public static void writeBooleanBoxArray(ByteBuf byteBuf, Boolean[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeBooleanBox(byteBuf, value); + } + } + + public static Boolean[] readBooleanBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var array = new Boolean[length]; + for (var i = 0; i < length; i++) { + array[i] = readBooleanBox(byteBuf); + } + return array; + } + + + //---------------------------------byte-------------------------------------- + public static void writeByteArray(ByteBuf byteBuf, byte[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + byteBuf.writeBytes(array); + } + + public static byte[] readByteArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + if (length == 0) { + return StringUtils.EMPTY_BYTES; + } + + var bytes = new byte[length]; + byteBuf.readBytes(bytes); + return bytes; + } + + public static void writeByteBoxArray(ByteBuf byteBuf, Byte[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeByteBox(byteBuf, value); + } + } + + public static Byte[] readByteBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var bytesBox = new Byte[length]; + for (var i = 0; i < length; i++) { + bytesBox[i] = readByteBox(byteBuf); + } + return bytesBox; + } + + + //---------------------------------short-------------------------------------- + public static void writeShortArray(ByteBuf byteBuf, short[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + var length = array.length; + writeInt(byteBuf, length); + var writeIndex = byteBuf.writerIndex(); + byteBuf.ensureWritable(length * 2); + for (var value : array) { + byteBuf.setShort(writeIndex, value); + writeIndex += 2; + } + byteBuf.writerIndex(writeIndex); + } + + public static short[] readShortArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var shorts = new short[length]; + var readIndex = byteBuf.readerIndex(); + for (var i = 0; i < length; i++) { + shorts[i] = byteBuf.getShort(readIndex); + readIndex += 2; + } + byteBuf.readerIndex(readIndex); + return shorts; + } + + public static void writeShortBoxArray(ByteBuf byteBuf, Short[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeShortBox(byteBuf, value); + } + } + + public static Short[] readShortBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var shorts = new Short[length]; + for (var i = 0; i < length; i++) { + shorts[i] = readShortBox(byteBuf); + } + return shorts; + } + + + //---------------------------------int-------------------------------------- + public static void writeIntArray(ByteBuf byteBuf, int[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeInt(byteBuf, value); + } + } + + public static int[] readIntArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var ints = new int[length]; + for (var i = 0; i < length; i++) { + ints[i] = readInt(byteBuf); + } + return ints; + } + + public static void writeIntBoxArray(ByteBuf byteBuf, Integer[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeIntBox(byteBuf, value); + } + } + + public static Integer[] readIntBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var ints = new Integer[length]; + for (var i = 0; i < length; i++) { + ints[i] = readIntBox(byteBuf); + } + return ints; + } + + public static void writeIntCollection(ByteBuf byteBuf, Collection collection) { + if (collection == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, collection.size()); + for (var value : collection) { + writeIntBox(byteBuf, value); + } + } + + public static void writeIntList(ByteBuf byteBuf, List list) { + writeIntCollection(byteBuf, list); + } + + public static List readIntList(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var list = (List) CollectionUtils.newFixedList(length); + for (var i = 0; i < length; i++) { + list.add(readIntBox(byteBuf)); + } + return list; + } + + public static void writeIntSet(ByteBuf byteBuf, Set set) { + writeIntCollection(byteBuf, set); + } + + public static Set readIntSet(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var list = (Set) CollectionUtils.newFixedSet(length); + for (var i = 0; i < length; i++) { + list.add(readIntBox(byteBuf)); + } + return list; + } + + + //---------------------------------long-------------------------------------- + public static void writeLongArray(ByteBuf byteBuf, long[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeLong(byteBuf, value); + } + } + + public static long[] readLongArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var longs = new long[length]; + for (var i = 0; i < length; i++) { + longs[i] = readLong(byteBuf); + } + return longs; + } + + public static void writeLongBoxArray(ByteBuf byteBuf, Long[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeLongBox(byteBuf, value); + } + } + + public static Long[] readLongBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var longs = new Long[length]; + for (var i = 0; i < length; i++) { + longs[i] = readLongBox(byteBuf); + } + return longs; + } + + public static void writeLongCollection(ByteBuf byteBuf, Collection collection) { + if (collection == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, collection.size()); + for (var value : collection) { + writeLongBox(byteBuf, value); + } + } + + public static void writeLongList(ByteBuf byteBuf, List list) { + writeLongCollection(byteBuf, list); + } + + public static List readLongList(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var list = (List) CollectionUtils.newFixedList(length); + for (var i = 0; i < length; i++) { + list.add(readLongBox(byteBuf)); + } + return list; + } + + public static void writeLongSet(ByteBuf byteBuf, Set set) { + writeLongCollection(byteBuf, set); + } + + public static Set readLongSet(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var set = (Set) CollectionUtils.newFixedSet(length); + for (var i = 0; i < length; i++) { + set.add(readLongBox(byteBuf)); + } + return set; + } + + //---------------------------------float-------------------------------------- + public static void writeFloatArray(ByteBuf byteBuf, float[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + var length = array.length; + writeInt(byteBuf, length); + var writeIndex = byteBuf.writerIndex(); + byteBuf.ensureWritable(length * 4); + for (var value : array) { + byteBuf.setFloat(writeIndex, value); + writeIndex += 4; + } + byteBuf.writerIndex(writeIndex); + } + + public static float[] readFloatArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var floats = new float[length]; + var readIndex = byteBuf.readerIndex(); + for (var i = 0; i < length; i++) { + floats[i] = byteBuf.getFloat(readIndex); + readIndex += 4; + } + byteBuf.readerIndex(readIndex); + return floats; + } + + public static void writeFloatBoxArray(ByteBuf byteBuf, Float[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeFloatBox(byteBuf, value); + } + } + + public static Float[] readFloatBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var floats = new Float[length]; + for (var i = 0; i < length; i++) { + floats[i] = readFloatBox(byteBuf); + } + return floats; + } + + + //---------------------------------double-------------------------------------- + public static void writeDoubleArray(ByteBuf byteBuf, double[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + var length = array.length; + writeInt(byteBuf, length); + var writeIndex = byteBuf.writerIndex(); + byteBuf.ensureWritable(length * 8); + for (var value : array) { + byteBuf.setDouble(writeIndex, value); + writeIndex += 8; + } + byteBuf.writerIndex(writeIndex); + } + + public static double[] readDoubleArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var doubles = new double[length]; + var readIndex = byteBuf.readerIndex(); + for (var i = 0; i < length; i++) { + doubles[i] = byteBuf.getDouble(readIndex); + readIndex += 8; + } + byteBuf.readerIndex(readIndex); + return doubles; + } + + public static void writeDoubleBoxArray(ByteBuf byteBuf, Double[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeDoubleBox(byteBuf, value); + } + } + + public static Double[] readDoubleBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var doubles = new Double[length]; + for (var i = 0; i < length; i++) { + doubles[i] = readDoubleBox(byteBuf); + } + return doubles; + } + + + //---------------------------------string-------------------------------------- + public static void writeStringArray(ByteBuf byteBuf, String[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeString(byteBuf, value); + } + } + + public static String[] readStringArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var strings = new String[length]; + for (var i = 0; i < length; i++) { + strings[i] = readString(byteBuf); + } + return strings; + } + + public static void writeStringCollection(ByteBuf byteBuf, Collection collection) { + if (collection == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, collection.size()); + for (var value : collection) { + writeString(byteBuf, value); + } + } + + public static void writeStringList(ByteBuf byteBuf, List list) { + writeStringCollection(byteBuf, list); + } + + public static List readStringList(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var list = (List) CollectionUtils.newFixedList(length); + for (var i = 0; i < length; i++) { + list.add(readString(byteBuf)); + } + return list; + } + + public static void writeStringSet(ByteBuf byteBuf, Set set) { + writeStringCollection(byteBuf, set); + } + + public static Set readStringSet(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var list = (Set) CollectionUtils.newFixedSet(length); + for (var i = 0; i < length; i++) { + list.add(readString(byteBuf)); + } + return list; + } + + + //---------------------------------char-------------------------------------- + public static void writeCharArray(ByteBuf byteBuf, char[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeChar(byteBuf, value); + } + } + + public static char[] readCharArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var chars = new char[length]; + for (var i = 0; i < length; i++) { + chars[i] = readChar(byteBuf); + } + return chars; + } + + public static void writeCharBoxArray(ByteBuf byteBuf, Character[] array) { + if (array == null) { + byteBuf.writeByte(0); + return; + } + writeInt(byteBuf, array.length); + for (var value : array) { + writeCharBox(byteBuf, value); + } + } + + public static Character[] readCharBoxArray(ByteBuf byteBuf) { + var length = readInt(byteBuf); + var chars = new Character[length]; + for (var i = 0; i < length; i++) { + chars[i] = readCharBox(byteBuf); + } + return chars; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/ArrayUtils.java b/protocol/src/main/java/com/zfoo/protocol/collection/ArrayUtils.java new file mode 100644 index 00000000..b2436edd --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/ArrayUtils.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection; + +import com.zfoo.protocol.util.AssertionUtils; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class ArrayUtils { + /** + * length + */ + public static int length(boolean[] array) { + return array == null ? 0 : array.length; + } + + public static int length(byte[] array) { + return array == null ? 0 : array.length; + } + + public static int length(short[] array) { + return array == null ? 0 : array.length; + } + + public static int length(int[] array) { + return array == null ? 0 : array.length; + } + + public static int length(long[] array) { + return array == null ? 0 : array.length; + } + + public static int length(float[] array) { + return array == null ? 0 : array.length; + } + + public static int length(double[] array) { + return array == null ? 0 : array.length; + } + + public static int length(char[] array) { + return array == null ? 0 : array.length; + } + + public static int length(T[] array) { + return array == null ? 0 : array.length; + } + + /** + * toList + */ + public static List toList(boolean[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var value : array) { + list.add(value); + } + return list; + } + + public static List toList(byte[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var value : array) { + list.add(value); + } + return list; + } + + public static List toList(short[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var value : array) { + list.add(value); + } + return list; + } + + public static List toList(int[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var j : array) { + list.add(j); + } + return list; + } + + public static List toList(long[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var j : array) { + list.add(j); + } + return list; + } + + public static List toList(float[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var j : array) { + list.add(j); + } + return list; + } + + public static List toList(double[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var j : array) { + list.add(j); + } + return list; + } + + public static List toList(char[] array) { + if (array == null || array.length == 0) { + return Collections.emptyList(); + } + var list = new ArrayList(); + for (var j : array) { + list.add(j); + } + return list; + } + + public static List toList(T[] array) { + if (CollectionUtils.isEmpty(array)) { + return Collections.emptyList(); + } + return Arrays.asList(array); + } + + + public static T[] listToArray(List list, Class clazz) { + AssertionUtils.notNull(list); + AssertionUtils.notNull(clazz); + + var length = list.size(); + var objectArray = Array.newInstance(clazz, length); + + for (var i = 0; i < length; i++) { + Array.set(objectArray, i, list.get(i)); + } + + return (T[]) objectArray; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/CollectionUtils.java b/protocol/src/main/java/com/zfoo/protocol/collection/CollectionUtils.java new file mode 100644 index 00000000..738b2dfe --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/CollectionUtils.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection; + + +import com.zfoo.protocol.collection.model.NaturalComparator; +import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.util.AssertionUtils; + +import java.util.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class CollectionUtils { + + /** + * Return {@code true} if the supplied Collection is {@code null} or empty. + * Otherwise, return {@code false}. + * + * @param collection the Collection to check + * @return whether the given Collection is empty + */ + public static boolean isEmpty(Collection collection) { + return (collection == null || collection.isEmpty()); + } + + public static boolean isNotEmpty(Collection collection) { + return !isEmpty(collection); + } + + public static boolean isEmpty(Object[] array) { + return (array == null || array.length == 0); + } + + public static boolean isNotEmpty(Object[] array) { + return !isEmpty(array); + } + + /** + * Return {@code true} if the supplied Map is {@code null} or empty. + * Otherwise, return {@code false}. + * + * @param map the Map to check + * @return whether the given Map is empty + */ + public static boolean isEmpty(Map map) { + return (map == null || map.isEmpty()); + } + + public static boolean isNotEmpty(Map map) { + return !isEmpty(map); + } + + + public static int size(Collection collection) { + return collection == null ? 0 : collection.size(); + } + + public static int size(Map map) { + return map == null ? 0 : map.size(); + } + + public static Iterator iterator(Collection collection) { + return isEmpty(collection) ? Collections.emptyIterator() : collection.iterator(); + } + + public static Iterator> iterator(Map map) { + return isEmpty(map) ? Collections.emptyIterator() : map.entrySet().iterator(); + } + + + /** + * 固定大小集合,如果初始化容量为0,则后续无法继续增加集合容量 + */ + public static List newFixedList(int size) { + return size <= 0 ? Collections.EMPTY_LIST : new ArrayList<>(size); + } + + public static Set newFixedSet(int size) { + return size <= 0 ? Collections.EMPTY_SET : new HashSet<>(comfortableCapacity(size)); + } + + public static Map newFixedMap(int size) { + return size <= 0 ? Collections.EMPTY_MAP : new HashMap<>(comfortableCapacity(size)); + } + + /** + * The largest power of two that can be represented as an {@code int}. + */ + public static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * 计算HashMap初始化合适的大小 + *

+ * from com.google.common.collect.Maps.capacity() + */ + public static int comfortableCapacity(int expectedSize) { + if (expectedSize < 3) { + return expectedSize + 1; + } + + if (expectedSize < MAX_POWER_OF_TWO) { + return (int) ((float) expectedSize / 0.75F + 1.0F); + } + + // any large value + return Integer.MAX_VALUE; + } + + // ----------------------------------归并排序---------------------------------- + + /** + * Merges two sorted Collections, a and b, into a single, sorted List + * such that the natural ordering of the elements is retained. + *

+ * Uses the standard O(n) merge algorithm for combining two sorted lists. + *

+ * + * @param aList the first collection, must not be null + * @param bList the second collection, must not be null + * @return a new sorted List, containing the elements of Collection a and b + */ + public static > List collate(List aList, List bList) { + return collate(aList, bList, NaturalComparator.getInstance(), true); + } + + public static > List collate(List aList, List bList, boolean includeDuplicates) { + return collate(aList, bList, NaturalComparator.getInstance(), includeDuplicates); + } + + public static List collate(List aList, List bList, Comparator comparator) { + return collate(aList, bList, comparator, true); + } + + /** + * Merges two sorted Collections, a and b, into a single, sorted List + * such that the ordering of the elements according to Comparator c is retained. + *

+ * Uses the standard O(n) merge algorithm for combining two sorted lists. + *

+ * + * @param the element type + * @param aList the first collection, must not be null + * @param bList the second collection, must not be null + * @param comparator the comparator to use for the merge. + * @param includeDuplicates if {@code true} duplicate elements will be retained, otherwise + * they will be removed in the output collection + * @return a new sorted List, containing the elements of Collection a and b + */ + public static List collate(List aList, List bList, Comparator comparator, boolean includeDuplicates) { + + if (aList == null || bList == null) { + throw new NullPointerException("The collections must not be null"); + } + if (comparator == null) { + throw new NullPointerException("The comparator must not be null"); + } + + var totalSize = aList.size() + bList.size(); + + var mergedList = new ArrayList(totalSize); + + var aIndex = 0; + var bIndex = 0; + + T lastItem = null; + while (aIndex < aList.size() && bIndex < bList.size()) { + var a = aList.get(aIndex); + var b = bList.get(bIndex); + if (a == null) { + aIndex++; + continue; + } + if (b == null) { + bIndex++; + continue; + } + + if (comparator.compare(a, b) >= 0) { + bIndex++; + if (!includeDuplicates && lastItem != null && lastItem.equals(b)) { + continue; + } + mergedList.add(b); + lastItem = b; + } else { + aIndex++; + if (!includeDuplicates && lastItem != null && lastItem.equals(a)) { + continue; + } + mergedList.add(a); + lastItem = a; + } + + } + + if (aIndex < aList.size()) { + for (var i = aIndex; i < aList.size(); i++) { + var value = aList.get(i); + + if (!includeDuplicates && lastItem != null && lastItem.equals(value)) { + continue; + } + + mergedList.add(value); + lastItem = value; + } + } + + if (bIndex < bList.size()) { + for (var i = bIndex; i < bList.size(); i++) { + var value = bList.get(i); + + if (!includeDuplicates && lastItem != null && lastItem.equals(value)) { + continue; + } + + mergedList.add(value); + lastItem = value; + } + } + + mergedList.trimToSize(); + return mergedList; + } + + + /** + * list合并 + * + * @param exclusive 元素是否是独占的,也就是说是否可以重复 + * @param pairs 需要被合并的pairs集合,第一个参数是步数,第二个参数是集合 + * @return 返回合并后的list + */ + public static List listJoinList(boolean exclusive, Pair>... pairs) { + return listJoinList(exclusive, List.of(pairs)); + } + + public static List listJoinList(boolean exclusive, List>> pairs) { + var iteratorList = new ArrayList>(); + var iteratorMap = new HashMap, Iterator>(); + var stepMap = new HashMap, Integer>(); + for (var pair : pairs) { + var step = pair.getKey(); + var list = pair.getValue(); + AssertionUtils.ge1(step); + if (isNotEmpty(list)) { + var iterator = list.iterator(); + iteratorList.add(list); + iteratorMap.put(list, iterator); + stepMap.put(list, step); + } + } + + var result = new ArrayList(); + + while (iteratorMap.values().stream().anyMatch(it -> it.hasNext())) { + for (var list : iteratorList) { + var iterator = iteratorMap.get(list); + var step = stepMap.get(list); + for (var i = 0; i < step && iterator.hasNext(); i++) { + var element = iterator.next(); + if (exclusive && result.contains(element)) { + i--; + continue; + } + result.add(element); + } + } + } + + return result; + } + + /** + * 获取集合的最后几个元素 + */ + public static List subListLast(List list, int num) { + if (isEmpty(list)) { + return Collections.emptyList(); + } + + var startIndex = list.size() - num; + if (startIndex <= 0) { + return new ArrayList<>(list); + } + + var result = new ArrayList(); + + + for (T element : list) { + startIndex--; + if (startIndex < 0) { + result.add(element); + } + } + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentArrayList.java b/protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentArrayList.java new file mode 100644 index 00000000..83c63f4b --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentArrayList.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection; + +import java.util.*; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConcurrentArrayList implements List { + + private ReentrantLock lock; + + private ArrayList list; + + public ConcurrentArrayList() { + this.lock = new ReentrantLock(); + this.list = new ArrayList<>(); + } + + public ConcurrentArrayList(int initialCapacity) { + this.lock = new ReentrantLock(); + this.list = new ArrayList<>(initialCapacity); + } + + + @Override + public int size() { + return list.size(); + } + + @Override + public boolean isEmpty() { + return list.isEmpty(); + } + + public List clearAndReturn() { + lock.lock(); + try { + var newList = (ArrayList) list.clone(); + list.clear(); + return newList; + } finally { + lock.unlock(); + } + } + + @Override + public boolean contains(Object o) { + lock.lock(); + try { + return list.contains(o); + } finally { + lock.unlock(); + } + } + + @Override + public Iterator iterator() { + lock.lock(); + try { + var newList = (ArrayList) list.clone(); + return newList.iterator(); + } finally { + lock.unlock(); + } + } + + @Override + public Object[] toArray() { + lock.lock(); + try { + return list.toArray(); + } finally { + lock.unlock(); + } + } + + @Override + public T[] toArray(T[] a) { + lock.lock(); + try { + return list.toArray(a); + } finally { + lock.unlock(); + } + } + + @Override + public boolean add(E e) { + lock.lock(); + try { + return list.add(e); + } finally { + lock.unlock(); + } + } + + @Override + public boolean remove(Object o) { + lock.lock(); + try { + return list.remove(o); + } finally { + lock.unlock(); + } + } + + @Override + public boolean containsAll(Collection c) { + lock.lock(); + try { + return list.containsAll(c); + } finally { + lock.unlock(); + } + } + + @Override + public boolean addAll(Collection c) { + lock.lock(); + try { + return list.addAll(c); + } finally { + lock.unlock(); + } + } + + @Override + public boolean addAll(int index, Collection c) { + lock.lock(); + try { + return list.addAll(index, c); + } finally { + lock.unlock(); + } + } + + @Override + public boolean removeAll(Collection c) { + lock.lock(); + try { + return list.removeAll(c); + } finally { + lock.unlock(); + } + } + + @Override + public boolean retainAll(Collection c) { + lock.lock(); + try { + return list.retainAll(c); + } finally { + lock.unlock(); + } + } + + @Override + public void clear() { + lock.lock(); + try { + list.clear(); + } finally { + lock.unlock(); + } + } + + @Override + public E get(int index) { + return list.get(index); + } + + @Override + public E set(int index, E element) { + lock.lock(); + try { + return list.set(index, element); + } finally { + lock.unlock(); + } + } + + @Override + public void add(int index, E element) { + lock.lock(); + try { + list.add(index, element); + } finally { + lock.unlock(); + } + } + + @Override + public E remove(int index) { + lock.lock(); + try { + return list.remove(index); + } finally { + lock.unlock(); + } + } + + @Override + public int indexOf(Object o) { + lock.lock(); + try { + return list.indexOf(o); + } finally { + lock.unlock(); + } + } + + @Override + public int lastIndexOf(Object o) { + lock.lock(); + try { + return list.lastIndexOf(o); + } finally { + lock.unlock(); + } + } + + @Override + public ListIterator listIterator() { + lock.lock(); + try { + var newList = (ArrayList) list.clone(); + return newList.listIterator(); + } finally { + lock.unlock(); + } + } + + @Override + public ListIterator listIterator(int index) { + lock.lock(); + try { + var newList = (ArrayList) list.clone(); + return newList.listIterator(index); + } finally { + lock.unlock(); + } + } + + @Override + public List subList(int fromIndex, int toIndex) { + lock.lock(); + try { + return list.subList(fromIndex, toIndex); + } finally { + lock.unlock(); + } + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + + if (this != o) { + return false; + } + + return list.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentHashSet.java b/protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentHashSet.java new file mode 100644 index 00000000..5a06afdd --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/ConcurrentHashSet.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConcurrentHashSet extends AbstractSet { + + private Map map; + + public ConcurrentHashSet() { + this.map = new ConcurrentHashMap<>(); + } + + public ConcurrentHashSet(int initialCapacity) { + this.map = new ConcurrentHashMap<>(initialCapacity); + } + + @Override + public Iterator iterator() { + return map.keySet().iterator(); + } + + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + @Override + public boolean add(E e) { + return map.put(e, Boolean.TRUE) == null; + } + + @Override + public boolean remove(Object o) { + return map.remove(o) != null; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public void clear() { + map.clear(); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/model/NaturalComparator.java b/protocol/src/main/java/com/zfoo/protocol/collection/model/NaturalComparator.java new file mode 100644 index 00000000..7a00100a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/model/NaturalComparator.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection.model; + +import java.util.Comparator; + +public class NaturalComparator> implements Comparator { + + + /** + * The singleton instance. + */ + private static final NaturalComparator INSTANCE = new NaturalComparator<>(); + + //----------------------------------------------------------------------- + + /** + * Constructor whose use should be avoided. + *

+ * Please use the {@link #getInstance()} method whenever possible. + */ + public NaturalComparator() { + super(); + } + + //----------------------------------------------------------------------- + + /** + * Gets the singleton instance of a ComparableComparator. + *

+ * Developers are encouraged to use the comparator returned from this method + * instead of constructing a new instance to reduce allocation and GC overhead + * when multiple comparable comparators may be used in the same VM. + * + * @param the element type + * @return the singleton ComparableComparator + */ + public static > NaturalComparator getInstance() { + return (NaturalComparator) INSTANCE; + } + + //----------------------------------------------------------------------- + + /** + * Compare the two {@link Comparable Comparable} arguments. + * This method is equivalent to: + *

((Comparable)obj1).compareTo(obj2)
+ * + * @param a the first object to compare + * @param b the second object to compare + * @return negative if obj1 is less, positive if greater, zero if equal + * @throws NullPointerException if obj1 is null, + * or when ((Comparable)obj1).compareTo(obj2) does + * @throws ClassCastException if obj1 is not a Comparable, + * or when ((Comparable)obj1).compareTo(obj2) does + */ + @Override + public int compare(final E a, final E b) { + return a.compareTo(b); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/tree/GeneralTree.java b/protocol/src/main/java/com/zfoo/protocol/collection/tree/GeneralTree.java new file mode 100644 index 00000000..0fd0355f --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/tree/GeneralTree.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection.tree; + +import com.zfoo.protocol.util.StringUtils; + +/** + * 多叉树 + * + * @author jaysunxiao + * @version 3.0 + */ +public class GeneralTree { + + private TreeNode rootNode = new TreeNode<>(null, null); + + public TreeNode getRootNode() { + return rootNode; + } + + public TreeNode getNodeByPath(String path) { + var current = rootNode; + var splitPath = splitPath(path); + for (var nodeName : splitPath) { + current = current.childByName(nodeName); + if (current == null) { + return null; + } + } + return current; + } + + public void addNode(String path, T data) { + var current = rootNode; + + var splitPath = splitPath(path); + for (var nodeName : splitPath) { + current = current.getOrAddChild(nodeName); + } + current.setData(data); + } + + public void removeNode(String path) { + var current = rootNode; + var parent = current.getParent(); + var splitPath = splitPath(path); + for (var nodeName : splitPath) { + parent = current; + current = current.childByName(nodeName); + if (current == null) { + return; + } + } + + if (parent != null) { + parent.removeChild(current.getName()); + } + } + + + /** + * 移除所有数据结点 + */ + public void clear() { + rootNode.clear(); + } + + private String[] splitPath(String path) { + if (StringUtils.isBlank(path)) { + return StringUtils.EMPTY_ARRAY; + } + + if (!path.contains(StringUtils.PERIOD)) { + return new String[]{path}; + } + + return path.split(StringUtils.PERIOD_REGEX); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/collection/tree/TreeNode.java b/protocol/src/main/java/com/zfoo/protocol/collection/tree/TreeNode.java new file mode 100644 index 00000000..78e81afa --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/collection/tree/TreeNode.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.collection.tree; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TreeNode { + + private String name; + private T data; + private TreeNode parent; + private List> children; + + + /** + * 创建树的结点 + * + * @param name 数据结点名称 + * @param parent 父数据结点 + */ + + TreeNode(String name, TreeNode parent) { + this.name = name; + this.parent = parent; + } + + /** + * 检测数据结点名称是否合法 + */ + private static void checkName(String name) { + if (StringUtils.isBlank(name) || name.contains(StringUtils.PERIOD)) { + throw new RuntimeException("Name of tree node is invalid."); + } + } + + + /** + * 获取数据结点的完整名称。 + */ + public String fullName() { + if (parent == null) { + return name; + } + + var parentName = parent.fullName(); + if (parentName == null) { + return name; + } + + return StringUtils.format("{}{}{}", parentName, StringUtils.PERIOD, name); + } + + + /** + * 根据名称检查是否存在子数据结点 + * + * @param name 子数据结点名称 + * @return 是否存在子数据结点 + */ + public boolean hasChild(String name) { + checkName(name); + + if (children == null) { + return false; + } + + for (var child : children) { + if (child.name.equals(name)) { + return true; + } + } + + return false; + } + + /** + * 根据名称获取子数据结点 + * + * @param name 子数据结点名称 + * @return 指定名称的子数据结点,如果没有找到,则返回空 + */ + public TreeNode childByName(String name) { + checkName(name); + + if (children == null) { + return null; + } + + for (var child : children) { + if (child.name.equals(name)) { + return child; + } + } + + return null; + } + + /** + * 获取所有子节点,包括父节点和子节点的子节点 + */ + public List> flatTreeNodes() { + var result = new ArrayList>(); + result.add(this); + + if (CollectionUtils.isEmpty(children)) { + return result; + } + + var queue = new LinkedList<>(children); + result.addAll(queue); + while (!queue.isEmpty()) { + var childTreeNode = queue.poll(); + var childChildren = childTreeNode.getChildren(); + if (CollectionUtils.isEmpty(childChildren)) { + continue; + } + for (var subClassId : childTreeNode.getChildren()) { + result.add(subClassId); + queue.offer(subClassId); + } + } + + return result; + } + + + /** + * 根据名称获取或增加子数据结点 + * + * @param name 子数据结点名称 + * @return 指定名称的子数据结点,如果对应名称的子数据结点已存在,则返回已存在的子数据结点,否则增加子数据结点 + */ + public TreeNode getOrAddChild(String name) { + var node = childByName(name); + if (node != null) { + return node; + } + + node = new TreeNode<>(name, this); + + if (children == null) { + children = new ArrayList<>(); + } + + children.add(node); + + return node; + } + + + /** + * 根据名称移除子数据结点 + * + * @param name 子数据结点名称 + */ + public void removeChild(String name) { + var node = childByName(name); + if (node == null) { + return; + } + + children.remove(node); + } + + public void clear() { + name = null; + data = null; + parent = null; + children = null; + } + + public int childCount() { + if (CollectionUtils.isEmpty(children)) { + return 0; + } + return children.size(); + } + + + public String getName() { + return name; + } + + public List> getChildren() { + return children; + } + + public TreeNode getParent() { + return parent; + + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + @Override + public String toString() { + return StringUtils.format("[{}]:[{}]", fullName(), data); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/exception/AssertException.java b/protocol/src/main/java/com/zfoo/protocol/exception/AssertException.java new file mode 100644 index 00000000..ad5a0f3a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/exception/AssertException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.exception; + +import com.zfoo.protocol.util.StringUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class AssertException extends RuntimeException { + + public AssertException(String message) { + super(message); + } + + public AssertException(String template, Object... args) { + super(StringUtils.format(template, args)); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/exception/ExceptionUtils.java b/protocol/src/main/java/com/zfoo/protocol/exception/ExceptionUtils.java new file mode 100644 index 00000000..f117d516 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/exception/ExceptionUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.exception; + +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; + +public abstract class ExceptionUtils { + + /** + * 获取异常全部信息,格式是: + *

+ * 类名称: 异常信息 + * 异常堆栈 + *

+ * + * @param throwable the throwable to get a message for, null returns empty string + * @return 异常的信息 + */ + public static String getMessage(final Throwable throwable) { + if (throwable == null) { + return StringUtils.EMPTY; + } + final String className = throwable.getClass().getName(); + return className + ": " + throwable.getMessage() + FileUtils.LS + + getStackTrace(throwable); + } + + + /** + *

Gets the stack trace from a Throwable as a String.

+ *

The result of this method vary by JDK version as this method uses {@link Throwable#printStackTrace(java.io.PrintWriter)}. + * + * @param throwable the Throwable to be examined + * @return the stack trace as generated by the exception's + */ + public static String getStackTrace(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + return sw.getBuffer().toString(); + } + + public static String getCurrentStackTrace() { + var builder = new StringBuilder(); + var stackTraces = Thread.currentThread().getStackTrace(); + Arrays.stream(stackTraces).forEach(it -> builder.append(it.toString()).append(FileUtils.LS)); + return builder.toString(); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/exception/POJOException.java b/protocol/src/main/java/com/zfoo/protocol/exception/POJOException.java new file mode 100644 index 00000000..49c1c77e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/exception/POJOException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.exception; + +/** + * 不是一个POJO对象,POJO对象不应该继承别的类 + * + * @author jaysunxiao + * @version 3.0 + */ +public class POJOException extends RuntimeException { + + private static final String MESSAGE = "not a POJO object, can't extend other object"; + + private static final String HYPHEN = "-";//连接号,连接号与破折号的区别是,连接号的两头不用空格 + + private static final String LEFT_SQUARE_BRACKET = "[";//左方括号 + + private static final String RIGHT_SQUARE_BRACKET = "]";//右方括号 + + public POJOException() { + super(POJOException.MESSAGE); + } + + public POJOException(String message) { + super(POJOException.MESSAGE + POJOException.HYPHEN + POJOException.LEFT_SQUARE_BRACKET + message + POJOException.RIGHT_SQUARE_BRACKET); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/exception/RunException.java b/protocol/src/main/java/com/zfoo/protocol/exception/RunException.java new file mode 100644 index 00000000..ef393e59 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/exception/RunException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.exception; + +import com.zfoo.protocol.util.StringUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RunException extends RuntimeException { + + public RunException(Throwable cause) { + super(cause); + } + + public RunException(String message) { + super(message); + } + + public RunException(String template, Object... args) { + super(StringUtils.format(template, args)); + } + + public RunException(Throwable cause, String message) { + super(message, cause); + } + + public RunException(Throwable cause, String template, Object... args) { + super(StringUtils.format(template, args), cause); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/exception/UnknownException.java b/protocol/src/main/java/com/zfoo/protocol/exception/UnknownException.java new file mode 100644 index 00000000..d4eadb18 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/exception/UnknownException.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.exception; + +import com.zfoo.protocol.util.StringUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class UnknownException extends RuntimeException { + + public UnknownException(Throwable cause) { + super(cause); + } + + public UnknownException(String message) { + super(message); + } + + public UnknownException(String template, Object... args) { + super(StringUtils.format(template, args)); + } + + public UnknownException(Throwable cause, String message) { + super(message, cause); + } + + public UnknownException(Throwable cause, String template, Object... args) { + super(StringUtils.format(template, args), cause); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/generate/GenerateOperation.java b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateOperation.java new file mode 100644 index 00000000..0290896a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateOperation.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.generate; + +/** + * 创建协议文件的操作类 + * + * @author jaysunxiao + * @version 3.0 + */ +public class GenerateOperation { + + /** + * 不创建任何协议文件 + */ + public static final GenerateOperation NO_OPERATION = new GenerateOperation(); + + /** + * 折叠协议,生成协议文件会和Java源文件保持相同的目录结构 + */ + private boolean foldProtocol; + + /** + * 生成协议文件的后缀名称,如果不指定,用语言约定的默认名称 + */ + private String protocolParam; + + /** + * 生成javascript协议文件 + */ + private boolean generateJsProtocol; + + /** + * 生成C#协议文件 + */ + private boolean generateCsharpProtocol; + + /** + * 生成Lua协议文件 + */ + private boolean generateLuaProtocol; + + public boolean isFoldProtocol() { + return foldProtocol; + } + + public void setFoldProtocol(boolean foldProtocol) { + this.foldProtocol = foldProtocol; + } + + public String getProtocolParam() { + return protocolParam; + } + + public void setProtocolParam(String protocolParam) { + this.protocolParam = protocolParam; + } + + public boolean isGenerateJsProtocol() { + return generateJsProtocol; + } + + public void setGenerateJsProtocol(boolean generateJsProtocol) { + this.generateJsProtocol = generateJsProtocol; + } + + public boolean isGenerateCsharpProtocol() { + return generateCsharpProtocol; + } + + public void setGenerateCsharpProtocol(boolean generateCsharpProtocol) { + this.generateCsharpProtocol = generateCsharpProtocol; + } + + public boolean isGenerateLuaProtocol() { + return generateLuaProtocol; + } + + public void setGenerateLuaProtocol(boolean generateLuaProtocol) { + this.generateLuaProtocol = generateLuaProtocol; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolDocument.java b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolDocument.java new file mode 100644 index 00000000..192d9433 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolDocument.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.generate; + +import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * 生成协议的时候,协议的文档注释和字段注释会使用这个类 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateProtocolDocument { + + // 临时变量,启动完成就会销毁,协议的文档,外层map的key为协议类;pair的key为总的注释,value为属性字段的注释,value表示的map的key为属性名称 + // 比如在Test中的ComplexObject生成的pari是如下格式 + /** + * key docTitle: + * // 复杂的对象 + * // 包括了各种复杂的结构,数组,List,Set,Map + * // + * // @author jaysunxiao + * // @version 1.0 + *

+ * value aa: + * // byte的包装类型 + * // 优先使用基础类型,包装类型会有装箱拆箱 + */ + private static Map>> tempProtocolDocumentMap = new HashMap<>(); + + + public static void clear() { + tempProtocolDocumentMap.clear(); + tempProtocolDocumentMap = null; + } + + + /** + * 此方法仅在生成协议的时候调用,一旦运行,不能调用 + */ + public static Pair> getProtocolDocument(short protocolId) { + AssertionUtils.notNull(tempProtocolDocumentMap, "[{}]已经初始完成,初始化完成过后不能调用getProtocolDocument", GenerateProtocolDocument.class.getSimpleName()); + + var protocolDocument = tempProtocolDocumentMap.get(protocolId); + if (protocolDocument == null) { + return new Pair<>(StringUtils.EMPTY, Collections.emptyMap()); + } + return protocolDocument; + } + + + public static void initProtocolDocument(List protocolRegistrations) { + AssertionUtils.notNull(tempProtocolDocumentMap, "[{}]已经初始完成,初始化完成过后不能调用initProtocolDocument", GenerateProtocolDocument.class.getSimpleName()); + + for (var protocolRegistration : protocolRegistrations) { + var protocolClazzName = protocolRegistration.protocolConstructor().getDeclaringClass().getSimpleName(); + + // 文件的注释生成 + var proAbsFile = new File(FileUtils.getProAbsPath()); + var list = FileUtils.getAllReadableFiles(proAbsFile.getParentFile() == null ? proAbsFile : proAbsFile.getParentFile()); + var protocolFile = list.stream() + .filter(it -> it.getName().equals(StringUtils.format("{}.java", protocolClazzName))) + .findFirst(); + + // 如果搜索不到协议文件则直接返回 + if (protocolFile.isEmpty()) { + continue; + } + + var docFieldMap = new HashMap(); + var docTitle = StringUtils.EMPTY; + + var protocolStringList = FileUtils.readFileToStringList(protocolFile.get()) + .stream() + .dropWhile(it -> !it.startsWith("package")) // 过滤掉package之上的版权信息 + .collect(Collectors.toList()); + + // 搜索包名,报名不匹配则直接返回 + var protocolClassTitle = StringUtils.format("public class {}", protocolClazzName); + if (protocolStringList.stream().noneMatch(it -> it.contains(protocolClassTitle))) { + continue; + } + + protocolStringList = protocolStringList.stream() + .dropWhile(it -> !it.startsWith("package")) + .collect(Collectors.toList()); + + var docBuilder = new StringBuilder(); + var docTitleBuilder = new StringBuilder(); + for (var line : protocolStringList) { + var startLineStr = line.trim(); + + // 排除java的包头 + if (startLineStr.startsWith("package") || startLineStr.startsWith("import")) { + continue; + } + + + if (startLineStr.startsWith("public class ")) { + if (docTitleBuilder != null) { + docTitle = docTitleBuilder.toString(); + docTitle = docTitle.replace("/**", StringUtils.EMPTY); + docTitle = docTitle.replace(" */", StringUtils.EMPTY); + docTitle = docTitle.replace(" *", "//"); + docTitle = docTitle.trim(); + docBuilder = new StringBuilder(); + docTitleBuilder = null; + } + } else { + if (docTitleBuilder != null) { + docTitleBuilder.append(line).append(LS); + } + } + + // 保留注释 + if (startLineStr.startsWith("*/")) { + continue; + } + + if (startLineStr.startsWith("//") || startLineStr.startsWith("*")) { + startLineStr = startLineStr.replaceFirst("//", StringUtils.EMPTY); + startLineStr = startLineStr.replaceFirst("\\*", StringUtils.EMPTY); + docBuilder.append("//").append(startLineStr).append(LS); + continue; + } + + if (startLineStr.startsWith("private static ")) { + continue; + } + + if (startLineStr.contains(" transient ")) { + continue; + } + + if (startLineStr.startsWith("public void set") || startLineStr.startsWith("public bool equals") + || startLineStr.startsWith("public int hashCode") || startLineStr.startsWith("@Override")) { + continue; + } + + if (startLineStr.endsWith("{") || startLineStr.startsWith("return ") || startLineStr.startsWith("}")) { + continue; + } + + if (!startLineStr.endsWith(";")) { + continue; + } + if (!(startLineStr.startsWith("private ") || startLineStr.startsWith("public "))) { + continue; + } + + var fieldName = StringUtils.substringBeforeLast(StringUtils.substringAfterLast(startLineStr, StringUtils.SPACE), StringUtils.SEMICOLON).trim(); + docFieldMap.put(fieldName, docBuilder.toString()); + docBuilder = new StringBuilder(); + } + + tempProtocolDocumentMap.put(protocolRegistration.protocolId(), new Pair<>(docTitle, docFieldMap)); + } + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java new file mode 100644 index 00000000..38ab0278 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolFile.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.generate; + +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.registration.ProtocolRegistration; +import com.zfoo.protocol.serializer.cs.GenerateCsUtils; +import com.zfoo.protocol.serializer.js.GenerateJsUtils; +import com.zfoo.protocol.serializer.lua.GenerateLuaUtils; +import com.zfoo.protocol.util.ReflectionUtils; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateProtocolFile { + + /** + * 生成协议的过滤器,默认不过滤 + */ + public static Predicate generateProtocolFilter = registration -> true; + + + public static void generate(IProtocolRegistration[] protocols, GenerateOperation generateOperation) throws IOException { + + // 如果没有需要生成的协议则直接返回 + var generateProtocolFlag = Arrays.stream(generateOperation.getClass().getDeclaredFields()) + .filter(it -> it.getName().startsWith("generate")) + .peek(it -> ReflectionUtils.makeAccessible(it)) + .map(it -> ReflectionUtils.getField(it, generateOperation)) + .filter(it -> it instanceof Boolean) + .anyMatch(it -> ((Boolean) it).booleanValue() == true); + + if (!generateProtocolFlag) { + return; + } + + // 外层需要生成的协议 + var outsideGenerateProtocols = Arrays.stream(protocols) + .filter(it -> Objects.nonNull(it)) + .filter(it -> generateProtocolFilter.test(it)) + .collect(Collectors.toList()); + + // 需要生成的子协议,因为外层协议的内部有其它协议 + var insideGenerateProtocols = outsideGenerateProtocols.stream() + .map(it -> ProtocolManager.getAllSubProtocolIds(it.protocolId())) + .flatMap(it -> it.stream()) + .map(it -> protocols[it]) + .distinct() + .collect(Collectors.toList()); + + var allGenerateProtocols = new HashSet(); + allGenerateProtocols.addAll(outsideGenerateProtocols); + allGenerateProtocols.addAll(insideGenerateProtocols); + + // 通过协议号,从小到大排序 + var allSortedGenerateProtocols = allGenerateProtocols.stream() + .sorted((a, b) -> a.protocolId() - b.protocolId()) + .collect(Collectors.toList()); + + // 解析协议的文档注释 + GenerateProtocolDocument.initProtocolDocument(allSortedGenerateProtocols); + + + // 计算协议生成的路径 + if (generateOperation.isFoldProtocol()) { + GenerateProtocolPath.initProtocolPath(allSortedGenerateProtocols); + } + + // 生成C#协议 + if (generateOperation.isGenerateCsharpProtocol()) { + GenerateCsUtils.init(); + GenerateCsUtils.createProtocolManager(); + allSortedGenerateProtocols.forEach(it -> GenerateCsUtils.createCsProtocolFile((ProtocolRegistration) it)); + } + + // 生成Javascript协议 + if (generateOperation.isGenerateJsProtocol()) { + GenerateJsUtils.init(); + allSortedGenerateProtocols.forEach(it -> GenerateJsUtils.createJsProtocolFile((ProtocolRegistration) it)); + GenerateJsUtils.createProtocolManager(allSortedGenerateProtocols); + } + + // 生成Lua协议 + if (generateOperation.isGenerateLuaProtocol()) { + GenerateLuaUtils.init(); + GenerateLuaUtils.createProtocolManager(allSortedGenerateProtocols); + allSortedGenerateProtocols.forEach(it -> GenerateLuaUtils.createLuaProtocolFile((ProtocolRegistration) it)); + } + + // 参数,以后可能会用,比如给Lua修改一个后缀名称 + var protocolParam = generateOperation.getProtocolParam(); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolPath.java b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolPath.java new file mode 100644 index 00000000..9be33ab8 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/generate/GenerateProtocolPath.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.generate; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.collection.tree.GeneralTree; +import com.zfoo.protocol.collection.tree.TreeNode; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 生成协议的时候,协议的最终生成路径会使用这个类 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateProtocolPath { + + // 临时变量,启动完成就会销毁,协议生成的路径 + private static Map tempProtocolPathMap = new HashMap<>(); + + + public static void clear() { + tempProtocolPathMap.clear(); + tempProtocolPathMap = null; + } + + /** + * 获取协议生成的路径 + */ + public static String getProtocolPath(short protocolId) { + AssertionUtils.notNull(tempProtocolPathMap, "[{}]已经初始完成,初始化完成过后不能调用getProtocolPath", GenerateProtocolPath.class.getSimpleName()); + + var protocolPath = tempProtocolPathMap.get(protocolId); + if (StringUtils.isBlank(protocolPath)) { + return StringUtils.EMPTY; + } + + return protocolPath.replaceAll(StringUtils.PERIOD_REGEX, StringUtils.SLASH); + } + + /** + * 获取协议生成的首字母大写的路径 + */ + public static String getCapitalizeProtocolPath(short protocolId) { + return StringUtils.joinWith(StringUtils.SLASH, Arrays.stream(getProtocolPath(protocolId).split(StringUtils.SLASH)).map(it -> StringUtils.capitalize(it)).toArray()); + } + + /** + * 解析协议的路径 + * + * @param protocolRegistrations 需要解析的路径 + */ + public static void initProtocolPath(List protocolRegistrations) { + AssertionUtils.notNull(tempProtocolPathMap, "[{}]已经初始完成,初始化完成过后不能调用initProtocolPath", GenerateProtocolPath.class.getSimpleName()); + + // 将需要生成的协议的路径添加到多叉树中 + var protocolPathTree = new GeneralTree(); + protocolRegistrations.forEach(it -> protocolPathTree.addNode(it.protocolConstructor().getDeclaringClass().getCanonicalName(), it)); + + var rootTreeNode = protocolPathTree.getRootNode(); + + if (CollectionUtils.isEmpty(rootTreeNode.getChildren())) { + return; + } + + var queue = new LinkedList<>(rootTreeNode.getChildren()); + while (!queue.isEmpty()) { + var childTreeNode = queue.poll(); + var childChildren = childTreeNode.getChildren(); + // 如果子节点为空,则以当前节点为路径 + if (CollectionUtils.isEmpty(childChildren)) { + toProtocolPath(childTreeNode); + continue; + } + + // 如果子节点的协议数据有一个不为空的,则以当前节点为路径 + if (childChildren.stream().anyMatch(it -> it.getData() != null)) { + toProtocolPath(childTreeNode); + continue; + } + + // 继续深度便利子节点的路径 + for (var subClassId : childTreeNode.getChildren()) { + queue.offer(subClassId); + } + } + } + + private static void toProtocolPath(TreeNode protocolTreeNode) { + var allChildren = protocolTreeNode.flatTreeNodes() + .stream() + .filter(it -> it.getData() != null) + .collect(Collectors.toList()); + var pathBefore = StringUtils.substringBeforeLast(protocolTreeNode.fullName(), StringUtils.PERIOD); + for (var child : allChildren) { + var protocolSimpleName = child.getData().protocolConstructor().getDeclaringClass().getSimpleName(); + var splits = Arrays.stream(StringUtils.substringBeforeLast(StringUtils.substringAfterFirst(child.fullName(), pathBefore), protocolSimpleName) + .split(StringUtils.PERIOD_REGEX)) + .filter(it -> !StringUtils.isBlank(it)) + .toArray(); + tempProtocolPathMap.put(child.getData().protocolId(), StringUtils.joinWith(StringUtils.PERIOD, splits)); + } + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/model/Pair.java b/protocol/src/main/java/com/zfoo/protocol/model/Pair.java new file mode 100644 index 00000000..acd6a17a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/model/Pair.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.model; + + +import java.util.Objects; + +/** + * 键值对对象,只能在构造时传入键值 + * + * @param 键类型 + * @param 值类型 + * @author jaysunxiao + * @version 3.0 + */ +public class Pair { + + private K key; + private V value; + + public Pair() { + + } + + /** + * 构造 + * + * @param key 键 + * @param value 值 + */ + public Pair(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * 获取键 + * + * @return 键 + */ + public K getKey() { + return this.key; + } + + /** + * 获取值 + * + * @return 值 + */ + public V getValue() { + return this.value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Pair pair = (Pair) o; + return Objects.equals(key, pair.key) && Objects.equals(value, pair.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + + @Override + public String toString() { + return "Pair [key=" + key + ", value=" + value + "]"; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/model/Quaternion.java b/protocol/src/main/java/com/zfoo/protocol/model/Quaternion.java new file mode 100644 index 00000000..4c7a1629 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/model/Quaternion.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.model; + + +/** + * T cardinal number that is the sum of three and one. + * + * @author jaysunxiao + * @version 3.0 + */ +public class Quaternion { + + private A a; + private B b; + private C c; + private D d; + + public Quaternion() { + } + + public Quaternion(A a, B b, C c, D d) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + public A getA() { + return a; + } + + public B getB() { + return b; + } + + public C getC() { + return c; + } + + public D getD() { + return d; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/model/Triple.java b/protocol/src/main/java/com/zfoo/protocol/model/Triple.java new file mode 100644 index 00000000..9d52e04a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/model/Triple.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.model; + + +/** + * A triple consisting of three elements. It refers to the elements as 'left', 'middle' and 'right'. + * + * @author jaysunxiao + * @version 3.0 + */ +public class Triple { + + private L left; + private M middle; + private R right; + + public Triple() { + } + + public Triple(L left, M middle, R right) { + this.left = left; + this.middle = middle; + this.right = right; + } + + public L getLeft() { + return left; + } + + public M getMiddle() { + return middle; + } + + public R getRight() { + return right; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/EnhanceUtils.java b/protocol/src/main/java/com/zfoo/protocol/registration/EnhanceUtils.java new file mode 100644 index 00000000..1e3f5f52 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/EnhanceUtils.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration; + +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.collection.ArrayUtils; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.*; +import com.zfoo.protocol.serializer.enhance.*; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import io.netty.buffer.ByteBuf; +import javassist.*; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 对应于ProtocolRegistration + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class EnhanceUtils { + + // 临时变量,是一个基本类型序列化器对应的增强类型序列化器 + private static Map tempEnhanceSerializerMap = new HashMap<>(); + + public static String byteBufUtils = ByteBufUtils.class.getSimpleName(); + public static String byteBufUtilsWriteBooleanFalse = byteBufUtils + ".writeBoolean($1, false);"; + public static String byteBufUtilsWriteBooleanTrue = byteBufUtils + ".writeBoolean($1, true);"; + public static String byteBufUtilsReadBoolean = byteBufUtils + ".readBoolean($1)"; + public static String byteBufUtilsWriteInt0 = byteBufUtils + ".writeInt($1, 0);"; + + static { + var classArray = new Class[]{ + IPacket.class, + IProtocolRegistration.class, + IFieldRegistration.class, + ByteBuf.class + }; + + var classPool = ClassPool.getDefault(); + + // 导入需要的包 + classPool.importPackage(IPacket.class.getCanonicalName()); + classPool.importPackage(ByteBufUtils.class.getCanonicalName()); + classPool.importPackage(Collections.class.getCanonicalName()); + classPool.importPackage(CollectionUtils.class.getCanonicalName()); + classPool.importPackage(ArrayUtils.class.getCanonicalName()); + classPool.importPackage(Iterator.class.getCanonicalName()); + classPool.importPackage(List.class.getCanonicalName()); + classPool.importPackage(ArrayList.class.getCanonicalName()); + classPool.importPackage(Map.class.getCanonicalName()); + classPool.importPackage(HashMap.class.getCanonicalName()); + classPool.importPackage(Set.class.getCanonicalName()); + classPool.importPackage(HashSet.class.getCanonicalName()); + + // 增加类的路径 + for (var clazz : classArray) { + if (classPool.find(clazz.getCanonicalName()) == null) { + ClassClassPath classPath = new ClassClassPath(clazz); + classPool.insertClassPath(classPath); + } + } + + tempEnhanceSerializerMap.put(BooleanSerializer.getInstance(), new EnhanceBooleanSerializer()); + tempEnhanceSerializerMap.put(ByteSerializer.getInstance(), new EnhanceByteSerializer()); + tempEnhanceSerializerMap.put(ShortSerializer.getInstance(), new EnhanceShortSerializer()); + tempEnhanceSerializerMap.put(IntSerializer.getInstance(), new EnhanceIntSerializer()); + tempEnhanceSerializerMap.put(LongSerializer.getInstance(), new EnhanceLongSerializer()); + tempEnhanceSerializerMap.put(FloatSerializer.getInstance(), new EnhanceFloatSerializer()); + tempEnhanceSerializerMap.put(DoubleSerializer.getInstance(), new EnhanceDoubleSerializer()); + tempEnhanceSerializerMap.put(CharSerializer.getInstance(), new EnhanceCharSerializer()); + tempEnhanceSerializerMap.put(StringSerializer.getInstance(), new EnhanceStringSerializer()); + tempEnhanceSerializerMap.put(ObjectProtocolSerializer.getInstance(), new EnhanceObjectProtocolSerializer()); + tempEnhanceSerializerMap.put(ListSerializer.getInstance(), new EnhanceListSerializer()); + tempEnhanceSerializerMap.put(SetSerializer.getInstance(), new EnhanceSetSerializer()); + tempEnhanceSerializerMap.put(MapSerializer.getInstance(), new EnhanceMapSerializer()); + tempEnhanceSerializerMap.put(ArraySerializer.getInstance(), new EnhanceArraySerializer()); + } + + public static IEnhanceSerializer enhanceSerializer(ISerializer serializer) { + return tempEnhanceSerializerMap.get(serializer); + } + + public static void clear() { + tempEnhanceSerializerMap.clear(); + tempEnhanceSerializerMap = null; + + byteBufUtils = null; + byteBufUtilsWriteBooleanFalse = null; + byteBufUtilsWriteBooleanTrue = null; + byteBufUtilsReadBoolean = null; + byteBufUtilsWriteInt0 = null; + } + + /** + * @param registration 需要增强的类 + * @return 返回类的名称格式:EnhanceUtilsProtocolRegistration1 + */ + public static IProtocolRegistration createProtocolRegistration(ProtocolRegistration registration) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + var classPool = ClassPool.getDefault(); + + GenerateUtils.index.set(0); + + short protocolId = registration.getId(); + IFieldRegistration[] packetFields = registration.getFieldRegistrations(); + + // 定义类名称 + CtClass enhanceClazz = classPool.makeClass(ProtocolRegistration.class.getCanonicalName() + protocolId); + enhanceClazz.addInterface(classPool.get(IProtocolRegistration.class.getCanonicalName())); + + // 定义类中的一个成员 + CtField constructorFiled = new CtField(classPool.get(Constructor.class.getCanonicalName()), "constructor", enhanceClazz); + constructorFiled.setModifiers(Modifier.PRIVATE); + enhanceClazz.addField(constructorFiled); + + // 定义类所包含的所有子协议成员 + var allSubProtocolIds = ProtocolManager.getAllSubProtocolIds(protocolId) + .stream() + .sorted((a, b) -> Short.compare(a, b)) + .collect(Collectors.toList()); + + for (var subProtocolId : allSubProtocolIds) { + var protocolRegistrationField = new CtField(classPool.get(IProtocolRegistration.class.getCanonicalName()), getProtocolRegistrationFieldNameByProtocolId(subProtocolId), enhanceClazz); + constructorFiled.setModifiers(Modifier.PRIVATE); + enhanceClazz.addField(protocolRegistrationField); + } + + // 定义类的构造器 + CtConstructor constructor = new CtConstructor(classPool.get(new String[]{Constructor.class.getCanonicalName()}), enhanceClazz); + constructor.setBody("{this.constructor=$1;}"); + constructor.setModifiers(Modifier.PUBLIC); + enhanceClazz.addConstructor(constructor); + + // 定义类实现的接口方法 + CtMethod protocolIdMethod = new CtMethod(classPool.get(short.class.getCanonicalName()), "protocolId", null, enhanceClazz); + protocolIdMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + protocolIdMethod.setBody("{return " + registration.protocolId() + ";}"); + enhanceClazz.addMethod(protocolIdMethod); + + CtMethod protocolConstructorMethod = new CtMethod(classPool.get(Constructor.class.getCanonicalName()), "protocolConstructor", null, enhanceClazz); + protocolConstructorMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + protocolConstructorMethod.setBody("{return this.constructor;}"); + enhanceClazz.addMethod(protocolConstructorMethod); + + CtMethod moduleMethod = new CtMethod(classPool.get(byte.class.getCanonicalName()), "module", null, enhanceClazz); + moduleMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + moduleMethod.setBody("{return " + registration.module() + ";}"); + enhanceClazz.addMethod(moduleMethod); + + CtMethod writeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "write", classPool.get(new String[]{ByteBuf.class.getCanonicalName(), IPacket.class.getCanonicalName()}), enhanceClazz); + writeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + writeMethod.setBody(writeMethodBody(registration)); + enhanceClazz.addMethod(writeMethod); + + CtMethod readMethod = new CtMethod(classPool.get(Object.class.getCanonicalName()), "read", classPool.get(new String[]{ByteBuf.class.getCanonicalName()}), enhanceClazz); + readMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + readMethod.setBody(readMethodBody(registration)); + enhanceClazz.addMethod(readMethod); + + // 释放缓存 + enhanceClazz.detach(); + + Class resultClazz = enhanceClazz.toClass(IProtocolRegistration.class); + Constructor resultConstructor = resultClazz.getConstructor(Constructor.class); + + return (IProtocolRegistration) resultConstructor.newInstance(registration.protocolConstructor()); + } + + // see: ProtocolRegistration.write() + private static String writeMethodBody(ProtocolRegistration registration) { + short protocolId = registration.getId(); + Constructor constructor = registration.getConstructor(); + Field[] fields = registration.getFields(); + IFieldRegistration[] fieldRegistrations = registration.getFieldRegistrations(); + + + Class packetClazz = constructor.getDeclaringClass(); + + StringBuilder builder = new StringBuilder(); + builder.append("{"); + builder.append(packetClazz.getCanonicalName() + " packet = (" + packetClazz.getCanonicalName() + ")$2;"); + builder.append("if(ByteBufUtils.writePacketFlag($1, packet)){") + .append("return;}"); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + IFieldRegistration fieldRegistration = fieldRegistrations[i]; + + if (Modifier.isPublic(field.getModifiers())) { + enhanceSerializer(fieldRegistration.serializer()) + .writeObject(builder, StringUtils.format("packet.{}", field.getName()), field, fieldRegistration); + } else { + enhanceSerializer(fieldRegistration.serializer()) + .writeObject(builder, StringUtils.format("packet.{}()", ReflectionUtils.fieldToGetMethod(packetClazz, field)), field, fieldRegistration); + } + } + + + builder.append("}"); + return builder.toString(); + } + + // see: ProtocolRegistration.read() + private static String readMethodBody(ProtocolRegistration registration) throws NoSuchMethodException { + short protocolId = registration.getId(); + Constructor constructor = registration.getConstructor(); + Field[] fields = registration.getFields(); + IFieldRegistration[] fieldRegistrations = registration.getFieldRegistrations(); + + StringBuilder builder = new StringBuilder(); + builder.append("{"); + builder.append("if(!" + EnhanceUtils.byteBufUtilsReadBoolean + "){") + .append("return null;}"); + Class packetClazz = constructor.getDeclaringClass(); + builder.append(packetClazz.getCanonicalName() + " packet=new " + packetClazz.getCanonicalName() + "();"); + + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + IFieldRegistration fieldRegistration = fieldRegistrations[i]; + + String readObject = enhanceSerializer(fieldRegistration.serializer()).readObject(builder, field, fieldRegistration); + + if (Modifier.isPublic(field.getModifiers())) { + builder.append(StringUtils.format("packet.{}={};", field.getName(), readObject)); + } else { + builder.append(StringUtils.format("packet.{}({});", ReflectionUtils.fieldToSetMethod(packetClazz, field), readObject)); + } + } + + builder.append("return packet;}"); + return builder.toString(); + } + + + public static String getProtocolRegistrationFieldNameByProtocolId(short id) { + return StringUtils.format("{}{}", StringUtils.uncapitalize(ProtocolRegistration.class.getSimpleName()), id); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/IProtocolRegistration.java b/protocol/src/main/java/com/zfoo/protocol/registration/IProtocolRegistration.java new file mode 100644 index 00000000..c455ef25 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/IProtocolRegistration.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration; + +import com.zfoo.protocol.IPacket; +import io.netty.buffer.ByteBuf; + +import java.lang.reflect.Constructor; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IProtocolRegistration { + + short protocolId(); + + byte module(); + + Constructor protocolConstructor(); + + Object read(ByteBuf buffer); + + void write(ByteBuf buffer, IPacket packet); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/ProtocolModule.java b/protocol/src/main/java/com/zfoo/protocol/registration/ProtocolModule.java new file mode 100644 index 00000000..d67e4da9 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/ProtocolModule.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration; + +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.StringUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ProtocolModule { + + public static final ProtocolModule DEFAULT_PROTOCOL_MODULE = new ProtocolModule((byte) 0, "default", "1.0.0"); + + private byte id; + + private String name; + /** + * 1.xxx.xxx,将1.0.0转化为1000000 + */ + private int version; + + private transient int hash; + + + public ProtocolModule(byte id, String name, String version) { + if (id < 0) { + throw new IllegalArgumentException(StringUtils.format("模块[{}]的id[{}]必须大于0", name, id)); + } + + this.id = id; + this.name = name; + this.version = versionStrToNum(version); + this.perfectHash(); + } + + public ProtocolModule(String name) { + this.name = name; + } + + public static void assertVersion(String version) { + if (!version.matches("[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}")) { + throw new IllegalArgumentException(StringUtils + .format("[version:{}] must like xxx.xxx.xxx", version)); + } + } + + public static int versionStrToNum(String version) { + assertVersion(version); + var splits = version.split("\\" + StringUtils.PERIOD); + var versionNum = Integer.parseInt(splits[0]) * 1_000_000 + Integer.parseInt(splits[1]) * 1_000 + Integer.parseInt(splits[2]); + + var newVersionStr = versionNumToStr(versionNum); + AssertionUtils.isTrue(version.equals(newVersionStr), "版本号转换前[{}]和转换后不相等[{}]", version, newVersionStr); + return versionNum; + } + + public static String versionNumToStr(int version) { + var versionStr = version / 1_000_000 + StringUtils.PERIOD + + version / 1_000 % 1_000 + StringUtils.PERIOD + + version % 1_000; + assertVersion(versionStr); + return versionStr; + } + + public void perfectHash() { + this.hash = id * 1_000_000 + this.version; + } + + public byte getId() { + return id; + } + + public void setId(byte id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProtocolModule module = (ProtocolModule) o; + return id == module.id && version == module.version; + } + + @Override + public int hashCode() { + return this.hash; + } + + @Override + public String toString() { + return StringUtils.format("[id:{}][name:{}][version:{}][hash:{}]", id, name, version, hash); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/ProtocolRegistration.java b/protocol/src/main/java/com/zfoo/protocol/registration/ProtocolRegistration.java new file mode 100644 index 00000000..36bc9544 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/ProtocolRegistration.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration; + +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.ISerializer; +import com.zfoo.protocol.util.ReflectionUtils; +import io.netty.buffer.ByteBuf; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; + +/** + * 协议必须为一个简单的POJO对象,必须有一个标识为private static final transient的PROTOCOL_ID号 + * 必须实现IPacket接口,返回的protocolId必须和PROTOCOL_ID号一致 + * + * @author jaysunxiao + * @version 3.0 + */ +public class ProtocolRegistration implements IProtocolRegistration { + + + private short id; + private byte module; + private Constructor constructor; + + private Field[] fields; + + + /** + * 所有的协议里的发送顺序都是按字段名称排序 + */ + private IFieldRegistration[] fieldRegistrations; + + public ProtocolRegistration() { + + } + + @Override + public short protocolId() { + return id; + } + + @Override + public byte module() { + return module; + } + + @Override + public Constructor protocolConstructor() { + return constructor; + } + + + @Override + public Object read(ByteBuf buffer) { + if (!ByteBufUtils.readBoolean(buffer)) { + return null; + } + Object object = ReflectionUtils.newInstance(constructor); + + for (int i = 0, length = fields.length; i < length; i++) { + Field field = fields[i]; + IFieldRegistration packetFieldRegistration = fieldRegistrations[i]; + ISerializer serializer = packetFieldRegistration.serializer(); + Object fieldValue = serializer.readObject(buffer, packetFieldRegistration); + ReflectionUtils.setField(field, object, fieldValue); + } + return object; + } + + @Override + public void write(ByteBuf buffer, IPacket packet) { + if (packet == null) { + ByteBufUtils.writeBoolean(buffer, false); + return; + } + + ByteBufUtils.writeBoolean(buffer, true); + + for (int i = 0, length = fields.length; i < length; i++) { + Field field = fields[i]; + IFieldRegistration packetFieldRegistration = fieldRegistrations[i]; + ISerializer serializer = packetFieldRegistration.serializer(); + Object fieldValue = ReflectionUtils.getField(field, packet); + serializer.writeObject(buffer, fieldValue, packetFieldRegistration); + } + } + + + public short getId() { + return id; + } + + public void setId(short id) { + this.id = id; + } + + public byte getModule() { + return module; + } + + public void setModule(byte module) { + this.module = module; + } + + public Field[] getFields() { + return fields; + } + + public void setFields(Field[] fields) { + this.fields = fields; + } + + public IFieldRegistration[] getFieldRegistrations() { + return fieldRegistrations; + } + + public void setFieldRegistrations(IFieldRegistration[] fieldRegistrations) { + this.fieldRegistrations = fieldRegistrations; + } + + public Constructor getConstructor() { + return constructor; + } + + public void setConstructor(Constructor constructor) { + this.constructor = constructor; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/ArrayField.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/ArrayField.java new file mode 100644 index 00000000..b38d8445 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/ArrayField.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ArraySerializer; +import com.zfoo.protocol.serializer.ISerializer; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ArrayField implements IFieldRegistration { + + private IFieldRegistration arrayElementRegistration; + private Field field; + + public static ArrayField valueOf(Field field, IFieldRegistration arrayElementRegistration) { + ArrayField arrayField = new ArrayField(); + arrayField.field = field; + arrayField.arrayElementRegistration = arrayElementRegistration; + return arrayField; + } + + + public Field getField() { + return field; + } + + + @Override + public ISerializer serializer() { + return ArraySerializer.getInstance(); + } + + public IFieldRegistration getArrayElementRegistration() { + return arrayElementRegistration; + } +} + diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/BaseField.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/BaseField.java new file mode 100644 index 00000000..f05ff336 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/BaseField.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ISerializer; + +/** + * 一个包里所包含的变量还有这个变量的序列化器 + * 描述boolean,byte,short,int,long,float,double,char,String等基本序列化器 + * + * @author jaysunxiao + * @version 3.0 + */ +public class BaseField implements IFieldRegistration { + + private ISerializer serializer; + + public static BaseField valueOf(ISerializer serializer) { + BaseField packetField = new BaseField(); + packetField.serializer = serializer; + return packetField; + } + + @Override + public ISerializer serializer() { + return serializer; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/IFieldRegistration.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/IFieldRegistration.java new file mode 100644 index 00000000..e8ab4326 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/IFieldRegistration.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ISerializer; + +/** + * 标记性接口,所有协议里描述变量都要实现这个接口 + * + * @author jaysunxiao + * @version 3.0 + */ +public interface IFieldRegistration { + + ISerializer serializer(); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/ListField.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/ListField.java new file mode 100644 index 00000000..fe40245c --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/ListField.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ISerializer; +import com.zfoo.protocol.serializer.ListSerializer; + +import java.lang.reflect.Type; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ListField implements IFieldRegistration { + + private IFieldRegistration listElementRegistration; + private Type type; + + public static ListField valueOf(IFieldRegistration listElementRegistration, Type type) { + ListField listField = new ListField(); + listField.listElementRegistration = listElementRegistration; + listField.type = type; + return listField; + } + + @Override + public ISerializer serializer() { + return ListSerializer.getInstance(); + } + + public IFieldRegistration getListElementRegistration() { + return listElementRegistration; + } + + public Type getType() { + return this.type; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/MapField.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/MapField.java new file mode 100644 index 00000000..d3d7d1c1 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/MapField.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ISerializer; +import com.zfoo.protocol.serializer.MapSerializer; + +import java.lang.reflect.Type; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MapField implements IFieldRegistration { + + private IFieldRegistration mapKeyRegistration; + private IFieldRegistration mapValueRegistration; + + private Type type; + + public static MapField valueOf(IFieldRegistration mapKeyRegistration, IFieldRegistration mapValueRegistration, Type type) { + MapField mapField = new MapField(); + mapField.mapKeyRegistration = mapKeyRegistration; + mapField.mapValueRegistration = mapValueRegistration; + mapField.type = type; + return mapField; + } + + + @Override + public ISerializer serializer() { + return MapSerializer.getInstance(); + } + + public IFieldRegistration getMapKeyRegistration() { + return mapKeyRegistration; + } + + public IFieldRegistration getMapValueRegistration() { + return mapValueRegistration; + } + + public Type getType() { + return type; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/ObjectProtocolField.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/ObjectProtocolField.java new file mode 100644 index 00000000..cc8042c3 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/ObjectProtocolField.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ISerializer; +import com.zfoo.protocol.serializer.ObjectProtocolSerializer; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ObjectProtocolField implements IFieldRegistration { + + /** + * 协议序列号是ProtocolRegistration的id + */ + private short protocolId; + + public static ObjectProtocolField valueOf(short protocolId) { + ObjectProtocolField objectProtocolField = new ObjectProtocolField(); + objectProtocolField.protocolId = protocolId; + return objectProtocolField; + } + + public short getProtocolId() { + return protocolId; + } + + @Override + public ISerializer serializer() { + return ObjectProtocolSerializer.getInstance(); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/registration/field/SetField.java b/protocol/src/main/java/com/zfoo/protocol/registration/field/SetField.java new file mode 100644 index 00000000..a6c6da15 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/registration/field/SetField.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.registration.field; + +import com.zfoo.protocol.serializer.ISerializer; +import com.zfoo.protocol.serializer.SetSerializer; + +import java.lang.reflect.Type; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SetField implements IFieldRegistration { + + private IFieldRegistration setElementRegistration; + private Type type; + + public static SetField valueOf(IFieldRegistration listElementRegistration, Type type) { + SetField setField = new SetField(); + setField.setElementRegistration = listElementRegistration; + setField.type = type; + return setField; + } + + @Override + public ISerializer serializer() { + return SetSerializer.getInstance(); + } + + public IFieldRegistration getSetElementRegistration() { + return setElementRegistration; + } + + public Type getType() { + return type; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/ArraySerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/ArraySerializer.java new file mode 100644 index 00000000..b4f96cd8 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/ArraySerializer.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.ArrayField; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +import java.lang.reflect.Array; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ArraySerializer implements ISerializer { + + + private static final ArraySerializer SERIALIZER = new ArraySerializer(); + + private ArraySerializer() { + + } + + public static ArraySerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + + ArrayField arrayField = (ArrayField) fieldRegistration; + + int length = Array.getLength(object); + if (length == 0) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + + ByteBufUtils.writeInt(buffer, length); + + for (int i = 0; i < length; i++) { + Object element = Array.get(object, i); + arrayField.getArrayElementRegistration().serializer().writeObject(buffer, element, arrayField.getArrayElementRegistration()); + } + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + var length = ByteBufUtils.readInt(buffer); + ArrayField arrayField = (ArrayField) fieldRegistration; + if (length <= 0) { + return Array.newInstance(arrayField.getField().getType().getComponentType(), 0); + } + + Object array = Array.newInstance(arrayField.getField().getType().getComponentType(), length); + + for (var i = 0; i < length; i++) { + Object value = arrayField.getArrayElementRegistration().serializer().readObject(buffer, arrayField.getArrayElementRegistration()); + Array.set(array, i, value); + } + + return array; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/BooleanSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/BooleanSerializer.java new file mode 100644 index 00000000..a931c1d0 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/BooleanSerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class BooleanSerializer implements ISerializer { + + private static final BooleanSerializer SERIALIZER = new BooleanSerializer(); + + private BooleanSerializer() { + + } + + public static BooleanSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeBoolean(buffer, Boolean.FALSE); + return; + } + ByteBufUtils.writeBoolean(buffer, (Boolean) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readBoolean(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/ByteSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/ByteSerializer.java new file mode 100644 index 00000000..03d09df5 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/ByteSerializer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ByteSerializer implements ISerializer { + + + private static final ByteSerializer SERIALIZER = new ByteSerializer(); + + private ByteSerializer() { + + } + + public static ByteSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeByte(buffer, (byte) 0); + return; + } + ByteBufUtils.writeByte(buffer, (Byte) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readByte(buffer); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/CharSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/CharSerializer.java new file mode 100644 index 00000000..9f7862ab --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/CharSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CharSerializer implements ISerializer { + + + private static final CharSerializer SERIALIZER = new CharSerializer(); + + private CharSerializer() { + + } + + public static CharSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeChar(buffer, Character.MIN_VALUE); + return; + } + ByteBufUtils.writeChar(buffer, (Character) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readChar(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/DoubleSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/DoubleSerializer.java new file mode 100644 index 00000000..39495be3 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/DoubleSerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class DoubleSerializer implements ISerializer { + + private static final DoubleSerializer SERIALIZER = new DoubleSerializer(); + + private DoubleSerializer() { + + } + + public static DoubleSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeDouble(buffer, 0); + return; + } + ByteBufUtils.writeDouble(buffer, (Double) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readDouble(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/FloatSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/FloatSerializer.java new file mode 100644 index 00000000..43b1def1 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/FloatSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class FloatSerializer implements ISerializer { + + + private static final FloatSerializer SERIALIZER = new FloatSerializer(); + + private FloatSerializer() { + + } + + public static FloatSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeFloat(buffer, 0F); + return; + } + ByteBufUtils.writeFloat(buffer, (Float) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readFloat(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/GenerateUtils.java b/protocol/src/main/java/com/zfoo/protocol/serializer/GenerateUtils.java new file mode 100644 index 00000000..cdda791e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/GenerateUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.zfoo.protocol.util.StringUtils.TAB; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateUtils { + + public static AtomicInteger index = new AtomicInteger(); + + public static StringBuilder addTab(StringBuilder builder, int deep) { + builder.append(TAB.repeat(Math.max(0, deep))); + return builder; + } + + public static void clear() { + index = null; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/ISerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/ISerializer.java new file mode 100644 index 00000000..d936f0e0 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/ISerializer.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ISerializer { + + void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration); + + Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/IntSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/IntSerializer.java new file mode 100644 index 00000000..2de9a95a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/IntSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class IntSerializer implements ISerializer { + + + private static final IntSerializer SERIALIZER = new IntSerializer(); + + private IntSerializer() { + + } + + public static IntSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + ByteBufUtils.writeInt(buffer, (Integer) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readInt(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/ListSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/ListSerializer.java new file mode 100644 index 00000000..88376f16 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/ListSerializer.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ListField; +import io.netty.buffer.ByteBuf; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ListSerializer implements ISerializer { + + private static final ListSerializer SERIALIZER = new ListSerializer(); + + + private ListSerializer() { + + } + + public static ListSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + + List list = (List) object; + ListField listField = (ListField) fieldRegistration; + + int size = list.size(); + if (size == 0) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + ByteBufUtils.writeInt(buffer, size); + + for (Object element : list) { + listField.getListElementRegistration().serializer().writeObject(buffer, element, listField.getListElementRegistration()); + } + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + int size = ByteBufUtils.readInt(buffer); + if (size <= 0) { + return Collections.EMPTY_LIST; + } + ListField listField = (ListField) fieldRegistration; + List list = new ArrayList<>(size); + + for (int i = 0; i < size; i++) { + Object value = listField.getListElementRegistration().serializer().readObject(buffer, listField.getListElementRegistration()); + list.add(value); + } + + return list; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/LongSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/LongSerializer.java new file mode 100644 index 00000000..101ca71b --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/LongSerializer.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LongSerializer implements ISerializer { + + private static final LongSerializer SERIALIZER = new LongSerializer(); + + private LongSerializer() { + + } + + public static LongSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeLong(buffer, 0L); + return; + } + ByteBufUtils.writeLong(buffer, (Long) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readLong(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/MapSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/MapSerializer.java new file mode 100644 index 00000000..7f7f594e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/MapSerializer.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.MapField; +import io.netty.buffer.ByteBuf; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MapSerializer implements ISerializer { + + + private static final MapSerializer SERIALIZER = new MapSerializer(); + + + private MapSerializer() { + + } + + public static MapSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + + Map map = (Map) object; + MapField mapField = (MapField) fieldRegistration; + + int size = map.size(); + if (size == 0) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + ByteBufUtils.writeInt(buffer, size); + + for (Map.Entry entry : map.entrySet()) { + mapField.getMapKeyRegistration().serializer().writeObject(buffer, entry.getKey(), mapField.getMapKeyRegistration()); + + mapField.getMapValueRegistration().serializer().writeObject(buffer, entry.getValue(), mapField.getMapValueRegistration()); + } + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + int size = ByteBufUtils.readInt(buffer); + if (size <= 0) { + return Collections.EMPTY_MAP; + } + + MapField mapField = (MapField) fieldRegistration; + Map map = new HashMap<>(CollectionUtils.comfortableCapacity(size)); + + for (int i = 0; i < size; i++) { + Object key = mapField.getMapKeyRegistration().serializer().readObject(buffer, mapField.getMapKeyRegistration()); + + Object value = mapField.getMapValueRegistration().serializer().readObject(buffer, mapField.getMapValueRegistration()); + + map.put(key, value); + } + return map; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/ObjectProtocolSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/ObjectProtocolSerializer.java new file mode 100644 index 00000000..ee5a9a45 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/ObjectProtocolSerializer.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.IPacket; +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import io.netty.buffer.ByteBuf; + +/** + * 只要是protocol都是使用FieldSerializer + * + * @author jaysunxiao + * @version 3.0 + */ +public class ObjectProtocolSerializer implements ISerializer { + + private static final ObjectProtocolSerializer SERIALIZER = new ObjectProtocolSerializer(); + + private ObjectProtocolSerializer() { + + } + + public static ObjectProtocolSerializer getInstance() { + return SERIALIZER; + } + + /** + * @param buffer ByteBuf + * @param object 必须继承IPacket接口 + */ + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + IProtocolRegistration protocol = ProtocolManager.getProtocol(objectProtocolField.getProtocolId()); + protocol.write(buffer, (IPacket) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + IProtocolRegistration protocol = ProtocolManager.getProtocol(objectProtocolField.getProtocolId()); + return protocol.read(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/SetSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/SetSerializer.java new file mode 100644 index 00000000..16aa0db6 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/SetSerializer.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.SetField; +import io.netty.buffer.ByteBuf; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SetSerializer implements ISerializer { + + private static final SetSerializer SERIALIZER = new SetSerializer(); + + + private SetSerializer() { + + } + + public static SetSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + + Set set = (Set) object; + SetField setField = (SetField) fieldRegistration; + + int size = set.size(); + if (size == 0) { + ByteBufUtils.writeInt(buffer, 0); + return; + } + ByteBufUtils.writeInt(buffer, size); + + for (Object element : set) { + setField.getSetElementRegistration().serializer().writeObject(buffer, element, setField.getSetElementRegistration()); + } + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + int size = ByteBufUtils.readInt(buffer); + if (size <= 0) { + return Collections.EMPTY_SET; + } + + SetField setField = (SetField) fieldRegistration; + Set set = new HashSet<>(CollectionUtils.comfortableCapacity(size)); + + for (int i = 0; i < size; i++) { + Object value = setField.getSetElementRegistration().serializer().readObject(buffer, setField.getSetElementRegistration()); + set.add(value); + } + + return set; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/ShortSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/ShortSerializer.java new file mode 100644 index 00000000..87dc13e4 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/ShortSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ShortSerializer implements ISerializer { + + + private static final ShortSerializer SERIALIZER = new ShortSerializer(); + + private ShortSerializer() { + + } + + public static ShortSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + if (object == null) { + ByteBufUtils.writeShort(buffer, (short) 0); + return; + } + ByteBufUtils.writeShort(buffer, (Short) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readShort(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/StringSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/StringSerializer.java new file mode 100644 index 00000000..64d7e0fb --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/StringSerializer.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import io.netty.buffer.ByteBuf; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StringSerializer implements ISerializer { + + + private static final StringSerializer SERIALIZER = new StringSerializer(); + + private StringSerializer() { + + } + + public static StringSerializer getInstance() { + return SERIALIZER; + } + + @Override + public void writeObject(ByteBuf buffer, Object object, IFieldRegistration fieldRegistration) { + ByteBufUtils.writeString(buffer, (String) object); + } + + @Override + public Object readObject(ByteBuf buffer, IFieldRegistration fieldRegistration) { + return ByteBufUtils.readString(buffer); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsArraySerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsArraySerializer.java new file mode 100644 index 00000000..e19facd0 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsArraySerializer.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.ArrayField; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsArraySerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ArrayField arrayField = (ArrayField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if (({} == null) || ({}.Length == 0))", objectStr, objectStr)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("buffer.WriteInt(0);").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("else").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("buffer.WriteInt({}.Length);", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + String length = "length" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {} = {}.Length;", length, objectStr)).append(LS); + + String i = "i" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (int {} = 0; {} < {}; {}++)", i, i, length, i)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 2); + String element = "element" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("{} {} = {}[{}];", GenerateCsUtils.toCsClassName(arrayField.getField().getType().getComponentType().getSimpleName()), element, objectStr, i)).append(LS); + + GenerateCsUtils.csSerializer(arrayField.getArrayElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, arrayField.getArrayElementRegistration()); + + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + var arrayField = (ArrayField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + + var typeName = GenerateCsUtils.toCsClassName(arrayField.getField().getType().getComponentType().getSimpleName()); + + var i = "index" + GenerateUtils.index.getAndIncrement(); + var size = "size" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("int {} = buffer.ReadInt();", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("{}[] {} = new {}[{}];", typeName, result, typeName, size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0)", size)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (int {} = 0; {} < {}; {}++)", i, i, size, i)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + var readObject = GenerateCsUtils.csSerializer(arrayField.getArrayElementRegistration().serializer()) + .readObject(builder, deep + 2, field, arrayField.getArrayElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}[{}] = {};", result, i, readObject)); + builder.append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsBooleanSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsBooleanSerializer.java new file mode 100644 index 00000000..5cb6b63a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsBooleanSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsBooleanSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteBool({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("bool {} = buffer.ReadBool();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsByteSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsByteSerializer.java new file mode 100644 index 00000000..c8fd573c --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsByteSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsByteSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteByte({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byte {} = buffer.ReadByte();", result)).append(LS); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsCharSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsCharSerializer.java new file mode 100644 index 00000000..ed57e843 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsCharSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsCharSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteChar({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("char {} = buffer.ReadChar();", result)).append(LS); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsDoubleSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsDoubleSerializer.java new file mode 100644 index 00000000..0fd31d48 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsDoubleSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsDoubleSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteDouble({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("double {} = buffer.ReadDouble();", result)).append(LS); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsFloatSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsFloatSerializer.java new file mode 100644 index 00000000..02b60e62 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsFloatSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsFloatSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteFloat({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("float {} = buffer.ReadFloat();", result)).append(LS); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsIntSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsIntSerializer.java new file mode 100644 index 00000000..93c3ec3f --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsIntSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsIntSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteInt({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("int {} = buffer.ReadInt();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsListSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsListSerializer.java new file mode 100644 index 00000000..361cf2b7 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsListSerializer.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ListField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsListSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ListField listField = (ListField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} == null)", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("buffer.WriteInt(0);").append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("else").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("buffer.WriteInt({}.Count);", objectStr)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + String length = "length" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {} = {}.Count;", length, objectStr)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + String i = "i" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for (int {} = 0; {} < {}; {}++)", i, i, length, i)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 2); + String element = "element" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("var {} = {}[{}];", element, objectStr, i)).append(LS); + + GenerateCsUtils.csSerializer(listField.getListElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, listField.getListElementRegistration()); + + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + var listField = (ListField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + + var typeName = GenerateCsUtils.toCsClassName(listField.getType().toString()); + + var i = "index" + GenerateUtils.index.getAndIncrement(); + var size = "size" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + + builder.append(StringUtils.format("int {} = buffer.ReadInt();", size)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("var {} = new {}({});", result, typeName, size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0)", size)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (int {} = 0; {} < {}; {}++)", i, i, size, i)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + var readObject = GenerateCsUtils.csSerializer(listField.getListElementRegistration().serializer()) + .readObject(builder, deep + 2, field, listField.getListElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}.Add({});", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsLongSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsLongSerializer.java new file mode 100644 index 00000000..4099aaad --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsLongSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsLongSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteLong({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("long {} = buffer.ReadLong();", result)).append(LS); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsMapSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsMapSerializer.java new file mode 100644 index 00000000..48dc329d --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsMapSerializer.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.MapField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsMapSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + MapField mapField = (MapField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if (({} == null) || ({}.Count == 0))", objectStr, objectStr)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append("buffer.WriteInt(0);").append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("else").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("buffer.WriteInt({}.Count);", objectStr)).append(LS); + + + String i = "i" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("foreach (var {} in {})", i, objectStr)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 2); + String key = "keyElement" + GenerateUtils.index.getAndIncrement(); + String value = "valueElement" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("var {} = {}.Key;", key, i)).append(LS); + + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("var {} = {}.Value;", value, i)).append(LS); + + GenerateCsUtils.csSerializer(mapField.getMapKeyRegistration().serializer()) + .writeObject(builder, key, deep + 2, field, mapField.getMapKeyRegistration()); + GenerateCsUtils.csSerializer(mapField.getMapValueRegistration().serializer()) + .writeObject(builder, value, deep + 2, field, mapField.getMapValueRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + MapField mapField = (MapField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + var typeName = GenerateCsUtils.toCsClassName(mapField.getType().toString()); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {} = buffer.ReadInt();", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("var {} = new {}({});", result, typeName, size)).append(LS); + + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0)", size)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + String i = "index" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (var {} = 0; {} < {}; {}++)", i, i, size, i)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + + String keyObject = GenerateCsUtils.csSerializer(mapField.getMapKeyRegistration().serializer()) + .readObject(builder, deep + 2, field, mapField.getMapKeyRegistration()); + + + String valueObject = GenerateCsUtils.csSerializer(mapField.getMapValueRegistration().serializer()) + .readObject(builder, deep + 2, field, mapField.getMapValueRegistration()); + GenerateUtils.addTab(builder, deep + 2); + + builder.append(StringUtils.format("{}[{}] = {};", result, keyObject, valueObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsObjectProtocolSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsObjectProtocolSerializer.java new file mode 100644 index 00000000..908b5f2f --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsObjectProtocolSerializer.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsObjectProtocolSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("ProtocolManager.GetProtocol({}).Write(buffer, {});", objectProtocolField.getProtocolId(), objectStr)) + .append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("{} {} = ({}) ProtocolManager.GetProtocol({}).Read(buffer);", getProtocolSimpleName(objectProtocolField), result, getProtocolSimpleName(objectProtocolField), objectProtocolField.getProtocolId())) + .append(LS); + return result; + } + + private String getProtocolSimpleName(ObjectProtocolField objectProtocolField) { + return ProtocolManager.getProtocol(objectProtocolField.getProtocolId()).protocolConstructor().getDeclaringClass().getSimpleName(); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsSetSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsSetSerializer.java new file mode 100644 index 00000000..02f12929 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsSetSerializer.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.SetField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsSetSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + SetField setField = (SetField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} == null)", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("buffer.WriteInt(0);").append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("else").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("buffer.WriteInt({}.Count);", objectStr)).append(LS); + + String element = "i" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("foreach (var {} in {})", element, objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + + GenerateCsUtils.csSerializer(setField.getSetElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, setField.getSetElementRegistration()); + + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + SetField setField = (SetField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + + var typeName = GenerateCsUtils.toCsClassName(setField.getType().toString()); + + var i = "index" + GenerateUtils.index.getAndIncrement(); + var size = "size" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("int {} = buffer.ReadInt();", size)).append(LS); + GenerateUtils.addTab(builder, deep); + // unity里不支持HashSet的初始化大小 +// builder.append("var " + result + " = new " + typeName + "(" + size + ");" + LS); + builder.append(StringUtils.format("var {} = new {}();", result, typeName)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0)", size)).append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("{").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (int {} = 0; {} < {}; {}++)", i, i, size, i)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("{").append(LS); + + var readObject = GenerateCsUtils.csSerializer(setField.getSetElementRegistration().serializer()) + .readObject(builder, deep + 2, field, setField.getSetElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}.Add({});", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsShortSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsShortSerializer.java new file mode 100644 index 00000000..9e2e33d4 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsShortSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsShortSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteShort({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("short {} = buffer.ReadShort();", result)).append(LS); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsStringSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsStringSerializer.java new file mode 100644 index 00000000..ef749c67 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/CsStringSerializer.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CsStringSerializer implements ICsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("buffer.WriteString({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("string {} = buffer.ReadString();", result)).append(LS); + return result; + } + + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/GenerateCsUtils.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/GenerateCsUtils.java new file mode 100644 index 00000000..13164bd4 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/GenerateCsUtils.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.generate.GenerateProtocolDocument; +import com.zfoo.protocol.generate.GenerateProtocolPath; +import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.registration.ProtocolRegistration; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.*; +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +import static com.zfoo.protocol.util.FileUtils.LS; +import static com.zfoo.protocol.util.StringUtils.TAB; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateCsUtils { + + private static final String PROTOCOL_OUTPUT_ROOT_PATH = "CsProtocol/"; + + private static Map csSerializerMap; + + public static ICsSerializer csSerializer(ISerializer serializer) { + return csSerializerMap.get(serializer); + } + + public static void init() { + FileUtils.deleteFile(new File(PROTOCOL_OUTPUT_ROOT_PATH)); + FileUtils.createDirectory(PROTOCOL_OUTPUT_ROOT_PATH); + + csSerializerMap = new HashMap<>(); + csSerializerMap.put(BooleanSerializer.getInstance(), new CsBooleanSerializer()); + csSerializerMap.put(ByteSerializer.getInstance(), new CsByteSerializer()); + csSerializerMap.put(ShortSerializer.getInstance(), new CsShortSerializer()); + csSerializerMap.put(IntSerializer.getInstance(), new CsIntSerializer()); + csSerializerMap.put(LongSerializer.getInstance(), new CsLongSerializer()); + csSerializerMap.put(FloatSerializer.getInstance(), new CsFloatSerializer()); + csSerializerMap.put(DoubleSerializer.getInstance(), new CsDoubleSerializer()); + csSerializerMap.put(CharSerializer.getInstance(), new CsCharSerializer()); + csSerializerMap.put(StringSerializer.getInstance(), new CsStringSerializer()); + csSerializerMap.put(ArraySerializer.getInstance(), new CsArraySerializer()); + csSerializerMap.put(ListSerializer.getInstance(), new CsListSerializer()); + csSerializerMap.put(SetSerializer.getInstance(), new CsSetSerializer()); + csSerializerMap.put(MapSerializer.getInstance(), new CsMapSerializer()); + csSerializerMap.put(ObjectProtocolSerializer.getInstance(), new CsObjectProtocolSerializer()); + } + + public static void clear() { + csSerializerMap = null; + } + + /** + * 生成协议依赖的工具类 + */ + public static void createProtocolManager() throws IOException { + var list = List.of("cs/ProtocolManager.cs" + , "cs/IProtocolRegistration.cs" + , "cs/IPacket.cs" + , "cs/Buffer/ByteBuffer.cs" + , "cs/Buffer/LittleEndianByteBuffer.cs" + , "cs/Buffer/BigEndianByteBuffer.cs"); + + for (var fileName : list) { + var fileInputStream = ClassUtils.getFileFromClassPath(fileName); + var createFile = new File(StringUtils.format("{}{}", PROTOCOL_OUTPUT_ROOT_PATH, StringUtils.substringAfterFirst(fileName, "cs/"))); + FileUtils.writeInputStreamToFile(createFile, fileInputStream); + } + } + + /** + * 生成协议类 + */ + public static void createCsProtocolFile(ProtocolRegistration registration) { + GenerateUtils.index.set(0); + + var protocolId = registration.protocolId(); + var registrationConstructor = registration.getConstructor(); + IFieldRegistration[] fieldRegistrations = registration.getFieldRegistrations(); + + var protocolClazzName = registrationConstructor.getDeclaringClass().getSimpleName(); + + var csBuilder = new StringBuilder(); + csBuilder.append("using System;").append(LS); + csBuilder.append("using System.Collections.Generic;").append(LS); + csBuilder.append("using CsProtocol.Buffer;").append(LS).append(LS); + csBuilder.append("namespace CsProtocol").append(LS); + csBuilder.append("{").append(LS); + + + // protocol object + csBuilder.append(protocolClass(registration)); + + csBuilder.append(TAB).append(StringUtils.format("public class {}Registration : IProtocolRegistration", protocolClazzName)).append(LS); + csBuilder.append(TAB).append("{").append(LS); + + // ProtocolId method + csBuilder.append(packetProtocolId(registration)); + + // writeObject method + csBuilder.append(writeObject(registration)); + + // readObject method + csBuilder.append(readObject(registration)); + + csBuilder.append(TAB).append("}").append(LS); + csBuilder.append("}").append(LS); + + var protocolOutputPath = StringUtils.format("{}{}/{}.cs" + , PROTOCOL_OUTPUT_ROOT_PATH + , GenerateProtocolPath.getCapitalizeProtocolPath(protocolId) + , protocolClazzName); + FileUtils.writeStringToFile(new File(protocolOutputPath), csBuilder.toString()); + } + + + public static String toCsClassName(String typeName) { + typeName = typeName.replaceAll("java.util.|java.lang.", StringUtils.EMPTY); + typeName = typeName.replaceAll("com\\.[a-zA-Z0-9_.]*\\.", StringUtils.EMPTY); + + // CSharp不适用基础类型的泛型,会影响性能 + switch (typeName) { + case "boolean": + case "Boolean": + typeName = "bool"; + return typeName; + case "Byte": + typeName = "byte"; + return typeName; + case "Short": + typeName = "short"; + return typeName; + case "Integer": + typeName = "int"; + return typeName; + case "Long": + typeName = "long"; + return typeName; + case "Float": + typeName = "float"; + return typeName; + case "Double": + typeName = "double"; + return typeName; + case "Character": + typeName = "char"; + return typeName; + case "String": + typeName = "string"; + return typeName; + default: + } + + // 将boolean转为bool + typeName = typeName.replace(" boolean ", " bool "); + typeName = typeName.replace(" Boolean ", " bool "); + typeName = typeName.replaceAll("[B|b]oolean\\[", "bool["); + typeName = typeName.replace("", "bool>"); + + // 将Byte转为byte + typeName = typeName.replace(" Byte ", " byte "); + typeName = typeName.replace("Byte[", "byte["); + typeName = typeName.replace("Byte>", "byte>"); + typeName = typeName.replace("", "short>"); + typeName = typeName.replace("", "int>"); + typeName = typeName.replace("", "long>"); + typeName = typeName.replace("", "float>"); + typeName = typeName.replace("", "double>"); + typeName = typeName.replace("", "char>"); + typeName = typeName.replace("", "string>"); + typeName = typeName.replace(" csBuilder.append(TAB).append(it).append(LS)); + } + csBuilder.append(TAB) + .append(StringUtils.format("public class {} : IPacket", protocolClazzName)) + .append(LS); + csBuilder.append(TAB).append("{").append(LS); + + // 协议的属性生成 + var filedList = new ArrayList>(); + for (var field : fields) { + var propertyType = toCsClassName(field.getGenericType().getTypeName()); + var propertyName = field.getName(); + + var propertyFullName = StringUtils.format("public {} {};", propertyType, propertyName); + // 生成注释 + var doc = docFieldMap.get(propertyName); + if (!StringUtils.isBlank(doc)) { + Arrays.stream(doc.split(LS)).forEach(it -> csBuilder.append(TAB + TAB).append(it).append(LS)); + } + + csBuilder.append(TAB + TAB).append(propertyFullName).append(LS); + filedList.add(new Pair<>(propertyType, propertyName)); + } + + csBuilder.append(LS); + + // ValueOf()方法 + var valueOfParams = filedList.stream() + .map(it -> StringUtils.format("{} {}", it.getKey(), it.getValue())) + .collect(Collectors.toList()); + + csBuilder.append(TAB + TAB) + .append(StringUtils.format("public static {} ValueOf({})", protocolClazzName, StringUtils.joinWith(StringUtils.COMMA + " ", valueOfParams.toArray()))) + .append(LS); + + csBuilder.append(TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB) + .append(StringUtils.format("var packet = new {}();", protocolClazzName)) + .append(LS); + filedList.forEach(it -> csBuilder.append(TAB + TAB + TAB).append(StringUtils.format("packet.{} = {};", it.getValue(), it.getValue())).append(LS)); + csBuilder.append(TAB + TAB + TAB).append("return packet;").append(LS); + csBuilder.append(TAB + TAB).append("}").append(LS); + csBuilder.append(LS).append(LS); + + // ProtocolId()方法 + csBuilder.append(TAB + TAB).append("public short ProtocolId()").append(LS); + csBuilder.append(TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB).append(StringUtils.format("return {};", registration.protocolId())).append(LS); + csBuilder.append(TAB + TAB).append("}").append(LS); + csBuilder.append(TAB).append("}").append(LS); + csBuilder.append(LS).append(LS); + + return csBuilder.toString(); + } + + private static String packetProtocolId(ProtocolRegistration registration) { + var csBuilder = new StringBuilder(); + csBuilder.append(TAB + TAB).append("public short ProtocolId()").append(LS); + csBuilder.append(TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB).append(StringUtils.format("return {};", registration.protocolId())).append(LS); + csBuilder.append(TAB + TAB).append("}"); + csBuilder.append(LS).append(LS); + return csBuilder.toString(); + } + + private static String writeObject(ProtocolRegistration registration) { + Field[] fields = registration.getFields(); + IFieldRegistration[] fieldRegistrations = registration.getFieldRegistrations(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var csBuilder = new StringBuilder(); + csBuilder.append(TAB + TAB).append("public void Write(ByteBuffer buffer, IPacket packet)").append(LS); + csBuilder.append(TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB).append("if (packet == null)").append(LS); + csBuilder.append(TAB + TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB + TAB).append("buffer.WriteBool(false);").append(LS); + csBuilder.append(TAB + TAB + TAB + TAB).append("return;").append(LS); + csBuilder.append(TAB + TAB + TAB + "}").append(LS); + csBuilder.append(TAB + TAB + TAB).append("buffer.WriteBool(true);").append(LS); + + csBuilder.append(TAB + TAB + TAB) + .append(StringUtils.format("{} message = ({}) packet;", protocolClazzName, protocolClazzName)) + .append(LS); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + IFieldRegistration fieldRegistration = fieldRegistrations[i]; + + csSerializer(fieldRegistration.serializer()).writeObject(csBuilder, "message." + field.getName(), 3, field, fieldRegistration); + } + + csBuilder.append(TAB + TAB + "}").append(LS).append(LS); + return csBuilder.toString(); + } + + + private static String readObject(ProtocolRegistration registration) { + Field[] fields = registration.getFields(); + IFieldRegistration[] fieldRegistrations = registration.getFieldRegistrations(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var csBuilder = new StringBuilder(); + csBuilder.append(TAB + TAB).append("public IPacket Read(ByteBuffer buffer)").append(LS); + csBuilder.append(TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB).append("if (!buffer.ReadBool())").append(LS); + csBuilder.append(TAB + TAB + TAB).append("{").append(LS); + csBuilder.append(TAB + TAB + TAB + TAB).append("return null;").append(LS); + csBuilder.append(TAB + TAB + TAB).append("}").append(LS); + + csBuilder.append(TAB + TAB + TAB) + .append(StringUtils.format("{} packet = new {}();", protocolClazzName, protocolClazzName)) + .append(LS); + + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + IFieldRegistration fieldRegistration = fieldRegistrations[i]; + + String readObject = csSerializer(fieldRegistration.serializer()).readObject(csBuilder, 3, field, fieldRegistration); + csBuilder.append(TAB + TAB + TAB) + .append(StringUtils.format("packet.{} = {};", field.getName(), readObject)) + .append(LS); + } + + csBuilder.append(TAB + TAB + TAB).append("return packet;").append(LS); + + csBuilder.append(TAB + TAB).append("}").append(LS); + + return csBuilder.toString(); + } + + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/cs/ICsSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/ICsSerializer.java new file mode 100644 index 00000000..d345895e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/cs/ICsSerializer.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.cs; + +import com.zfoo.protocol.registration.field.IFieldRegistration; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ICsSerializer { + + void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration); + + String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceArraySerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceArraySerializer.java new file mode 100644 index 00000000..da0bbe45 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceArraySerializer.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.ArrayField; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceArraySerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + var arrayField = (ArrayField) fieldRegistration; + var arrayName = getArrayClassName(arrayField); + + switch (arrayName) { + case "boolean": + builder.append(StringUtils.format("{}.writeBooleanArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Boolean": + builder.append(StringUtils.format("{}.writeBooleanBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "byte": + builder.append(StringUtils.format("{}.writeByteArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Byte": + builder.append(StringUtils.format("{}.writeByteBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "short": + builder.append(StringUtils.format("{}.writeShortArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Short": + builder.append(StringUtils.format("{}.writeShortBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "int": + builder.append(StringUtils.format("{}.writeIntArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Integer": + builder.append(StringUtils.format("{}.writeIntBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "long": + builder.append(StringUtils.format("{}.writeLongArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Long": + builder.append(StringUtils.format("{}.writeLongBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "float": + builder.append(StringUtils.format("{}.writeFloatArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Float": + builder.append(StringUtils.format("{}.writeFloatBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "double": + builder.append(StringUtils.format("{}.writeDoubleArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Double": + builder.append(StringUtils.format("{}.writeDoubleBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "String": + builder.append(StringUtils.format("{}.writeStringArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "char": + builder.append(StringUtils.format("{}.writeCharArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "Character": + builder.append(StringUtils.format("{}.writeCharBoxArray($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + return; + default: + } + + var array = "array" + GenerateUtils.index.getAndIncrement(); + var length = "length" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("{}[] {} = {};", arrayName, array, objectStr)); + builder.append(StringUtils.format("int {} = ArrayUtils.length({});", length, array)); + builder.append(StringUtils.format("{}.writeInt($1,{});", EnhanceUtils.byteBufUtils, length)); + + var i = "i" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for(int {}=0; {}<{}; {}++){", i, i, length, i)); + + var element = "element" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("{} {} = {}[{}];", arrayName, element, array, i)); + + EnhanceUtils.enhanceSerializer(arrayField.getArrayElementRegistration().serializer()) + .writeObject(builder, element, arrayField.getField(), arrayField.getArrayElementRegistration()); + + builder.append("}"); + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var arrayField = (ArrayField) fieldRegistration; + var arrayName = getArrayClassName(arrayField); + + var array = "array" + GenerateUtils.index.getAndIncrement(); + + switch (arrayName) { + case "boolean": + builder.append(StringUtils.format("{}[] {} = {}.readBooleanArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Boolean": + builder.append(StringUtils.format("{}[] {} = {}.readBooleanBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "byte": + builder.append(StringUtils.format("{}[] {} = {}.readByteArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Byte": + builder.append(StringUtils.format("{}[] {} = {}.readByteBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "short": + builder.append(StringUtils.format("{}[] {} = {}.readShortArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Short": + builder.append(StringUtils.format("{}[] {} = {}.readShortBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "int": + builder.append(StringUtils.format("{}[] {} = {}.readIntArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Integer": + builder.append(StringUtils.format("{}[] {} = {}.readIntBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "long": + builder.append(StringUtils.format("{}[] {} = {}.readLongArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Long": + builder.append(StringUtils.format("{}[] {} = {}.readLongBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "float": + builder.append(StringUtils.format("{}[] {} = {}.readFloatArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Float": + builder.append(StringUtils.format("{}[] {} = {}.readFloatBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "double": + builder.append(StringUtils.format("{}[] {} = {}.readDoubleArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Double": + builder.append(StringUtils.format("{}[] {} = {}.readDoubleBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "String": + builder.append(StringUtils.format("{}[] {} = {}.readStringArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "char": + builder.append(StringUtils.format("{}[] {} = {}.readCharArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + case "Character": + builder.append(StringUtils.format("{}[] {} = {}.readCharBoxArray($1);", arrayName, array, EnhanceUtils.byteBufUtils)); + return array; + default: + } + + var length = "length" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {} = {}.readInt($1);", length, EnhanceUtils.byteBufUtils)); + + builder.append(StringUtils.format("{}[] {} = new {}[{}];", arrayName, array, arrayName, length)); + + var i = "i" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for(int {}=0; {} < {}; {}++){", i, i, length, i)); + var readObject = EnhanceUtils.enhanceSerializer(arrayField.getArrayElementRegistration().serializer()) + .readObject(builder, arrayField.getField(), arrayField.getArrayElementRegistration()); + builder.append(StringUtils.format("{}[{}] = {};}", array, i, readObject)); + return array; + } + + + private String getArrayClassName(ArrayField arrayField) { + // 去掉包装类型的前缀java.lang + return arrayField.getField().getType().getComponentType().getCanonicalName().replaceFirst("java.lang.", StringUtils.EMPTY); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceBooleanSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceBooleanSerializer.java new file mode 100644 index 00000000..5bad0d95 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceBooleanSerializer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceBooleanSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeBoolean($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeBooleanBox($1, (Boolean){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("boolean {} = {}.readBoolean($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Boolean {} = {}.readBooleanBox($1);", result, EnhanceUtils.byteBufUtils)); + } + + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceByteSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceByteSerializer.java new file mode 100644 index 00000000..7f04e348 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceByteSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceByteSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeByte($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeByteBox($1, (Byte){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("byte {} = {}.readByte($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Byte {} = {}.readByteBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceCharSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceCharSerializer.java new file mode 100644 index 00000000..6c63bd11 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceCharSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceCharSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeChar($1,{});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeCharBox($1, (Character){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("char {} = {}.readChar($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Character {} = {}.readCharBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceDoubleSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceDoubleSerializer.java new file mode 100644 index 00000000..d994c0d7 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceDoubleSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceDoubleSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeDouble($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeDoubleBox($1, (Double){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("double {} = {}.readDouble($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Double {} = {}.readDoubleBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceFloatSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceFloatSerializer.java new file mode 100644 index 00000000..15e7c89c --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceFloatSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceFloatSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeFloat($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeFloatBox($1, (Float){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("float {} = {}.readFloat($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Float {} = {}.readFloatBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceIntSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceIntSerializer.java new file mode 100644 index 00000000..3b88e709 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceIntSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceIntSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeInt($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeIntBox($1, (Integer){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("int {} = {}.readInt($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Integer {} = {}.readIntBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceListSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceListSerializer.java new file mode 100644 index 00000000..644bec61 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceListSerializer.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ListField; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceListSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + var listField = (ListField) fieldRegistration; + + switch (listField.getType().getTypeName()) { + case "java.util.List": + builder.append(StringUtils.format("{}.writeIntList($1, (List){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "java.util.List": + builder.append(StringUtils.format("{}.writeLongList($1, (List){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "java.util.List": + builder.append(StringUtils.format("{}.writeStringList($1, (List){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + default: + } + + // List + if (listField.getListElementRegistration() instanceof ObjectProtocolField) { + var objectProtocolField = (ObjectProtocolField) listField.getListElementRegistration(); + builder.append(StringUtils.format("{}.writePacketList($1, (List){}, {});", EnhanceUtils.byteBufUtils, objectStr, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(objectProtocolField.getProtocolId()))); + return; + } + + var list = "list" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("List {} = (List){};", list, objectStr)); + + builder.append(StringUtils.format("{}.writeInt($1, CollectionUtils.size({}));", EnhanceUtils.byteBufUtils, list)); + + var iterator = "iterator" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Iterator {} = CollectionUtils.iterator({});", iterator, list)); + builder.append(StringUtils.format("while({}.hasNext()){", iterator)); + + var element = "element" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Object {}={}.next();", element, iterator)); + EnhanceUtils.enhanceSerializer(listField.getListElementRegistration().serializer()) + .writeObject(builder, element, field, listField.getListElementRegistration()); + builder.append("}"); + + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var listField = (ListField) fieldRegistration; + var list = "list" + GenerateUtils.index.getAndIncrement(); + + switch (listField.getType().getTypeName()) { + case "java.util.List": + builder.append(StringUtils.format("List {} = {}.readIntList($1);", list, EnhanceUtils.byteBufUtils)); + return list; + case "java.util.List": + builder.append(StringUtils.format("List {} = {}.readLongList($1);", list, EnhanceUtils.byteBufUtils)); + return list; + case "java.util.List": + builder.append(StringUtils.format("List {} = {}.readStringList($1);", list, EnhanceUtils.byteBufUtils)); + return list; + default: + } + + if (listField.getListElementRegistration() instanceof ObjectProtocolField) { + var objectProtocolField = (ObjectProtocolField) listField.getListElementRegistration(); + builder.append(StringUtils.format("List {} = {}.readPacketList($1, {});", list, EnhanceUtils.byteBufUtils, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(objectProtocolField.getProtocolId()))); + return list; + } + + var size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {}={}.readInt($1);", size, EnhanceUtils.byteBufUtils)); + + builder.append(StringUtils.format("List {} = CollectionUtils.newFixedList({});", list, size)); + + var i = "i" + GenerateUtils.index.getAndIncrement(); + + builder.append(StringUtils.format("for(int {}=0; {}<{}; {}++){", i, i, size, i)); + var readObject = EnhanceUtils.enhanceSerializer(listField.getListElementRegistration().serializer()).readObject(builder, field, listField.getListElementRegistration()); + builder.append(StringUtils.format("{}.add({});}", list, readObject)); + return list; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceLongSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceLongSerializer.java new file mode 100644 index 00000000..e222228a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceLongSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceLongSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeLong($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeLongBox($1, (Long){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("long {} = {}.readLong($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Long {} = {}.readLongBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceMapSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceMapSerializer.java new file mode 100644 index 00000000..5a8ef572 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceMapSerializer.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.BaseField; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.MapField; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.serializer.*; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceMapSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + var mapField = (MapField) fieldRegistration; + var keyRegistration = mapField.getMapKeyRegistration(); + var valueRegistration = mapField.getMapValueRegistration(); + + if (keyRegistration instanceof BaseField) { + if (valueRegistration instanceof BaseField) { + var keyBaseRegistration = (BaseField) keyRegistration; + var valueBaseRegistration = (BaseField) valueRegistration; + if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueBaseRegistration.serializer() == IntSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeIntIntMap($1, (Map){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + } else if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueBaseRegistration.serializer() == LongSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeIntLongMap($1, (Map){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + } else if (keyBaseRegistration.serializer() == LongSerializer.getInstance() && valueBaseRegistration.serializer() == IntSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeLongIntMap($1, (Map){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + } else if (keyBaseRegistration.serializer() == LongSerializer.getInstance() && valueBaseRegistration.serializer() == LongSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeLongLongMap($1, (Map){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + } else if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueBaseRegistration.serializer() == StringSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeIntStringMap($1, (Map){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + } + } else if (valueRegistration instanceof ObjectProtocolField) { + var keyBaseRegistration = (BaseField) keyRegistration; + var valueProtocolRegistration = (ObjectProtocolField) valueRegistration; + if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueProtocolRegistration.serializer() == ObjectProtocolSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeIntPacketMap($1, (Map){}, {});", EnhanceUtils.byteBufUtils, objectStr, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(valueProtocolRegistration.getProtocolId()))); + return; + } else if (keyBaseRegistration.serializer() == LongSerializer.getInstance() && valueProtocolRegistration.serializer() == ObjectProtocolSerializer.getInstance()) { + builder.append(StringUtils.format("{}.writeLongPacketMap($1, (Map){}, {});", EnhanceUtils.byteBufUtils, objectStr, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(valueProtocolRegistration.getProtocolId()))); + return; + } + } + } + + var map = "map" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Map {} = (Map){};", map, objectStr)); + builder.append(StringUtils.format("{}.writeInt($1, CollectionUtils.size({}));", EnhanceUtils.byteBufUtils, map)); + + var iterator = "iterator" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Iterator {} = CollectionUtils.iterator({});", iterator, map)); + builder.append(StringUtils.format("while({}.hasNext()) {", iterator)); + + var entry = "entry" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("{} {}=({}){}.next();", Map.Entry.class.getCanonicalName(), entry, Map.Entry.class.getCanonicalName(), iterator)); + + var key = "key" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Object {} = {}.getKey();", key, entry)); + + var value = "value" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Object {} = {}.getValue();", value, entry)); + + EnhanceUtils.enhanceSerializer(keyRegistration.serializer()).writeObject(builder, key, field, keyRegistration); + EnhanceUtils.enhanceSerializer(valueRegistration.serializer()).writeObject(builder, value, field, valueRegistration); + + builder.append("}"); + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var mapField = (MapField) fieldRegistration; + var keyRegistration = mapField.getMapKeyRegistration(); + var valueRegistration = mapField.getMapValueRegistration(); + + var map = "map" + GenerateUtils.index.getAndIncrement(); + + if (keyRegistration instanceof BaseField) { + if (valueRegistration instanceof BaseField) { + var keyBaseRegistration = (BaseField) keyRegistration; + var valueBaseRegistration = (BaseField) valueRegistration; + if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueBaseRegistration.serializer() == IntSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readIntIntMap($1);", map, EnhanceUtils.byteBufUtils)); + return map; + } else if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueBaseRegistration.serializer() == LongSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readIntLongMap($1);", map, EnhanceUtils.byteBufUtils)); + return map; + } else if (keyBaseRegistration.serializer() == LongSerializer.getInstance() && valueBaseRegistration.serializer() == IntSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readLongIntMap($1);", map, EnhanceUtils.byteBufUtils)); + return map; + } else if (keyBaseRegistration.serializer() == LongSerializer.getInstance() && valueBaseRegistration.serializer() == LongSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readLongLongMap($1);", map, EnhanceUtils.byteBufUtils)); + return map; + } else if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueBaseRegistration.serializer() == StringSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readIntStringMap($1);", map, EnhanceUtils.byteBufUtils)); + return map; + } + } else if (valueRegistration instanceof ObjectProtocolField) { + var keyBaseRegistration = (BaseField) keyRegistration; + var valueProtocolRegistration = (ObjectProtocolField) valueRegistration; + if (keyBaseRegistration.serializer() == IntSerializer.getInstance() && valueProtocolRegistration.serializer() == ObjectProtocolSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readIntPacketMap($1, {});", map, EnhanceUtils.byteBufUtils, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(valueProtocolRegistration.getProtocolId()))); + return map; + } else if (keyBaseRegistration.serializer() == LongSerializer.getInstance() && valueProtocolRegistration.serializer() == ObjectProtocolSerializer.getInstance()) { + builder.append(StringUtils.format("Map {} = {}.readLongPacketMap($1, {});", map, EnhanceUtils.byteBufUtils, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(valueProtocolRegistration.getProtocolId()))); + return map; + } + } + } + + var size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {}={}.readInt($1);", size, EnhanceUtils.byteBufUtils)); + builder.append(StringUtils.format("Map {} = CollectionUtils.newFixedMap({});", map, size)); + + var i = "i" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for(int {}=0; {}<{}; {}++){", i, i, size, i)); + + var keyObject = EnhanceUtils.enhanceSerializer(keyRegistration.serializer()).readObject(builder, field, keyRegistration); + var valueObject = EnhanceUtils.enhanceSerializer(valueRegistration.serializer()).readObject(builder, field, valueRegistration); + + builder.append(StringUtils.format("{}.put({},{});}", map, keyObject, valueObject)); + return map; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceObjectProtocolSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceObjectProtocolSerializer.java new file mode 100644 index 00000000..3014ac4c --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceObjectProtocolSerializer.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * 对应于ObjectProtocolSerializer + * + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceObjectProtocolSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + var objectProtocolField = (ObjectProtocolField) fieldRegistration; + builder.append(StringUtils.format("{}.write($1, (IPacket){});", EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(objectProtocolField.getProtocolId()), objectStr)); + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var objectProtocolField = (ObjectProtocolField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + var protocolName = getProtocolClassCanonicalName(objectProtocolField.getProtocolId()); + builder.append(StringUtils.format("{} {} = ({}){}.read($1);", protocolName, result, protocolName, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(objectProtocolField.getProtocolId()))); + return result; + } + + private String getProtocolClassCanonicalName(short protocolId) { + return ProtocolManager.getProtocol(protocolId).protocolConstructor().getDeclaringClass().getCanonicalName(); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceSetSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceSetSerializer.java new file mode 100644 index 00000000..ba3f80ec --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceSetSerializer.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.registration.field.SetField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceSetSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + var setField = (SetField) fieldRegistration; + + switch (setField.getType().getTypeName()) { + case "java.util.Set": + builder.append(StringUtils.format("{}.writeIntSet($1, (Set){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "java.util.Set": + builder.append(StringUtils.format("{}.writeLongSet($1, (Set){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + case "java.util.Set": + builder.append(StringUtils.format("{}.writeStringSet($1, (Set){});", EnhanceUtils.byteBufUtils, objectStr)); + return; + default: + } + + // Set + if (setField.getSetElementRegistration() instanceof ObjectProtocolField) { + var objectProtocolField = (ObjectProtocolField) setField.getSetElementRegistration(); + builder.append(StringUtils.format("{}.writePacketSet($1, (Set){}, {});", EnhanceUtils.byteBufUtils, objectStr, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(objectProtocolField.getProtocolId()))); + return; + } + + var set = "set" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Set {} = (Set){};", set, objectStr)); + + builder.append(StringUtils.format("{}.writeInt($1, CollectionUtils.size({}));", EnhanceUtils.byteBufUtils, set)); + + var iterator = "iterator" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Iterator {} = CollectionUtils.iterator({});", iterator, set)); + builder.append(StringUtils.format("while({}.hasNext()) {", iterator)); + + var element = "element" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("Object {}={}.next();", element, iterator)); + EnhanceUtils.enhanceSerializer(setField.getSetElementRegistration().serializer()) + .writeObject(builder, element, field, setField.getSetElementRegistration()); + builder.append("}"); + + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var setField = (SetField) fieldRegistration; + var set = "set" + GenerateUtils.index.getAndIncrement(); + + switch (setField.getType().getTypeName()) { + case "java.util.Set": + builder.append(StringUtils.format("Set {} = {}.readIntSet($1);", set, EnhanceUtils.byteBufUtils)); + return set; + case "java.util.Set": + builder.append(StringUtils.format("Set {} = {}.readLongSet($1);", set, EnhanceUtils.byteBufUtils)); + return set; + case "java.util.Set": + builder.append(StringUtils.format("Set {} = {}.readStringSet($1);", set, EnhanceUtils.byteBufUtils)); + return set; + default: + } + + if (setField.getSetElementRegistration() instanceof ObjectProtocolField) { + var objectProtocolField = (ObjectProtocolField) setField.getSetElementRegistration(); + builder.append(StringUtils.format("Set {} = {}.readPacketSet($1, {});", set, EnhanceUtils.byteBufUtils, EnhanceUtils.getProtocolRegistrationFieldNameByProtocolId(objectProtocolField.getProtocolId()))); + return set; + } + + var size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("int {} = {}.readInt($1);", size, EnhanceUtils.byteBufUtils)); + builder.append(StringUtils.format("Set {} = CollectionUtils.newFixedSet({});", set, size)); + + var i = "i" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for(int {}=0; {}<{}; {}++){", i, i, size, i)); + + var readObject = EnhanceUtils.enhanceSerializer(setField.getSetElementRegistration().serializer()).readObject(builder, field, setField.getSetElementRegistration()); + builder.append(StringUtils.format("{}.add({});}", set, readObject)); + return set; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceShortSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceShortSerializer.java new file mode 100644 index 00000000..f6f34fe0 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceShortSerializer.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceShortSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("{}.writeShort($1, {});", EnhanceUtils.byteBufUtils, objectStr)); + } else { + builder.append(StringUtils.format("{}.writeShortBox($1, (Short){});", EnhanceUtils.byteBufUtils, objectStr)); + } + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + + if (field.getType().isPrimitive() || (field.getType().isArray() && field.getType().getComponentType().isPrimitive())) { + builder.append(StringUtils.format("short {} = {}.readShort($1);", result, EnhanceUtils.byteBufUtils)); + } else { + builder.append(StringUtils.format("Short {} = {}.readShortBox($1);", result, EnhanceUtils.byteBufUtils)); + } + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceStringSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceStringSerializer.java new file mode 100644 index 00000000..5e676b7b --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/EnhanceStringSerializer.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.EnhanceUtils; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class EnhanceStringSerializer implements IEnhanceSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration) { + builder.append(StringUtils.format("{}.writeString($1, (String){});", EnhanceUtils.byteBufUtils, objectStr)); + } + + @Override + public String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration) { + var result = "result" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("String {} = {}.readString($1);", result, EnhanceUtils.byteBufUtils)); + return result; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/IEnhanceSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/IEnhanceSerializer.java new file mode 100644 index 00000000..d381cbec --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/enhance/IEnhanceSerializer.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.enhance; + +import com.zfoo.protocol.registration.field.IFieldRegistration; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IEnhanceSerializer { + + /** + * IProtocolRegistration.write(ByteBuf buffer, IPacket packet); + * $1=buffer + * $2=packet + */ + void writeObject(StringBuilder builder, String objectStr, Field field, IFieldRegistration fieldRegistration); + + /** + * IProtocolRegistration.Object read(ByteBuf buffer); + * $1=buffer + */ + String readObject(StringBuilder builder, Field field, IFieldRegistration fieldRegistration); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/GenerateJsUtils.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/GenerateJsUtils.java new file mode 100644 index 00000000..27f0e585 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/GenerateJsUtils.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.generate.GenerateProtocolDocument; +import com.zfoo.protocol.generate.GenerateProtocolPath; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.registration.ProtocolRegistration; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.*; +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +import static com.zfoo.protocol.util.FileUtils.LS; +import static com.zfoo.protocol.util.StringUtils.TAB; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateJsUtils { + + private static final String PROTOCOL_OUTPUT_ROOT_PATH = "jsProtocol/"; + + private static Map jsSerializerMap; + + public static IJsSerializer jsSerializer(ISerializer serializer) { + return jsSerializerMap.get(serializer); + } + + public static void init() { + FileUtils.deleteFile(new File(PROTOCOL_OUTPUT_ROOT_PATH)); + FileUtils.createDirectory(PROTOCOL_OUTPUT_ROOT_PATH); + + jsSerializerMap = new HashMap<>(); + jsSerializerMap.put(BooleanSerializer.getInstance(), new JsBooleanSerializer()); + jsSerializerMap.put(ByteSerializer.getInstance(), new JsByteSerializer()); + jsSerializerMap.put(ShortSerializer.getInstance(), new JsShortSerializer()); + jsSerializerMap.put(IntSerializer.getInstance(), new JsIntSerializer()); + jsSerializerMap.put(LongSerializer.getInstance(), new JsLongSerializer()); + jsSerializerMap.put(FloatSerializer.getInstance(), new JsFloatSerializer()); + jsSerializerMap.put(DoubleSerializer.getInstance(), new JsDoubleSerializer()); + jsSerializerMap.put(CharSerializer.getInstance(), new JsCharSerializer()); + jsSerializerMap.put(StringSerializer.getInstance(), new JsStringSerializer()); + jsSerializerMap.put(ArraySerializer.getInstance(), new JsArraySerializer()); + jsSerializerMap.put(ListSerializer.getInstance(), new JsListSerializer()); + jsSerializerMap.put(SetSerializer.getInstance(), new JsSetSerializer()); + jsSerializerMap.put(MapSerializer.getInstance(), new JsMapSerializer()); + jsSerializerMap.put(ObjectProtocolSerializer.getInstance(), new JsObjectProtocolSerializer()); + } + + public static void clear() { + jsSerializerMap = null; + } + + public static void createProtocolManager(List protocolList) throws IOException { + var list = List.of("js/buffer/ByteBuffer.js" + , "js/buffer/long.js" + , "js/buffer/longbits.js"); + + for (var fileName : list) { + var fileInputStream = ClassUtils.getFileFromClassPath(fileName); + var createFile = new File(StringUtils.format("{}{}", PROTOCOL_OUTPUT_ROOT_PATH, StringUtils.substringAfterFirst(fileName, "js/"))); + FileUtils.writeInputStreamToFile(createFile, fileInputStream); + } + + + // 生成ProtocolManager.js文件 + var jsBuilder = new StringBuilder(); + + protocolList.stream() + .filter(it -> Objects.nonNull(it)) + .forEach(it -> { + var name = it.protocolConstructor().getDeclaringClass().getSimpleName(); + var path = GenerateProtocolPath.getProtocolPath(it.protocolId()); + if (StringUtils.isBlank(path)) { + jsBuilder.append(StringUtils.format("import {} from './{}.js';", name, name)).append(LS); + } else { + jsBuilder.append(StringUtils.format("import {} from './{}/{}.js';", name, path, name)).append(LS); + } + }); + + jsBuilder.append(LS).append(LS); + + var protocolManagerStr = StringUtils.bytesToString(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("js/ProtocolManager.js"))); + jsBuilder.append(protocolManagerStr); + + jsBuilder.append("ProtocolManager.initProtocol = function initProtocol() {").append(LS); + protocolList.stream().filter(it -> Objects.nonNull(it)) + .forEach(it -> jsBuilder.append(TAB).append(StringUtils.format("protocols.set({}, {});", it.protocolId(), it.protocolConstructor().getDeclaringClass().getSimpleName())).append(LS)); + jsBuilder.append("};").append(LS + LS); + + jsBuilder.append("export default ProtocolManager;").append(LS); + + FileUtils.writeStringToFile(new File(StringUtils.format("{}{}", PROTOCOL_OUTPUT_ROOT_PATH, "ProtocolManager.js")), jsBuilder.toString()); + } + + public static void createJsProtocolFile(ProtocolRegistration registration) { + // 初始化index + GenerateUtils.index.set(0); + + var protocolId = registration.protocolId(); + var registrationConstructor = registration.getConstructor(); + + var protocolClazzName = registrationConstructor.getDeclaringClass().getSimpleName(); + + var jsBuilder = new StringBuilder(); + + // 如果协议包含子协议,则需要导入ProtocolManager + var subProtocols = ProtocolManager.getAllSubProtocolIds(protocolId); + if (CollectionUtils.isNotEmpty(subProtocols)) { + var path = GenerateProtocolPath.getProtocolPath(protocolId); + if (StringUtils.isBlank(path)) { + jsBuilder.append("import ProtocolManager from './ProtocolManager.js';").append(LS); + } else { + jsBuilder.append("import ProtocolManager from '"); + Arrays.stream(path.split(StringUtils.SLASH)).forEach(it -> jsBuilder.append("../")); + jsBuilder.append("ProtocolManager.js';").append(LS); + } + } + + // export object + jsBuilder.append(exportFunction(registration)); + + // protocolId method + jsBuilder.append(protocolIdFunction(registration)); + + // writeObject method + jsBuilder.append(writeObject(registration)); + + // readObject method + jsBuilder.append(readObject(registration)); + + + jsBuilder.append(LS).append(StringUtils.format("export default {};", protocolClazzName)).append(LS); + + + var protocolOutputPath = StringUtils.format("{}{}/{}.js" + , PROTOCOL_OUTPUT_ROOT_PATH + , GenerateProtocolPath.getProtocolPath(protocolId) + , protocolClazzName); + FileUtils.writeStringToFile(new File(protocolOutputPath), jsBuilder.toString()); + } + + private static String exportFunction(ProtocolRegistration registration) { + var protocolId = registration.getId(); + var fields = registration.getFields(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var protocolDocument = GenerateProtocolDocument.getProtocolDocument(protocolId); + var docTitle = protocolDocument.getKey(); + var docFieldMap = protocolDocument.getValue(); + + var jsBuilder = new StringBuilder(); + + if (!StringUtils.isBlank(docTitle)) { + jsBuilder.append(docTitle).append(LS); + } + + jsBuilder.append(StringUtils.format("const {} = function(", protocolClazzName)); + jsBuilder.append(StringUtils.joinWith(", ", Arrays.stream(fields).map(it -> it.getName()).collect(Collectors.toList()).toArray())) + .append(") {") + .append(LS); + + for (var field : fields) { + var propertyName = field.getName(); + + // 生成注释 + var doc = docFieldMap.get(propertyName); + if (!StringUtils.isBlank(doc)) { + Arrays.stream(doc.split(LS)).forEach(it -> jsBuilder.append(TAB).append(it).append(LS)); + } + + jsBuilder.append(TAB) + .append(StringUtils.format("this.{} = {};", propertyName, propertyName)) + // 生成类型的注释 + .append(" // ").append(field.getGenericType().getTypeName()) + .append(LS); + } + + jsBuilder.append("};").append(LS).append(LS); + return jsBuilder.toString(); + } + + private static String protocolIdFunction(ProtocolRegistration registration) { + var protocolId = registration.getId(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var jsBuilder = new StringBuilder(); + jsBuilder.append(StringUtils.format("{}.prototype.protocolId = function() {", protocolClazzName)).append(LS); + jsBuilder.append(TAB).append(StringUtils.format("return {};", protocolId)).append(LS); + jsBuilder.append("};").append(LS).append(LS); + + return jsBuilder.toString(); + } + + + private static String writeObject(ProtocolRegistration registration) { + var fields = registration.getFields(); + var fieldRegistrations = registration.getFieldRegistrations(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var jsBuilder = new StringBuilder(); + jsBuilder.append(StringUtils.format("{}.writeObject = function(byteBuffer, packet) {", protocolClazzName)).append(LS); + + jsBuilder.append(TAB).append("if (packet === null) {").append(LS); + jsBuilder.append(TAB + TAB).append("byteBuffer.writeBoolean(false);").append(LS); + jsBuilder.append(TAB + TAB).append("return;").append(LS); + jsBuilder.append(TAB).append("}").append(LS); + + jsBuilder.append(TAB).append("byteBuffer.writeBoolean(true);").append(LS); + + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + IFieldRegistration fieldRegistration = fieldRegistrations[i]; + + jsSerializer(fieldRegistration.serializer()).writeObject(jsBuilder, "packet." + field.getName(), 1, field, fieldRegistration); + } + + jsBuilder.append("};").append(LS).append(LS); + return jsBuilder.toString(); + } + + + private static String readObject(ProtocolRegistration registration) { + var fields = registration.getFields(); + var fieldRegistrations = registration.getFieldRegistrations(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var jsBuilder = new StringBuilder(); + jsBuilder.append(StringUtils.format("{}.readObject = function(byteBuffer) {", protocolClazzName)).append(LS); + jsBuilder.append(TAB).append("if (!byteBuffer.readBoolean()) {").append(LS); + jsBuilder.append(TAB + TAB).append("return null;").append(LS); + jsBuilder.append(TAB).append("}").append(LS); + + + jsBuilder.append(TAB).append(StringUtils.format("const packet = new {}();", protocolClazzName)).append(LS); + + + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var fieldRegistration = fieldRegistrations[i]; + + var readObject = jsSerializer(fieldRegistration.serializer()).readObject(jsBuilder, 1, field, fieldRegistration); + jsBuilder.append(TAB).append(StringUtils.format("packet.{} = {};", field.getName(), readObject)).append(LS); + } + + jsBuilder.append(TAB).append("return packet;").append(LS); + + jsBuilder.append("};").append(LS); + + return jsBuilder.toString(); + } + + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/IJsSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/IJsSerializer.java new file mode 100644 index 00000000..cbdfc9e3 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/IJsSerializer.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IJsSerializer { + + void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration); + + String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsArraySerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsArraySerializer.java new file mode 100644 index 00000000..29ddc629 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsArraySerializer.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.ArrayField; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsArraySerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ArrayField arrayField = (ArrayField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} === null) {", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer.writeInt(0);").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("} else {").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer.writeInt({}.length);", objectStr)).append(LS); + + String element = "element" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("{}.forEach({} => {", objectStr, element)).append(LS); + GenerateJsUtils.jsSerializer(arrayField.getArrayElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, arrayField.getArrayElementRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("});").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + ArrayField arrayField = (ArrayField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = [];", result)).append(LS); + + String i = "index" + GenerateUtils.index.getAndIncrement(); + String size = "size" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readInt();", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0) {", size)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (let {} = 0; {} < {}; {}++) {", i, i, size, i)).append(LS); + String readObject = GenerateJsUtils.jsSerializer(arrayField.getArrayElementRegistration().serializer()) + .readObject(builder, deep + 2, field, arrayField.getArrayElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}.push({});", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsBooleanSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsBooleanSerializer.java new file mode 100644 index 00000000..dbd2992a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsBooleanSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsBooleanSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeBoolean({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readBoolean(); ", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsByteSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsByteSerializer.java new file mode 100644 index 00000000..3f8c8c1d --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsByteSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsByteSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeByte({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readByte();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsCharSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsCharSerializer.java new file mode 100644 index 00000000..1b79cece --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsCharSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsCharSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeChar({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readChar();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsDoubleSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsDoubleSerializer.java new file mode 100644 index 00000000..c4cfa5fb --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsDoubleSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsDoubleSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeDouble({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readDouble();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsFloatSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsFloatSerializer.java new file mode 100644 index 00000000..9de2176e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsFloatSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsFloatSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeFloat({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readFloat();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsIntSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsIntSerializer.java new file mode 100644 index 00000000..885c85c5 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsIntSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsIntSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeInt({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readInt();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsListSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsListSerializer.java new file mode 100644 index 00000000..74431b5a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsListSerializer.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ListField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsListSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ListField listField = (ListField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} === null) {", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer.writeInt(0);").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("} else {").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer.writeInt({}.length);", objectStr)).append(LS); + + String element = "element" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("{}.forEach({} => {", objectStr, element)).append(LS); + GenerateJsUtils.jsSerializer(listField.getListElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, listField.getListElementRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("});").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + ListField listField = (ListField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = [];", result)).append(LS); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("const {} = byteBuffer.readInt();", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0) {", size)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + String i = "index" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for (let {} = 0; {} < {}; {}++) {", i, i, size, i)).append(LS); + String readObject = GenerateJsUtils.jsSerializer(listField.getListElementRegistration().serializer()) + .readObject(builder, deep + 2, field, listField.getListElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}.push({});", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsLongSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsLongSerializer.java new file mode 100644 index 00000000..6bb562d4 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsLongSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsLongSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeLong({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readLong();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsMapSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsMapSerializer.java new file mode 100644 index 00000000..4483e976 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsMapSerializer.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.MapField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsMapSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + MapField mapField = (MapField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} === null) {", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer.writeInt(0);").append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append("} else {").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer.writeInt({}.size);", objectStr)).append(LS); + + String key = "key" + GenerateUtils.index.getAndIncrement(); + String value = "value" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("{}.forEach(({}, {}) => {", objectStr, value, key)).append(LS); + GenerateJsUtils.jsSerializer(mapField.getMapKeyRegistration().serializer()) + .writeObject(builder, key, deep + 2, field, mapField.getMapKeyRegistration()); + GenerateJsUtils.jsSerializer(mapField.getMapValueRegistration().serializer()) + .writeObject(builder, value, deep + 2, field, mapField.getMapValueRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("});").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + MapField mapField = (MapField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = new Map();", result)).append(LS); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("const {} = byteBuffer.readInt();", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0) {", size)).append(LS); + + String i = "index" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for (let {} = 0; {} < {}; {}++) {", i, i, size, i)).append(LS); + + String keyObject = GenerateJsUtils.jsSerializer(mapField.getMapKeyRegistration().serializer()) + .readObject(builder, deep + 2, field, mapField.getMapKeyRegistration()); + + + String valueObject = GenerateJsUtils.jsSerializer(mapField.getMapValueRegistration().serializer()) + .readObject(builder, deep + 2, field, mapField.getMapValueRegistration()); + GenerateUtils.addTab(builder, deep + 2); + + builder.append(StringUtils.format("{}.set({}, {});", result, keyObject, valueObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsObjectProtocolSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsObjectProtocolSerializer.java new file mode 100644 index 00000000..5d37f7f2 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsObjectProtocolSerializer.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsObjectProtocolSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("ProtocolManager.getProtocol({}).writeObject(byteBuffer, {});", objectProtocolField.getProtocolId(), objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = ProtocolManager.getProtocol({}).readObject(byteBuffer);", result, objectProtocolField.getProtocolId())).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsSetSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsSetSerializer.java new file mode 100644 index 00000000..76e92031 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsSetSerializer.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.SetField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsSetSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + SetField setField = (SetField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} === null) {", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer.writeInt(0);").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("} else {").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer.writeInt({}.size);", objectStr)).append(LS); + + String element = "element" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("{}.forEach({} => {", objectStr, element)).append(LS); + GenerateJsUtils.jsSerializer(setField.getSetElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, setField.getSetElementRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("});").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + SetField setField = (SetField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = new Set();", result)).append(LS); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("const {} = byteBuffer.readInt();", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if ({} > 0) {", size)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + String i = "index" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for (let {} = 0; {} < {}; {}++) {", i, i, size, i)).append(LS); + String readObject = GenerateJsUtils.jsSerializer(setField.getSetElementRegistration().serializer()) + .readObject(builder, deep + 2, field, setField.getSetElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}.add({});", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("}").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("}").append(LS); + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsShortSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsShortSerializer.java new file mode 100644 index 00000000..fa0ab70e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsShortSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsShortSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeShort({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readShort();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsStringSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsStringSerializer.java new file mode 100644 index 00000000..8ecfe0c4 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/js/JsStringSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.js; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsStringSerializer implements IJsSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer.writeString({});", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("const {} = byteBuffer.readString();", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/GenerateLuaUtils.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/GenerateLuaUtils.java new file mode 100644 index 00000000..a7ec9282 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/GenerateLuaUtils.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.ProtocolManager; +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.generate.GenerateProtocolDocument; +import com.zfoo.protocol.generate.GenerateProtocolPath; +import com.zfoo.protocol.registration.IProtocolRegistration; +import com.zfoo.protocol.registration.ProtocolRegistration; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.*; +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.FileUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.stream.Collectors; + +import static com.zfoo.protocol.util.FileUtils.LS; +import static com.zfoo.protocol.util.StringUtils.TAB; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class GenerateLuaUtils { + + private static final String PROTOCOL_OUTPUT_ROOT_PATH = "LuaProtocol/"; + + private static Map luaSerializerMap; + + public static ILuaSerializer luaSerializer(ISerializer serializer) { + return luaSerializerMap.get(serializer); + } + + public static void init() { + FileUtils.deleteFile(new File(PROTOCOL_OUTPUT_ROOT_PATH)); + FileUtils.createDirectory(PROTOCOL_OUTPUT_ROOT_PATH); + + luaSerializerMap = new HashMap<>(); + luaSerializerMap.put(BooleanSerializer.getInstance(), new LuaBooleanSerializer()); + luaSerializerMap.put(ByteSerializer.getInstance(), new LuaByteSerializer()); + luaSerializerMap.put(ShortSerializer.getInstance(), new LuaShortSerializer()); + luaSerializerMap.put(IntSerializer.getInstance(), new LuaIntSerializer()); + luaSerializerMap.put(LongSerializer.getInstance(), new LuaLongSerializer()); + luaSerializerMap.put(FloatSerializer.getInstance(), new LuaFloatSerializer()); + luaSerializerMap.put(DoubleSerializer.getInstance(), new LuaDoubleSerializer()); + luaSerializerMap.put(CharSerializer.getInstance(), new LuaCharSerializer()); + luaSerializerMap.put(StringSerializer.getInstance(), new LuaStringSerializer()); + luaSerializerMap.put(ArraySerializer.getInstance(), new LuaArraySerializer()); + luaSerializerMap.put(ListSerializer.getInstance(), new LuaListSerializer()); + luaSerializerMap.put(SetSerializer.getInstance(), new LuaSetSerializer()); + luaSerializerMap.put(MapSerializer.getInstance(), new LuaMapSerializer()); + luaSerializerMap.put(ObjectProtocolSerializer.getInstance(), new LuaObjectProtocolSerializer()); + } + + public static void clear() { + luaSerializerMap = null; + } + + public static void createProtocolManager(List protocolList) throws IOException { + var list = List.of("lua/Buffer/ByteBuffer.lua", "lua/Buffer/Long.lua"); + + for (var fileName : list) { + var fileInputStream = ClassUtils.getFileFromClassPath(fileName); + var createFile = new File(StringUtils.format("{}{}", PROTOCOL_OUTPUT_ROOT_PATH, StringUtils.substringAfterFirst(fileName, "lua/"))); + FileUtils.writeInputStreamToFile(createFile, fileInputStream); + } + + // 生成Protocol.lua文件 + var luaBuilder = new StringBuilder(); + + var protocolManagerStr = StringUtils.bytesToString(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("lua/ProtocolManager.lua"))); + luaBuilder.append(protocolManagerStr); + + luaBuilder.append("function initProtocol()").append(LS); + protocolList.stream() + .filter(it -> Objects.nonNull(it)) + .forEach(it -> { + var name = it.protocolConstructor().getDeclaringClass().getSimpleName(); + var path = GenerateProtocolPath.getCapitalizeProtocolPath(it.protocolId()); + + if (StringUtils.isBlank(path)) { + luaBuilder.append(TAB).append(StringUtils.format("local {} = require(\"LuaProtocol.{}\")", name, name)).append(LS); + } else { + luaBuilder.append(TAB).append(StringUtils.format("local {} = require(\"LuaProtocol.{}.{}\")" + , name, path.replaceAll(StringUtils.SLASH, StringUtils.PERIOD), name)).append(LS); + } + }); + + protocolList.stream().filter(it -> Objects.nonNull(it)) + .forEach(it -> luaBuilder.append(TAB).append(StringUtils.format("protocols[{}] = {}", it.protocolId(), it.protocolConstructor().getDeclaringClass().getSimpleName())).append(LS)); + + luaBuilder.append("end").append(LS + LS); + luaBuilder.append("ProtocolManager.initProtocol = initProtocol").append(LS); + luaBuilder.append("return ProtocolManager").append(LS); + + FileUtils.writeStringToFile(new File(StringUtils.format("{}{}", PROTOCOL_OUTPUT_ROOT_PATH, "ProtocolManager.lua")), luaBuilder.toString()); + } + + public static void createLuaProtocolFile(ProtocolRegistration registration) { + // 初始化index + GenerateUtils.index.set(0); + + var protocolId = registration.protocolId(); + var registrationConstructor = registration.getConstructor(); + var fieldRegistrations = registration.getFieldRegistrations(); + + var protocolClazzName = registrationConstructor.getDeclaringClass().getSimpleName(); + + var luaBuilder = new StringBuilder(); + + // document + luaBuilder.append(documentTitleAndImport(registration)); + + // new object + luaBuilder.append(newFunction(registration)); + + // protocolId method + luaBuilder.append(protocolIdFunction(registration)); + + // writeObject method + luaBuilder.append(writePacket(registration)); + + // readObject method + luaBuilder.append(readPacket(registration)).append(LS); + + + luaBuilder.append(StringUtils.format("return {}", protocolClazzName)).append(LS); + + + var protocolOutputPath = StringUtils.format("{}{}/{}.lua" + , PROTOCOL_OUTPUT_ROOT_PATH + , GenerateProtocolPath.getCapitalizeProtocolPath(protocolId) + , protocolClazzName); + FileUtils.writeStringToFile(new File(protocolOutputPath), luaBuilder.toString()); + } + + private static String documentTitleAndImport(ProtocolRegistration registration) { + var protocolId = registration.protocolId(); + var registrationConstructor = registration.getConstructor(); + var fieldRegistrations = registration.getFieldRegistrations(); + var luaBuilder = new StringBuilder(); + + var protocolDocument = GenerateProtocolDocument.getProtocolDocument(protocolId); + var docTitle = protocolDocument.getKey(); + + if (!StringUtils.isBlank(docTitle)) { + Arrays.stream(docTitle.split(LS)).forEach(it -> luaBuilder.append(docToLuaDoc(it)).append(LS)); + luaBuilder.append(LS); + } + + + // 如果协议包含子协议,则需要导入ProtocolManager + var subProtocols = ProtocolManager.getAllSubProtocolIds(protocolId); + if (CollectionUtils.isNotEmpty(subProtocols)) { + luaBuilder.append("local ProtocolManager = require(\"LuaProtocol.ProtocolManager\")").append(LS + LS); + } + + return luaBuilder.toString(); + } + + private static String newFunction(ProtocolRegistration registration) { + short protocolId = registration.getId(); + var fields = registration.getFields(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var protocolDocument = GenerateProtocolDocument.getProtocolDocument(protocolId); + var docFieldMap = protocolDocument.getValue(); + + var luaBuilder = new StringBuilder(); + + luaBuilder.append(StringUtils.format("local {} = {}", protocolClazzName)).append(LS + LS); + + luaBuilder.append(StringUtils.format("function {}:new(", protocolClazzName)); + luaBuilder.append(StringUtils.joinWith(", ", Arrays.stream(fields).map(it -> it.getName()).collect(Collectors.toList()).toArray())) + .append(")") + .append(LS); + luaBuilder.append(TAB).append("local obj = {").append(LS); + + for (int i = 0; i < fields.length; i++) { + var field = fields[i]; + var propertyName = field.getName(); + + // 生成注释 + var doc = docFieldMap.get(propertyName); + if (!StringUtils.isBlank(doc)) { + Arrays.stream(doc.split(LS)).forEach(it -> luaBuilder.append(TAB + TAB).append(docToLuaDoc(it)).append(LS)); + } + + if (i == fields.length - 1) { + luaBuilder.append(TAB + TAB) + .append(StringUtils.format("{} = {}", propertyName, propertyName)); + } else { + luaBuilder.append(TAB + TAB) + .append(StringUtils.format("{} = {},", propertyName, propertyName)); + } + + // 生成类型的注释 + luaBuilder.append(" -- ").append(field.getGenericType().getTypeName()).append(LS); + } + luaBuilder.append(TAB).append("}").append(LS); + luaBuilder.append(TAB).append("setmetatable(obj, self)").append(LS); + luaBuilder.append(TAB).append("self.__index = self").append(LS); + luaBuilder.append(TAB).append("return obj").append(LS); + luaBuilder.append("end").append(LS).append(LS); + return luaBuilder.toString(); + } + + private static String protocolIdFunction(ProtocolRegistration registration) { + short protocolId = registration.getId(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var luaBuilder = new StringBuilder(); + luaBuilder.append(StringUtils.format("function {}:protocolId()", protocolClazzName)).append(LS); + luaBuilder.append(TAB).append(StringUtils.format("return {}", protocolId)).append(LS); + luaBuilder.append("end").append(LS).append(LS); + + return luaBuilder.toString(); + } + + + private static String writePacket(ProtocolRegistration registration) { + var fields = registration.getFields(); + var fieldRegistrations = registration.getFieldRegistrations(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var luaBuilder = new StringBuilder(); + luaBuilder.append(StringUtils.format("function {}:write(byteBuffer, packet)", protocolClazzName)).append(LS); + + luaBuilder.append(TAB).append("if packet == null then").append(LS); + luaBuilder.append(TAB + TAB).append("byteBuffer:writeBoolean(false)").append(LS); + luaBuilder.append(TAB + TAB).append("return").append(LS); + luaBuilder.append(TAB).append("end").append(LS); + + luaBuilder.append(TAB).append("byteBuffer:writeBoolean(true)").append(LS); + + + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + var fieldRegistration = fieldRegistrations[i]; + + luaSerializer(fieldRegistration.serializer()).writeObject(luaBuilder, "packet." + field.getName(), 1, field, fieldRegistration); + } + + luaBuilder.append("end").append(LS).append(LS); + return luaBuilder.toString(); + } + + + private static String readPacket(ProtocolRegistration registration) { + Field[] fields = registration.getFields(); + IFieldRegistration[] fieldRegistrations = registration.getFieldRegistrations(); + var protocolClazzName = registration.getConstructor().getDeclaringClass().getSimpleName(); + + var jsBuilder = new StringBuilder(); + jsBuilder.append(StringUtils.format("function {}:read(byteBuffer)", protocolClazzName)).append(LS); + jsBuilder.append(TAB).append("if not(byteBuffer:readBoolean()) then").append(LS); + jsBuilder.append(TAB + TAB).append("return nil").append(LS); + jsBuilder.append(TAB).append("end").append(LS); + + + jsBuilder.append(TAB).append(StringUtils.format("local packet = {}:new()", protocolClazzName)).append(LS); + + + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + IFieldRegistration fieldRegistration = fieldRegistrations[i]; + + String readObject = luaSerializer(fieldRegistration.serializer()).readObject(jsBuilder, 1, field, fieldRegistration); + jsBuilder.append(TAB).append(StringUtils.format("packet.{} = {}", field.getName(), readObject)).append(LS); + } + + jsBuilder.append(TAB).append("return packet").append(LS); + + jsBuilder.append("end").append(LS); + + return jsBuilder.toString(); + } + + + private static String docToLuaDoc(String doc) { + return doc.replaceFirst("//", "--"); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/ILuaSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/ILuaSerializer.java new file mode 100644 index 00000000..a18571be --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/ILuaSerializer.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ILuaSerializer { + + void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration); + + String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration); + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaArraySerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaArraySerializer.java new file mode 100644 index 00000000..c7ab55fc --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaArraySerializer.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.ArrayField; +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaArraySerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ArrayField arrayField = (ArrayField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} == null then", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer:writeInt(0)").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("else").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer:writeInt(#{});", objectStr)).append(LS); + + String index = "index" + GenerateUtils.index.getAndIncrement(); + String element = "element" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for {}, {} in pairs({}) do", index, element, objectStr)).append(LS); + GenerateLuaUtils.luaSerializer(arrayField.getArrayElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, arrayField.getArrayElementRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + var arrayField = (ArrayField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = {}", result)).append(LS); + + var i = "index" + GenerateUtils.index.getAndIncrement(); + var size = "size" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readInt()", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} > 0 then", size)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for {} = 1, {} do", i, size)).append(LS); + String readObject = GenerateLuaUtils.luaSerializer(arrayField.getArrayElementRegistration().serializer()) + .readObject(builder, deep + 2, field, arrayField.getArrayElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("table.insert({}, {})", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaBooleanSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaBooleanSerializer.java new file mode 100644 index 00000000..321f8bdb --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaBooleanSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaBooleanSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeBoolean({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readBoolean()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaByteSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaByteSerializer.java new file mode 100644 index 00000000..da5dfebb --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaByteSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaByteSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeByte({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readByte()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaCharSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaCharSerializer.java new file mode 100644 index 00000000..768f7b33 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaCharSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaCharSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeChar({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readChar()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaDoubleSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaDoubleSerializer.java new file mode 100644 index 00000000..05c764e6 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaDoubleSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaDoubleSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeDouble({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readDouble()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaFloatSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaFloatSerializer.java new file mode 100644 index 00000000..7104036b --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaFloatSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaFloatSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeFloat({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readFloat()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaIntSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaIntSerializer.java new file mode 100644 index 00000000..1cf258e3 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaIntSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaIntSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeInt({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readInt()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaListSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaListSerializer.java new file mode 100644 index 00000000..1980d95f --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaListSerializer.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ListField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaListSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ListField listField = (ListField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} == null then", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer:writeInt(0)").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("else").append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer:writeInt(#{})", objectStr)).append(LS); + + String index = "index" + GenerateUtils.index.getAndIncrement(); + String element = "element" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for {}, {} in pairs({}) do", index, element, objectStr)).append(LS); + GenerateLuaUtils.luaSerializer(listField.getListElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, listField.getListElementRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + ListField listField = (ListField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = {}", result)).append(LS); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("local {} = byteBuffer:readInt()", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} > 0 then", size)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + String i = "index" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for {} = 1, {} do", i, size)).append(LS); + String readObject = GenerateLuaUtils.luaSerializer(listField.getListElementRegistration().serializer()) + .readObject(builder, deep + 2, field, listField.getListElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("table.insert({}, {})", result, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaLongSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaLongSerializer.java new file mode 100644 index 00000000..47ab336f --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaLongSerializer.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaLongSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeLong({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readLong()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaMapSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaMapSerializer.java new file mode 100644 index 00000000..b3853774 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaMapSerializer.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.MapField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaMapSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + MapField mapField = (MapField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} == null then", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer:writeInt(0)").append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append("else").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer:writeInt(table.mapSize({}))", objectStr)).append(LS); + + String key = "key" + GenerateUtils.index.getAndIncrement(); + String value = "value" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for {}, {} in pairs({}) do", key, value, objectStr)).append(LS); + GenerateLuaUtils.luaSerializer(mapField.getMapKeyRegistration().serializer()) + .writeObject(builder, key, deep + 2, field, mapField.getMapKeyRegistration()); + GenerateLuaUtils.luaSerializer(mapField.getMapValueRegistration().serializer()) + .writeObject(builder, value, deep + 2, field, mapField.getMapValueRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + MapField mapField = (MapField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = {}", result)).append(LS); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("local {} = byteBuffer:readInt()", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} > 0 then", size)).append(LS); + + String i = "index" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for {} = 1, {} do", i, size, i)).append(LS); + + String keyObject = GenerateLuaUtils.luaSerializer(mapField.getMapKeyRegistration().serializer()) + .readObject(builder, deep + 2, field, mapField.getMapKeyRegistration()); + + + String valueObject = GenerateLuaUtils.luaSerializer(mapField.getMapValueRegistration().serializer()) + .readObject(builder, deep + 2, field, mapField.getMapValueRegistration()); + GenerateUtils.addTab(builder, deep + 2); + + builder.append(StringUtils.format("{}[{}] = {}", result, keyObject, valueObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaObjectProtocolSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaObjectProtocolSerializer.java new file mode 100644 index 00000000..11c228f3 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaObjectProtocolSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.ObjectProtocolField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaObjectProtocolSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("ProtocolManager.getProtocol({}):write(byteBuffer, {})", objectProtocolField.getProtocolId(), objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + ObjectProtocolField objectProtocolField = (ObjectProtocolField) fieldRegistration; + var result = "result" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = ProtocolManager.getProtocol({}):read(byteBuffer)", result, objectProtocolField.getProtocolId())).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaSetSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaSetSerializer.java new file mode 100644 index 00000000..b4bfdc88 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaSetSerializer.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.registration.field.SetField; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaSetSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + SetField setField = (SetField) fieldRegistration; + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} == null then", objectStr)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("byteBuffer:writeInt(0)").append(LS); + GenerateUtils.addTab(builder, deep); + + builder.append("else").append(LS); + + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("byteBuffer:writeInt(table.setSize({}))", objectStr)).append(LS); + + String index = "index" + GenerateUtils.index.getAndIncrement(); + String element = "element" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep + 1); + builder.append(StringUtils.format("for {}, {} in pairs({}) do", index, element, objectStr)).append(LS); + GenerateLuaUtils.luaSerializer(setField.getSetElementRegistration().serializer()) + .writeObject(builder, element, deep + 2, field, setField.getSetElementRegistration()); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + SetField setField = (SetField) fieldRegistration; + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = {}", result)).append(LS); + + GenerateUtils.addTab(builder, deep); + String size = "size" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("local {} = byteBuffer:readInt()", size)).append(LS); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("if {} > 0 then", size)).append(LS); + + GenerateUtils.addTab(builder, deep + 1); + String i = "index" + GenerateUtils.index.getAndIncrement(); + builder.append(StringUtils.format("for {} = 1, {} do", i, size)).append(LS); + String readObject = GenerateLuaUtils.luaSerializer(setField.getSetElementRegistration().serializer()) + .readObject(builder, deep + 2, field, setField.getSetElementRegistration()); + GenerateUtils.addTab(builder, deep + 2); + builder.append(StringUtils.format("{}[{}] = {}", result, readObject, readObject)).append(LS); + GenerateUtils.addTab(builder, deep + 1); + builder.append("end").append(LS); + GenerateUtils.addTab(builder, deep); + builder.append("end").append(LS); + + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaShortSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaShortSerializer.java new file mode 100644 index 00000000..fb9a570a --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaShortSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaShortSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeShort({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readShort()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaStringSerializer.java b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaStringSerializer.java new file mode 100644 index 00000000..fd48da35 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/serializer/lua/LuaStringSerializer.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.serializer.lua; + +import com.zfoo.protocol.registration.field.IFieldRegistration; +import com.zfoo.protocol.serializer.GenerateUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.lang.reflect.Field; + +import static com.zfoo.protocol.util.FileUtils.LS; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LuaStringSerializer implements ILuaSerializer { + + @Override + public void writeObject(StringBuilder builder, String objectStr, int deep, Field field, IFieldRegistration fieldRegistration) { + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("byteBuffer:writeString({})", objectStr)).append(LS); + } + + @Override + public String readObject(StringBuilder builder, int deep, Field field, IFieldRegistration fieldRegistration) { + String result = "result" + GenerateUtils.index.getAndIncrement(); + GenerateUtils.addTab(builder, deep); + builder.append(StringUtils.format("local {} = byteBuffer:readString()", result)).append(LS); + return result; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/util/AssertionUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/AssertionUtils.java new file mode 100644 index 00000000..84bee318 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/AssertionUtils.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + + +import com.zfoo.protocol.exception.AssertException; + +import java.util.Collection; +import java.util.Map; + +/** + * Assertion utility class that assists in validating arguments. + * This class is similar to JUnit's assertion library. If an argument value is + * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AssertionUtils { + + // ----------------------------------bool---------------------------------- + + /** + * Assert a boolean expression, throwing {@code IllegalArgumentException} + * if the test result is {@code false}. + * + * @param expression a boolean expression + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if expression is {@code false} + */ + public static void isTrue(boolean expression, String message) { + if (!expression) { + throw new AssertException(message); + } + } + + /** + * 可支持带参数format的类型:类{}的成员变量:{}不能有set方法:{} + * + * @param expression 表达式 + * @param format 格式 + * @param args 参数 + */ + public static void isTrue(boolean expression, String format, Object... args) { + if (!expression) { + throw new AssertException(format, args); + } + } + + public static void isTrue(boolean expression) { + isTrue(expression, "[Assertion failed] - this expression must be true"); + } + + + // ----------------------------------long---------------------------------- + + /** + * lt 参数1是否小于参数2 + * le 参数1是否小于等于参数2 + * gt 参数1是否大于参数2 + * ge 参数1是否大于等于参数2 + */ + public static void ge(long x, long y) { + if (x < y) { + throw new AssertException("[Assertion failed] - the param [x:{}] must greater or equal [y:{}]", x, y); + } + } + + public static void le(long x, long y) { + if (x > y) { + throw new AssertException("[Assertion failed] - the param [x:{}] must less or equal [y:{}]", x, y); + } + } + + public static void ge0(long x) { + ge(x, 0); + } + + public static void ge1(long x) { + ge(x, 1); + } + + public static void le0(long x) { + le(x, 1); + } + + public static void le1(long x) { + le(x, 1); + } + + + // ----------------------------------collection---------------------------------- + + /** + * Assert that a collection has elements; that is, it must not be + * {@code null} and must have at least one element. + * + * @param collection the collection to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the collection is {@code null} or has no elements + */ + public static void notEmpty(Collection collection, String message) { + if (collection == null || collection.isEmpty()) { + throw new AssertException(message); + } + } + + public static void notEmpty(Collection collection) { + notEmpty(collection, + "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); + } + + /** + * Assert that a Map has entries; that is, it must not be {@code null} + * and must have at least one entry. + * + * @param map the map to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the map is {@code null} or has no entries + */ + public static void notEmpty(Map map, String message) { + if (map == null || map.isEmpty()) { + throw new AssertException(message); + } + } + + public static void notEmpty(Map map) { + notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); + } + + + // ----------------------------------object---------------------------------- + + /** + * Assert that an object is {@code null} . + * + * @param object the object to check + * @param message the exception message to use if the assertion fails + * @throws IllegalArgumentException if the object is not {@code null} + */ + public static void isNull(Object object, String message) { + if (object != null) { + throw new AssertException(message); + } + } + + public static void isNull(Object object, String format, Object... args) { + if (object != null) { + throw new AssertException(format, args); + } + } + + public static void isNull(Object object) { + isNull(object, "[Assertion failed] - the object argument must be null"); + } + + public static void notNull(Object object, String message) { + if (object == null) { + throw new AssertException(message); + } + } + + public static void notNull(Object object, String format, Object... args) { + if (object == null) { + throw new AssertException(format, args); + } + } + + public static void notNull(Object object) { + notNull(object, "[Assertion failed] - this argument is required; it must not be null"); + } + + public static void notNull(Object... objects) { + for (int i = 0; i < objects.length; i++) { + notNull(objects[i], "the [index:{}] of objects must not be null", i); + } + } + + /** + * Assert that the provided object is an instance of the provided class. + * + * @param type the type to check against + * @param obj the object to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in ":" or "." so that the generated message looks OK when + * appended to it. + * @throws IllegalArgumentException if the object is not an instance of clazz + * @see Class#isInstance + */ + public static void isInstanceOf(Class type, Object obj, String message) { + notNull(type, "Type to check against must not be null"); + if (!type.isInstance(obj)) { + throw new AssertException( + (!StringUtils.isBlank(message) ? message + " " : "") + + "Object of class [" + (obj != null ? obj.getClass().getName() : "null") + + "] must be an instance of " + type); + } + } + + public static void isInstanceOf(Class clazz, Object obj) { + isInstanceOf(clazz, obj, ""); + } + + /** + * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. + *
Assert.isAssignable(Number.class, myClass);
+ * + * @param superType the super type to check against + * @param subType the sub type to check + * @param message a message which will be prepended to the message produced by + * the function itself, and which may be used to provide context. It should + * normally end in ":" or "." so that the generated message looks OK when + * appended to it. + * @throws IllegalArgumentException if the classes are not assignable + */ + public static void isAssignable(Class superType, Class subType, String message) { + notNull(superType, "Type to check against must not be null"); + if (subType == null || !superType.isAssignableFrom(subType)) { + throw new AssertException((!StringUtils.isBlank(message) ? message + " " : "") + + subType + " is not assignable to " + superType); + } + } + + public static void isAssignable(Class superType, Class subType) { + isAssignable(superType, subType, ""); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java new file mode 100644 index 00000000..93fd113e --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/ClassUtils.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + +import com.zfoo.protocol.collection.CollectionUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.security.CodeSource; +import java.security.ProtectionDomain; +import java.util.*; +import java.util.function.Predicate; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class ClassUtils { + + public static final String FILE_URL_PROTOCOL = "file"; + + public static final String JAR_URL_PROTOCOL = "jar"; + + public static final String CLASS_SUFFIX = ".class"; + + public static final String JAR_SUFFIX = ".jar"; + + public static final String ZIP_SUFFIX = ".zip"; + + public static final char FILE_SEPARATOR = '/'; + + /* + find the location of the class come from + 1.FileTest.class.getResource("") + 得到的是当前类FileTest.class文件的URI目录。不包括自己! + 如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/com/test/ + 2.FileTest.class.getResource("/") +   得到的是当前的classpath的绝对URI路径。 +   如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/ + 3.Thread.currentThread().getContextClassLoader().getResource("") + 得到的也是当前ClassPath的绝对URI路径。 +    如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/ + 4.FileTest.class.getClassLoader().getResource("") +   得到的也是当前ClassPath的绝对URI路径。 +    如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/ + 5.ClassLoader.getSystemResource("") +   得到的也是当前ClassPath的绝对URI路径。 +    如:file:/D:/java/eclipse32/workspace/jbpmtest3/bin/ +  我推荐使用Thread.currentThread().getContextClassLoader().getResource("")来得到当前的classpath的绝对路径的URI表示法。 + */ + + public static String classLocation(final Class cls) { + AssertionUtils.notNull(cls); + URL result = null; + String clsAsResource = cls.getName().replace(StringUtils.PERIOD, StringUtils.SLASH).concat(CLASS_SUFFIX); + ProtectionDomain pd = cls.getProtectionDomain(); + if (pd != null) { + CodeSource cs = pd.getCodeSource(); + if (cs != null) { + result = cs.getLocation(); + } + if (result != null) { + if (FILE_URL_PROTOCOL.equals(result.getProtocol())) { + try { + // "!/"为分隔符,分割jar包,和jar包里的文件 + if (result.toExternalForm().endsWith(JAR_SUFFIX) || result.toExternalForm().endsWith(ZIP_SUFFIX)) { + result = new URL(JAR_URL_PROTOCOL + StringUtils.COLON + result.toExternalForm() + "!/" + clsAsResource); + } else if (new File(result.getFile()).isDirectory()) { + result = new URL(result, clsAsResource); + } + } catch (MalformedURLException ignore) { + } + } + } + } + if (result == null) { + final ClassLoader clsLoader = cls.getClassLoader(); + result = clsLoader != null ? clsLoader.getResource(clsAsResource) : ClassLoader.getSystemResource(clsAsResource); + } + return result.toString(); + } + + + /** + * 获取指定包下的所有类,只能搜索当前项目路径和maven项目路径 + * + * @param packageName 形如"org.hotswap",不能带有斜线/,以为java格式为主 + * @return 当前项目下的所有Java类 + * @throws Exception 异常 + */ + public static Set> getAllClasses(String packageName) throws Exception { + Set> classSet = new LinkedHashSet<>(); + // 定义一个枚举的集合并进行循环来处理这个目录下的things,当前的classpath的绝对路径的URI表示法。 + Enumeration urlEnumeration = Thread.currentThread().getContextClassLoader().getResources(packageName.replace(StringUtils.PERIOD, StringUtils.SLASH)); + while (urlEnumeration.hasMoreElements()) { + // 获取下一个元素,如果是jar://得到的结果大概是:jar:file:/C:/Users/ibm/.m2/repository/junit/junit/4.12/junit-4.12.jar!/org/junit + URL url = urlEnumeration.nextElement(); + String protocol = url.getProtocol(); + System.out.println(url); + if (StringUtils.isBlank(protocol)) { + continue; + } + // file(不打包成jar运行),jar(打包成jar运行) + if (protocol.equals(FILE_URL_PROTOCOL)) { + // 获取包的物理路径 + String filePath = URLDecoder.decode(url.getFile(), StringUtils.DEFAULT_CHARSET); + List fileList = FileUtils.getAllReadableFiles(new File(filePath)); + for (File file : fileList) { + String fileName = file.getName(); + // 不是.class文件和包含美元符号的内部类或者匿名内部类不算 + if (!fileName.endsWith(CLASS_SUFFIX) || fileName.contains(StringUtils.DOLLAR)) { + continue; + } + // 如果是java类文件,则去掉后面的.class 只留下类名 + String className = StringUtils.substringBeforeLast(fileName, CLASS_SUFFIX); + if (!StringUtils.isBlank(packageName)) { + String a = StringUtils.substringAfterFirst(file.getAbsolutePath(), FileUtils.getProAbsPath() + File.separator); + a = a.replaceAll(StringUtils.BACK_SLASH + File.separator, StringUtils.PERIOD); + String b = StringUtils.substringBeforeFirst(a, packageName); + String c = StringUtils.substringAfterFirst(a, b); + className = StringUtils.substringBeforeLast(c, CLASS_SUFFIX); + } + classSet.add(Thread.currentThread().getContextClassLoader().loadClass(className)); + } + } else if (protocol.equals(JAR_URL_PROTOCOL)) { + JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile(); + if (jarFile == null) { + continue; + } + //得到该jar文件下面的类实体 + Enumeration jarEntryEnumeration = jarFile.entries(); + while (jarEntryEnumeration.hasMoreElements()) { + JarEntry entry = jarEntryEnumeration.nextElement(); + String jarEntryName = entry.getName(); + //这里我们需要过滤不是class文件和不在basePack包名下的类 + if (!jarEntryName.contains(CLASS_SUFFIX) || jarEntryName.contains(StringUtils.DOLLAR)) { + continue; + } + String className = StringUtils.substringBeforeLast(jarEntryName, CLASS_SUFFIX).replaceAll(StringUtils.SLASH, StringUtils.PERIOD); + if (!className.startsWith(packageName)) { + continue; + } + classSet.add(Thread.currentThread().getContextClassLoader().loadClass(className)); + } + } else { + throw new IllegalStateException(StringUtils.format("不合法的协议文件[protocol:{}]", protocol)); + } + } + return classSet; + } + + + public static List> getClasses(String packageName, Predicate> classFilter) { + Set> allClasses = null; + try { + allClasses = getAllClasses(packageName); + } catch (Exception e) { + throw new RuntimeException(e); + } + + if (CollectionUtils.isEmpty(allClasses)) { + return new ArrayList<>(); + } + + List> list = new ArrayList<>(); + + if (classFilter == null) { + list.addAll(allClasses); + return list; + } + + for (Class clazz : allClasses) { + if (classFilter.test(clazz)) { + list.add(clazz); + } + } + + return list; + } + + + /** + * 获取编译过后的类文件(*.class)的绝对路径 + * + * @param clazz 类Class + * @return 对应类的绝对路径 + */ + public static String getClassAbsPath(Class clazz) { + File file = new File(clazz.getResource("").getPath()); + return file.getAbsolutePath(); + } + + + public static ClassLoader getDefaultClassLoader() { + var classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader != null) { + return classLoader; + } + + // No thread context class loader -> use class loader of this class. + classLoader = ClassUtils.class.getClassLoader(); + if (classLoader != null) { + return classLoader; + } + + // getClassLoader() returning null indicates the bootstrap ClassLoader + classLoader = ClassLoader.getSystemClassLoader(); + return classLoader; + } + + + /** + * 从类路径中读取文件 + * + * @param filePath 一般指resources中的文件,也可以在jar中 + */ + public static InputStream getFileFromClassPath(String filePath) throws IOException { +// ClassUtils.getDefaultClassLoader().getResourceAsStream(filePath) + return ClassUtils.getDefaultClassLoader().getResource(filePath).openStream(); + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java new file mode 100644 index 00000000..60ad396b --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/FileUtils.java @@ -0,0 +1,554 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + +import com.zfoo.protocol.collection.CollectionUtils; + +import java.io.*; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +/** + * 文件操作工具类 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class FileUtils { + + + /** + * 类Unix路径分隔符 + */ + private static final String UNIX_SEPARATOR = StringUtils.SLASH; + /** + * Windows路径分隔符 + */ + private static final String WINDOWS_SEPARATOR = StringUtils.BACK_SLASH; + + + /** + * 获取当前系统的换行分隔符 + *
+     * Windows: \r\n
+     * Mac: \r
+     * Linux: \n
+     * 
+ */ + public static final String LS = System.lineSeparator(); + public static final String UNIX_LS = "\\n"; + public static final String WINDOWS_LS = "\\r\\n"; + // The file copy buffer size (30 MB) + private static final long FILE_COPY_BUFFER_SIZE = IOUtils.BYTES_PER_MB * 30; + + + /** + * User's current working directory + * + * @return 绝对路径路径 + */ + public static String getProAbsPath() { + return System.getProperty("user.dir"); + } + + //---------------------------------搜索文件-------------------------------------- + + /** + * 深度优先搜索文件 + * + * @param file 需要搜索的文件 + * @param fileName 需要搜索的目标文件名 + * @return 搜索到的文件 + */ + private static File searchFileInProject(File file, String fileName) { + // System.out.println(file.getName()); + if (file.isFile() && file.getName().equals(fileName)) { + return file; + } + if (file.isDirectory()) { + var files = file.listFiles(); + if (CollectionUtils.isEmpty(files)) { + return null; + } + + for (File temp : files) { + File result = searchFileInProject(temp, fileName); + if (result == null) { + continue; + } + return result; + } + } + return null; + } + + /** + * 广度优先搜索文件 + * + * @param fileOrDirectory 需要查找的文件夹 + * @return 所有可读的文件 + */ + public static List getAllReadableFiles(File fileOrDirectory) { + List readableFileList = new ArrayList<>(); + Queue queue = new LinkedList<>(); + queue.add(fileOrDirectory); + while (!queue.isEmpty()) { + File file = queue.poll(); + if (file.isDirectory()) { + for (File f : file.listFiles()) { + queue.offer(f); + } + continue; + } + + if (file.canRead()) { + readableFileList.add(file); + } + } + + return readableFileList; + } + + /** + * 搜索文件 + * + * @param file 需要查找的文件 + * @return 如果没有搜索到返回null + */ + public static File searchFileInProject(File file) { + return searchFileInProject(new File(getProAbsPath()), file.getName()); + } + + + /** + * 搜索文件 + *

+ * 注意:文件名必须是文件全称,包括文件名的后缀 + * + * @param fileName 文件名的全称,包括文件名的后缀 + * @return 如果没有搜索到返回null + */ + public static File searchFileInProject(String fileName) { + return searchFileInProject(new File(getProAbsPath()), fileName); + } + + //---------------------------------创建,删除文件-------------------------------------- + + /** + * 在path文件夹下创建一个fileName文件 + * + * @param path 路径 + * @param fileName 文件名 + * @return 新创建的File + * @throws IOException IO异常 + */ + public static File createFile(String path, String fileName) throws IOException { + var file = createDirectory(path); + + var newFile = new File(file.getAbsoluteFile() + File.separator + fileName); + if (newFile.exists()) { + throw new RuntimeException(StringUtils.format("文件已经存在[fileName:{}]", fileName)); + } + + if (!newFile.createNewFile()) { + throw new RuntimeException(StringUtils.format("创建文件[fileName:{}]失败", fileName)); + } + return newFile; + } + + public static File createDirectory(String path) { + var file = new File(path); + if (!file.exists()) { + if (!file.mkdirs()) { + throw new RuntimeException(StringUtils.format("Directory [file:{}] could not be created", file)); + } + } + return file; + } + + + /** + * Deletes a file. If file is a directory, delete it and all sub-directories. + * + * @param file file or directory to delete, must not be null + */ + public static void deleteFile(final File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File subFile : files) { + deleteFile(subFile); + } + } + if (!file.delete()) { + throw new RuntimeException("Unable to delete file directory: " + file); + } + } else { + boolean filePresent = file.exists(); + if (filePresent) { + if (!file.delete()) { + throw new RuntimeException("Unable to delete file: " + file); + } + } + } + } + + // ------------------------------------------------复制文件------------------------------------------------ + + /** + * Copies a file to a new location preserving the file date. + *

+ * This method copies the contents of the specified source file to the + * specified destination file. The directory holding the destination file is + * created if it does not exist. If the destination file exists, then this + * method will overwrite it. + *

+ * Note: This method tries to preserve the file's last + * modified date/times using {@link File#setLastModified(long)}, however + * it is not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be null + * @param destFile the new file, must not be null + * @throws IOException if source or destination is invalid + * @throws IOException if an IO error occurs during copying + * @throws IOException if the output file length is not the same as the input file length after the copy completes + */ + public static void copyFile(final File srcFile, final File destFile) throws IOException { + copyFile(srcFile, destFile, true); + } + + public static void copyFileToDirectory(final File srcFile, final File destDir) throws IOException { + copyFileToDirectory(srcFile, destDir, true); + } + + public static void copyFileToDirectory(final File srcFile, final File destDir, final boolean preserveFileDate) + throws IOException { + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (destDir.exists() && !destDir.isDirectory()) { + throw new IllegalArgumentException(StringUtils.format("Destination [destDir:{}] is not a directory", destDir)); + } + final File destFile = new File(destDir, srcFile.getName()); + copyFile(srcFile, destFile, preserveFileDate); + } + + /** + * Copies a file to a new location. + *

+ * This method copies the contents of the specified source file + * to the specified destination file. + * The directory holding the destination file is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

+ * Note: Setting preserveFileDate to + * {@code true} tries to preserve the file's last modified + * date/times using {@link File#setLastModified(long)}, however it is + * not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destFile the new file, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws IOException if source or destination is invalid + * @throws IOException if an IO error occurs during copying + * @throws IOException if the output file length is not the same as the input file length after the copy completes + */ + public static void copyFile(final File srcFile, final File destFile, + final boolean preserveFileDate) throws IOException { + checkFileRequirements(srcFile, destFile); + if (srcFile.isDirectory()) { + throw new IOException(StringUtils.format("Source [srcFile:{}] exists but is a directory", srcFile)); + } + if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { + throw new IOException(StringUtils.format("Source [srcFile:{}] and destination [destFile:{}] are the same", srcFile, destFile)); + } + final File parentFile = destFile.getParentFile(); + if (parentFile != null) { + if (!parentFile.mkdirs() && !parentFile.isDirectory()) { + throw new IOException(StringUtils.format("Destination [parentFile:{}] directory cannot be created", parentFile)); + } + } + if (destFile.exists() && !destFile.canWrite()) { + throw new IOException(StringUtils.format("Destination [destFile:{}] exists but is read-only", destFile)); + } + doCopyFile(srcFile, destFile, preserveFileDate); + } + + /** + * checks requirements for file copy + * + * @param src the source file + * @param dest the destination + * @throws FileNotFoundException if the destination does not exist + */ + private static void checkFileRequirements(File src, File dest) throws FileNotFoundException { + if (src == null) { + throw new NullPointerException("Source must not be null"); + } + if (dest == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!src.exists()) { + throw new FileNotFoundException(StringUtils.format("Source [src:{}] does not exist", src)); + } + } + + /** + * Internal copy file method. + * This caches the original file length, and throws an IOException + * if the output file length is different from the current input file length. + * So it may fail if the file changes size. + * It may also fail with "IllegalArgumentException: Negative size" if the input file is truncated part way + * through copying the data and the new file size is less than the current position. + * + * @param srcFile the validated source file, must not be {@code null} + * @param destFile the validated destination file, must not be {@code null} + * @param preserveFileDate whether to preserve the file date + * @throws IOException if an error occurs + * @throws IOException if the output file length is not the same as the input file length after the + * copy completes + * @throws IllegalArgumentException "Negative size" if the file is truncated so that the size is less than the + * position + */ + private static void doCopyFile(final File srcFile, final File destFile, final boolean preserveFileDate) + throws IOException { + if (destFile.exists() && destFile.isDirectory()) { + throw new IOException(StringUtils.format("Destination [destFile:{}] exists but is a directory", destFile)); + } + + FileInputStream fis = null; + FileOutputStream fos = null; + FileChannel input = null; + FileChannel output = null; + try { + fis = new FileInputStream(srcFile); + fos = new FileOutputStream(destFile); + input = fis.getChannel(); + output = fos.getChannel(); + final long size = input.size(); // TODO See IO-386 + long pos = 0; + long count; + while (pos < size) { + final long remain = size - pos; + count = Math.min(remain, FILE_COPY_BUFFER_SIZE); + final long bytesCopied = output.transferFrom(input, pos, count); + if (bytesCopied == 0) { // IO-385 - can happen if file is truncated after caching the size + break; // ensure we don't loop forever + } + pos += bytesCopied; + } + } finally { + IOUtils.closeIO(output, fos, input, fis); + } + + final long srcLen = srcFile.length(); // TODO See IO-386 + final long dstLen = destFile.length(); // TODO See IO-386 + if (srcLen != dstLen) { + throw new IOException(StringUtils.format("Failed to copy full contents from [srcFile:{}] to [destFile:{}] Expected length:[srcLen:{}] Actual [dstLen:{}]" + , srcFile, destFile, srcLen, dstLen)); + } + if (preserveFileDate) { + destFile.setLastModified(srcFile.lastModified()); + } + } + + + // ------------------------------------------------读取文件------------------------------------------------ + + /** + * Reads the contents of a file into a byte array. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the file contents, never {@code null} + * @throws IOException in case of an I/O error + */ + public static byte[] readFileToByteArray(final File file) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.toByteArray(in); // Do NOT use file.length() - see IO-453 + } finally { + IOUtils.closeIO(in); + } + } + + + public static String readFileToString(final File file) { + return StringUtils.joinWith(StringUtils.EMPTY, readFileToStringList(file).toArray()); + } + + public static List readFileToStringList(final File file) { + FileInputStream fileInputStream = null; + InputStreamReader inputStreamReader = null; + BufferedReader bufferedReader = null; + var list = new ArrayList(); + try { + fileInputStream = openInputStream(file); + inputStreamReader = new InputStreamReader(fileInputStream, StringUtils.DEFAULT_CHARSET_NAME); + bufferedReader = new BufferedReader(inputStreamReader); + String line; + while ((line = bufferedReader.readLine()) != null) { + list.add(line); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(bufferedReader, inputStreamReader, fileInputStream); + } + return list; + } + + + /** + * 以追加的方式写入一个content + * + * @param file 文件的绝对路径 + * @param content 写入的内容 + */ + public static void writeStringToFile(File file, String content) { + // 字节流 + FileOutputStream fileOutputStream = null; + // 转换流,设置编码集和解码集 .处理乱码问题,是字节到字符的桥梁 + OutputStreamWriter outputStreamWriter = null; + //处理流中的缓冲流,提高效率 + BufferedWriter bufferedWriter = null; + // 如果不用缓冲流的话,程序是读一个数据,写一个数据,这样在数据量大的程序中非常影响效率。 + // 缓冲流作用是把数据先写入缓冲区,等缓冲区满了,再把数据写到文件里。这样效率就大大提高了 + try { + // 以追加的方式打开文件 + fileOutputStream = openOutputStream(file, true); + outputStreamWriter = new OutputStreamWriter(fileOutputStream, StringUtils.DEFAULT_CHARSET_NAME); + bufferedWriter = new BufferedWriter(outputStreamWriter); + bufferedWriter.write(content);// 写数据 + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + // Java的垃圾回收机制不会回收任何的物理资源,只会回收堆内存中对象所占用的内存 + // finally总会被执行,即使try块中和catch块中有return,也会被执行。 + // 用来显示回收数据库连接,网络连接,磁盘文件 + IOUtils.closeIO(bufferedWriter, outputStreamWriter, fileOutputStream); + } + } + + public static void writeInputStreamToFile(File file, InputStream inputStream) { + FileOutputStream fileOutputStream = null; + OutputStreamWriter outputStreamWriter = null; + try { + fileOutputStream = openOutputStream(file, true); + IOUtils.copy(inputStream, fileOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(outputStreamWriter, fileOutputStream); + } + } + + //---------------------------------打开,关闭,文件流-------------------------------------- + + /** + * Opens a {@link FileInputStream} for the specified file, providing better + * error messages than simply calling new FileInputStream(file). + * + * @param file the file to open for input, must not be {@code null} + * @return a new {@link FileInputStream} for the specified file + * @throws FileNotFoundException if the file does not exist + * @throws IOException if the file object is a directory + * @throws IOException if the file cannot be read + */ + public static FileInputStream openInputStream(final File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException(StringUtils.format("File [file:{}] exists but is a directory", file)); + } + if (!file.canRead()) { + throw new IOException(StringUtils.format("File [file:{}] cannot be read", file)); + } + } else { + throw new FileNotFoundException(StringUtils.format("File [file:{}] does not exist", file)); + } + return new FileInputStream(file); + } + + /** + * 如果文件不存在,则创建该文件。最好指定为true,以追加的方式打开文件 + *

+ * The parent directory will be created if it does not exist.The file will be created if it does not exist. + * + * @param file the file to open for output, must not be {@code null} + * @param append if {@code true}, then bytes will be added to the end of the file rather than overwriting + * @return a new {@link FileOutputStream} for the specified file + * @throws IOException if the file object is a directory + * @throws IOException if the file cannot be written to + * @throws IOException if a parent directory needs creating but that fails + */ + public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException(StringUtils.format("File [file:{}] exists but is a directory", file)); + } + if (!file.canWrite()) { + throw new IOException(StringUtils.format("File [file:{}] cannot be written to", file)); + } + } else { + final File parentFile = file.getParentFile(); + if (parentFile != null) { + if (!parentFile.mkdirs() && !parentFile.isDirectory()) { + throw new IOException(StringUtils.format("Directory [parentFile:{}] could not be created", parentFile)); + } + } + } + return new FileOutputStream(file, append); + } + + + // ------------------------------------------------文件名称------------------------------------------------ + + /** + * 获得文件的扩展名,扩展名不带“.” + * + * @param fileName 文件名 + * @return 扩展名 + */ + public static String fileExtName(String fileName) { + if (StringUtils.isBlank(fileName)) { + return StringUtils.EMPTY; + } + var fileExtName = StringUtils.substringAfterLast(fileName, StringUtils.PERIOD); + if (StringUtils.isBlank(fileExtName) || fileExtName.contains(UNIX_SEPARATOR) || fileExtName.contains(WINDOWS_SEPARATOR)) { + return StringUtils.EMPTY; + } + return fileExtName; + } + + /** + * 获得文件的名称,不带“.”和扩展名 + */ + public static String fileSimpleName(String fileName) { + if (StringUtils.isBlank(fileName)) { + return StringUtils.EMPTY; + } + var fileSimpleName = StringUtils.substringBeforeLast(fileName, StringUtils.PERIOD); + if (StringUtils.isBlank(fileSimpleName) || fileSimpleName.contains(UNIX_SEPARATOR) || fileSimpleName.contains(WINDOWS_SEPARATOR)) { + return StringUtils.EMPTY; + } + return fileSimpleName; + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/util/IOUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/IOUtils.java new file mode 100644 index 00000000..57710f83 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/IOUtils.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + +import java.io.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class IOUtils { + + // Represents the end-of-file (or stream) + public static final int EOF = -1; + + // The number of bytes in a kilobyte + public static final int ONE_BYTE = 1; + // The number of bytes in a kilobyte + public static final int BYTES_PER_KB = ONE_BYTE * 1024; + // The number of bytes in a megabyte + public static final int BYTES_PER_MB = BYTES_PER_KB * 1024; + // The number of bytes in a gigabyte + public static final long BYTES_PER_GB = BYTES_PER_MB * 1024; + + public static final int BITS_PER_BYTE = ONE_BYTE * 8; + public static final int BITS_PER_KB = BYTES_PER_KB * 8; + public static final int BITS_PER_MB = BYTES_PER_MB * 8; + public static final long BITS_PER_GB = BYTES_PER_GB * 8L; + + + /** + * Copies bytes from an InputStream to an OutputStream. + *

+ * Large streams (over 2GB) will return a bytes copied value of -1 + * after the copy has completed since the correct number of bytes cannot be returned as an int. + * For large streams use the copyLarge(InputStream, OutputStream) method. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE + * @throws IOException IO异常 + */ + public static int copy(final InputStream input, final OutputStream output) throws IOException { + byte[] buffer = new byte[BYTES_PER_KB]; + long count = 0; + int n; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + + if (count > BYTES_PER_GB * 2L) { + return -1; + } + return (int) count; + } + + /** + * Gets the contents of an InputStream as a byte[]. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws IOException IO异常 + */ + public static byte[] toByteArray(final InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + var bytes = output.toByteArray(); + IOUtils.closeIO(input, output); + return bytes; + } + + + public static void closeIO(Closeable... closeables) { + if (closeables == null) { + return; + } + + for (Closeable obj : closeables) { + if (obj == null) { + continue; + } + try { + obj.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/protocol/src/main/java/com/zfoo/protocol/util/JsonUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/JsonUtils.java new file mode 100644 index 00000000..5a96e845 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/JsonUtils.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.type.ArrayType; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import com.fasterxml.jackson.module.afterburner.AfterburnerModule; +import com.zfoo.protocol.exception.ExceptionUtils; + +import java.io.IOException; +import java.util.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class JsonUtils { + + /** + * 适用于任何场景下的json转换,只要在各个类方法中不调用configure方法,则MAPPER都是线程安全的 + */ + private static final ObjectMapper MAPPER = new ObjectMapper(); + + + /** + * 使用字节码增强技术,只能转换POJO对象 + */ + private static final ObjectMapper MAPPER_TURBO = new ObjectMapper(); + + static { + //序列化 + MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); + MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + // MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + //序列化枚举是以toString()来输出,默认false,即默认以name()来输出 + // MAPPER.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX, true); + + + //反序列化 + //当反序列化有未知属性则抛异常,true打开这个设置 + MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + + MAPPER_TURBO.setSerializationInclusion(JsonInclude.Include.NON_NULL); + MAPPER_TURBO.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + MAPPER_TURBO.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + MAPPER_TURBO.registerModule(new AfterburnerModule()); + } + + public static T string2Object(String json, Class clazz) {//json=json string + try { + return MAPPER.readValue(json, clazz); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为对象[class:{}]时异常[error:{}]" + , json, clazz, ExceptionUtils.getStackTrace(e))); + } + } + + public static String object2String(Object object) { + try { + return MAPPER.writeValueAsString(object); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将对象[object:{}]转换为json字符串时异常[error:{}]" + , object, e.getMessage())); + } + } + + /** + * 涡轮增压,会字节码增强,慎用 + */ + public static T string2ObjectTurbo(String json, Class clazz) { + try { + return MAPPER_TURBO.readValue(json, clazz); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为对象[class:{}]时异常[error:{}]" + , json, clazz, ExceptionUtils.getStackTrace(e))); + } + } + + /** + * 涡轮增压,会字节码增强,慎用 + */ + public static String object2StringTurbo(Object object) { + try { + return MAPPER_TURBO.writeValueAsString(object); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将对象[object:{}]转换为json字符串时异常[error:{}]", object, e.getMessage())); + } + } + + @SuppressWarnings("unchecked") + public static List string2List(String json, Class clazz) { + CollectionType collectionType = MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, clazz); + try { + return (List) MAPPER.readValue(json, collectionType); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为list异常[error:{}]", json, e.getMessage())); + } + } + + //元素不可重复 + @SuppressWarnings("unchecked") + public static Set string2Set(String json, Class clazz) { + CollectionType collectionType = MAPPER.getTypeFactory().constructCollectionType(HashSet.class, clazz); + try { + return (Set) MAPPER.readValue(json, collectionType); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为Set[set:{}]异常[error:{}]" + , json, clazz, ExceptionUtils.getStackTrace(e))); + } + } + + public static , T> C string2Collection(String json, Class collectionType, Class elementType) { + try { + CollectionType e = MAPPER.getTypeFactory().constructCollectionType(collectionType, elementType); + return MAPPER.readValue(json, e); + } catch (IOException e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为Collection[collection:{}]异常[error:{}]" + , json, collectionType, ExceptionUtils.getStackTrace(e))); + } + } + + @SuppressWarnings("unchecked") + public static Map string2Map(String json, Class kClazz, Class vClazz) { + MapType mapType = MAPPER.getTypeFactory().constructMapType(HashMap.class, kClazz, vClazz); + try { + return (Map) MAPPER.readValue(json, mapType); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为map[key:{},value:{}]异常[error:{}]" + , json, kClazz, vClazz, ExceptionUtils.getStackTrace(e))); + } + } + + @SuppressWarnings("unchecked") + public static T[] string2Array(String json, Class clazz) { + ArrayType arrayType = MAPPER.getTypeFactory().constructArrayType(clazz); + try { + return (T[]) MAPPER.readValue(json, arrayType); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将json字符串[{}]转换为array[element:{}]异常[error:{}]" + , json, clazz, ExceptionUtils.getStackTrace(e))); + } + } + + /** + * 在js中获取属性名为nodeName的节点 + *

+ * 树模型是基于流式操作 + * + * @param json json string + * @param nodeName 节点名称 + * @return 节点名称为nodeName的json节点,没有返回空 + */ + public static JsonNode getNode(String json, String nodeName) { + try { + Queue queue = new ArrayDeque<>(); + JsonNode rootNode = MAPPER.readTree(json);//将Json串以树状结构读入内存 + JsonNode resultNode; + queue.add(rootNode); + while (!queue.isEmpty()) {//深度优先遍历算法 + JsonNode pollNode = queue.poll(); + resultNode = pollNode.get(nodeName); + if (resultNode != null) { + return resultNode; + } + Iterator iterator = pollNode.elements(); + while (iterator.hasNext()) {//循环遍历子节点下的信息 + JsonNode node = iterator.next(); + queue.add(node); + } + } + } catch (IOException e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为jsonTree[nodeName:{}]异常[error:{}]" + , json, nodeName, ExceptionUtils.getStackTrace(e))); + } + return null; + } + + public static Map getJsonMap(String json) { + try { + Map jsonMap = new HashMap<>(); + Queue queue = new ArrayDeque<>(); + JsonNode rootNode = MAPPER.readTree(json);//将Json串以树状结构读入内存 + JsonNode resultNode; + queue.add(rootNode); + while (!queue.isEmpty()) {//深度优先遍历算法 + var pollNode = queue.poll(); + var iterator = pollNode.fields(); + while (iterator.hasNext()) {//循环遍历子节点下的信息 + var node = iterator.next(); + var filed = node.getKey(); + var value = node.getValue().asText(); + jsonMap.put(filed, value); + } + } + return jsonMap; + } catch (IOException e) { + throw new RuntimeException(StringUtils.format("将json字符串[json:{}]转换为jsonMap异常[error:{}]", json, ExceptionUtils.getStackTrace(e))); + } + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java new file mode 100644 index 00000000..72bef403 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/ReflectionUtils.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + +import com.zfoo.protocol.exception.POJOException; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * 反射工具类 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class ReflectionUtils { + + /** + * 如果field符合FieldFilter过滤条件,则执行回调方法 + * + * @param field 属性 + * @param fieldFilter 属性过滤器 + * @param fieldCallback 属性回调方法 + */ + public static void filterField(Field field, Predicate fieldFilter, Consumer fieldCallback) { + if (fieldFilter != null && !fieldFilter.test(field)) { + return; + } + fieldCallback.accept(field); + } + + /** + * 将clazz通过filter过滤,过滤后的field执行callback方法 + *

+ * Invoke the given callback on all fields in the target class, going up the + * class hierarchy to get all declared fields. + *

+ * + * @param clazz the target class to analyze + * @param fieldCallback the callback to invoke for each field + * @param fieldFilter the matches that determines the fields to apply the callback to + */ + public static void filterFieldsInClass(Class clazz, Predicate fieldFilter, Consumer fieldCallback) { + // Keep backing up the inheritance hierarchy. + Class targetClass = clazz; + do { + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + ReflectionUtils.filterField(field, fieldFilter, fieldCallback); + } + targetClass = targetClass.getSuperclass(); + } while (targetClass != null && targetClass != Object.class); + } + + public static boolean isPOJOClass(Class clazz) { + return clazz.getSuperclass().equals(Object.class); + } + + public static void assertIsPOJOClass(Class clazz) { + if (!isPOJOClass(clazz)) { + throw new POJOException(clazz.getName() + "不是简单的javabean"); + } + } + + /** + * 从一个指定的POJO的Class中获得具有指定注解的Field,只获取子类的Field,不获取父类的Field + * + * @param clazz 指定的Class + * @param annotation 指定注解的Class + * @return 数组,可能长度为0 + */ + public static Field[] getFieldsByAnnoInPOJOClass(Class clazz, Class annotation) { + List list = new ArrayList(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(annotation)) { + list.add(field); + } + } + return list.toArray(new Field[list.size()]); + } + + public static Field getFieldByNameInPOJOClass(Class clazz, String fieldName) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + throw new IllegalStateException(StringUtils.format("[class:{}] has no [field:{}] exception", clazz, fieldName), e); + } + } + + /** + * 从一个Class中获得具有指定注解的Method,只获取子类的Method,不获取父类的Method + * + * @param clazz 指定的Class + * @param annotation 指定注解的Class + * @return 数组,可能长度为0 + */ + public static Method[] getMethodsByAnnoInPOJOClass(Class clazz, Class annotation) { + List list = new ArrayList<>(); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(annotation)) { + list.add(method); + } + } + return list.toArray(new Method[list.size()]); + } + + public static Method[] getMethodsByNameInPOJOClass(Class clazz, String methodName) { + List list = new ArrayList<>(); + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + if (method.getName().equalsIgnoreCase(methodName)) { + list.add(method); + } + } + return list.toArray(new Method[list.size()]); + } + + /** + * Attempt to get all Methods on the supplied class. + * Searches all superclasses up to {@code Object}. + * + * @param clazz the class to introspect + * @return 数组,可能长度为0 + */ + public static Method[] getAllMethods(Class clazz) { + AssertionUtils.notNull(clazz, "Class must not be null"); + List list = new ArrayList<>(); + Class superClazz = clazz; + while (superClazz != null) { + Method[] methods = superClazz.getDeclaredMethods(); + Collections.addAll(list, methods); + superClazz = superClazz.getSuperclass(); + } + return list.toArray(new Method[list.size()]); + } + //*************************************操作Class********************************* + + public static T newInstance(Class clazz) { + try { + return newInstance(clazz.getDeclaredConstructor()); + } catch (NoSuchMethodException e) { + throw new RuntimeException(StringUtils.format("[{}]无法被实例化", clazz)); + } + } + + public static T newInstance(Constructor constructor) { + try { + return constructor.newInstance(); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("[{}]无法被实例化", constructor)); + } + } + + + /** + * 等于{@link Field#get(Object)} + *

+ * In accordance with {@link Field#get(Object)} + * semantics, the returned value is automatically wrapped if the underlying field + * has a primitive type. + *

+ * + * @param field the field to get + * @param target the target object from which to get the field + * @return the field's current value + */ + public static Object getField(Field field, Object target) { + try { + return field.get(target); + } catch (Exception e) { + throw new IllegalStateException("Unexpected reflection exception - " + e.getClass().getName() + ": " + e.getMessage()); + } + } + + /** + * 等于{@link Field#set(Object, Object)} + *

+ * In accordance with {@link Field#set(Object, Object)} semantics, the new value + * is automatically unwrapped if the underlying field has a primitive type. + *

+ * + * @param field the field to set + * @param target the target object on which to set the field + * @param value the value to set; may be {@code null} + */ + public static void setField(Field field, Object target, Object value) { + try { + field.set(target, value); + } catch (Exception e) { + throw new IllegalStateException("Unexpected reflection exception - " + e.getClass().getName() + ": " + e.getMessage()); + } + } + + + /** + * Invoke the specified {@link Method} against the supplied target object with the + * supplied arguments. The target object can be {@code null} when invoking a + * static {@link Method}. + * + * @param target the target object to invoke the method on + * @param method the method to invoke + * @param args the invocation arguments (may be {@code null}) + * @return the invocation result, if any + */ + public static Object invokeMethod(Object target, Method method, Object... args) { + try { + return method.invoke(target, args); + } catch (Exception e) { + throw new IllegalStateException("Unexpected reflection exception - " + e.getClass().getName() + ": " + e.getMessage()); + } + } + + /** + * 让私有变量可访问,在必要的情况下调用 + *

+ * Make the given field accessible, explicitly setting it accessible if necessary. + *

+ * + * @param field the field to make accessible + * @see Field#setAccessible + */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || Modifier.isFinal(field.getModifiers()))) { + field.setAccessible(true); + } + } + + /** + * Make the given method accessible + * + * @param method the method to make accessible + * @see Method#setAccessible + */ + public static void makeAccessible(Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))) { + method.setAccessible(true); + } + } + + /** + * Make the given constructor accessible, explicitly setting it accessible + * if necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * + * @param constructor the constructor to make accessible + * @see Constructor#setAccessible + */ + public static void makeAccessible(Constructor constructor) { + if ((!Modifier.isPublic(constructor.getModifiers()) || !Modifier.isPublic(constructor.getDeclaringClass().getModifiers()))) { + constructor.setAccessible(true); + } + } + + + public static String fieldToGetMethod(Class clazz, Field field) { + var fieldName = field.getName(); + + if (fieldName.startsWith("is")) { + throw new RuntimeException(StringUtils.format("field:[{}] can not be name of 'is' in class:[{}]", field.getName(), clazz.getCanonicalName())); + } + + var methodName = "get" + StringUtils.capitalize(fieldName); + + try { + clazz.getDeclaredMethod(methodName, null); + return methodName; + } catch (NoSuchMethodException e) { + } + + methodName = "is" + StringUtils.capitalize(fieldName); + try { + clazz.getDeclaredMethod(methodName, null); + return methodName; + } catch (NoSuchMethodException e) { + throw new RuntimeException(StringUtils.format("field:[{}] has no getMethod or isMethod in class:[{}]", field.getName(), clazz.getCanonicalName())); + } + } + + public static String fieldToSetMethod(Class clazz, Field field) { + var fieldName = field.getName(); + + if (fieldName.startsWith("is")) { + throw new RuntimeException(StringUtils.format("field:[{}] can not be name of 'is' in class:[{}]", field.getName(), clazz.getCanonicalName())); + } + + var methodName = "set" + StringUtils.capitalize(fieldName); + try { + clazz.getDeclaredMethod(methodName, field.getType()); + return methodName; + } catch (NoSuchMethodException e) { + throw new RuntimeException(StringUtils.format("field:[{}] has no setMethod in class:[{}]", field.getName(), clazz.getCanonicalName())); + } + } + +} + diff --git a/protocol/src/main/java/com/zfoo/protocol/util/StringUtils.java b/protocol/src/main/java/com/zfoo/protocol/util/StringUtils.java new file mode 100644 index 00000000..83b7a79c --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/util/StringUtils.java @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.protocol.util; + +import com.zfoo.protocol.collection.CollectionUtils; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * Operations on {@link String} that are null safe and thread safe. + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class StringUtils { + + public static final String EMPTY = ""; + public static final byte[] EMPTY_BYTES = new byte[0]; + public static final String[] EMPTY_ARRAY = new String[0]; + + public static final String SPACE = " "; + + public static final String TAB = " "; + + + public static final String COMMA = ",";// [com·ma || 'kɒmə] n. 逗点; 逗号 + public static final String COMMA_REGEX = ",|,"; + + public static final String PERIOD = ".";// 句号 + public static final String PERIOD_REGEX = "\\."; + + public static final String LEFT_SQUARE_BRACKET = "[";//左方括号 + + public static final String RIGHT_SQUARE_BRACKET = "]";//右方括号 + + public static final String COLON = ":";//冒号[co·lon || 'kəʊlən] + public static final String COLON_REGEX = ":|:"; + + public static final String SEMICOLON = ";";//分号['semi'kәulәn] + public static final String SEMICOLON_REGEX = ";|;"; + + public static final String QUOTATION_MARK = "\"";//引号[quo·ta·tion || kwəʊ'teɪʃn] + + public static final String ELLIPSIS = "...";//省略号 + + public static final String EXCLAMATION_POINT = "!";//感叹号 + + public static final String DASH = "-";//破折号 + + public static final String QUESTION_MARK = "?";//问号 + + public static final String HYPHEN = "-";//连接号,连接号与破折号的区别是,连接号的两头不用空格 + + public static final String SLASH = "/";//斜线号 + + public static final String BACK_SLASH = "\\";//反斜线号 + + public static final String VERTICAL_BAR = "|";// 竖线 + public static final String VERTICAL_BAR_REGEX = "\\|"; + + public static final String SHARP = "#"; + public static final String SHARP_REGEX = "\\#"; + + public static final String DOLLAR = "$";// 美元符号 + + public static final String EMPTY_JSON = "{}"; + + public static final String MULTIPLE_HYPHENS = "-----------------------------------------------------------------------"; + + + public static final int INDEX_NOT_FOUND = -1;//Represents a failed index search. + + public static final String DEFAULT_CHARSET_NAME = "UTF-8"; + public static final Charset DEFAULT_CHARSET = Charset.forName(DEFAULT_CHARSET_NAME); + + /** + * 用于随机选的数字 + */ + public static final String ARAB_NUMBER = "0123456789"; + /** + * 用于随机选的字符 + */ + public static final String ENGLISH_CHAR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static final Set ENGLISH_SET = Set.of( + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' + ); + + + public static final Set STOP_WORD = Set.of( + ' ', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '/', '[', '{', ']', '}' + , '\\', '|', ';', ':', '\'', '"', ',', '<', '.', '>', '?', + '·', '!', '¥', ',', '。', '、', 'ˇ', '#', '%', '&', '(', ')', '.', '/', ':', ';', '<', '=', '>', '?', '〔', '〕', '\', '_', '{', '|', '}', + '—', '~', '‖', '…', '‘', '’', '“', '”', '〈', '〉', '《', '》', '「', '」', '『', + '』', '〖', '〗', '【', '】', '±', '+', '-', '×', '÷', '∧', '∨', '∏', '∪', '∩', '∈', '√', '⊥', '⊙', '∫', + '∮', '≡', '≌', '≈', '∽', '∝', '≠', '≮', '≯', '≤', '≥', '∞', '∶', '∵', '∴', '∷', '♂', '♀', '°', '′', '〃', + '℃', '$', '¤', '¢', '£', '‰', '§', '☆', '★', '〇', '○', '●', '◎', '◇', '◆', '□', '■', '△', '▽', '⊿', '▲', + '▼', '◣', '◤', '◢', '◥', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▉', '▊', '▋', '▌', '▍', '▎', '▏', '▓', + '※', '→', '←', '↑', '↓', '↖', '↗', '↘', '↙', '〓', + '①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', + '⒈', '⒉', '⒊', '⒋', '⒌', '⒍', '⒎', '⒏', '⒐', '⒑', '⒒', '⒓', '⒔', '⒕', '⒖', '⒗', '⒘', '⒙', '⒚', '⒛', '⑴', '⑵', '⑶', '⑷', '⑸', '⑹', '⑺', '⑻', '⑼', '⑽', '⑾', '⑿', '⒀', '⒁', '⒂', '⒃', '⒄', '⒅', '⒆', '⒇', + 'Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ', 'Ⅴ', 'Ⅵ', 'Ⅶ', 'Ⅷ', 'Ⅸ', 'Ⅹ', 'Ⅺ', 'Ⅻ', + 'ⅰ', 'ⅱ', 'ⅲ', 'ⅳ', 'ⅴ', 'ⅵ', 'ⅶ', 'ⅷ', 'ⅸ', 'ⅹ', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'Ρ', '∑', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', + 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', '^', '﹊', '﹍', '╭', '╮', '╰', '╯', + '々', '', '@', '*', '卐', '㎎', '㎏', '㎜', '㎝', '㎞', '㎡', '㏄', '㏎', '㏑', '㏒', '㏕', + '´', '﹏'); + + + // Empty checks + //----------------------------------------------------------------------- + + /** + * Checks if a CharSequence is empty ("") or null. + *
+     * StringUtils.isEmpty(null)      = true
+     * StringUtils.isEmpty("")        = true
+     * StringUtils.isEmpty(" ")       = false
+     * StringUtils.isEmpty("bob")     = false
+     * 
+ * It no longer trims the CharSequence. + * That functionality is available in isBlank(). + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is empty or null + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + + /** + * StringUtils.isBlank(null)=true + * StringUtils.isBlank("")=true + * StringUtils.isBlank(" ")=true + * StringUtils.isBlank(" b ")=false + * + * @param cs 要检查的字符串 + * @return 是否为空的字符串 + */ + public static boolean isBlank(final CharSequence cs) { + if (isEmpty(cs)) { + return true; + } + int length = cs.length(); + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + return true; + } + + public static String trim(String str) { + if (StringUtils.isBlank(str)) { + return EMPTY; + } + return str.trim(); + } + + // Case conversion + //----------------------------------------------------------------------- + + /** + * 首字母小写(capitalize ['kæpɪtlaɪz] vt.以大写字母写,【根】cap=head(头)) + * + * @param str 被转换的字符串,可以为null + * @return 首字母小写的字符串,如果str为null,则返回null + */ + public static String uncapitalize(String str) { + return changeFirstCharacterCase(str, false); + } + + /** + * Capitalize a {@code String}, changing the first letter to + * upper case as per {@link Character#toUpperCase(char)}. + * No other letters are changed. + * + * @param str the {@code String} to capitalize, may be {@code null} + * @return the capitalized {@code String}, or {@code null} if the supplied + * string is {@code null} + */ + public static String capitalize(String str) { + return changeFirstCharacterCase(str, true); + } + + private static String changeFirstCharacterCase(String str, boolean capitalize) { + if (isEmpty(str)) { + return str; + } + StringBuilder sb = new StringBuilder(str.length()); + if (capitalize) { + sb.append(Character.toUpperCase(str.charAt(0))); + } else { + sb.append(Character.toLowerCase(str.charAt(0))); + } + sb.append(str.substring(1)); + return sb.toString(); + } + + + // SubStringAfter/SubStringBefore + //----------------------------------------------------------------------- + + /** + * 从第一个分隔符开始分割 + *

Gets the substring before the first occurrence of a separator. + * The separator is not returned.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the input string.

+ * + *

If nothing is found, the string input is returned.

+ * + *
+     * StringUtils.substringBefore(null, *)      = null
+     * StringUtils.substringBefore("", *)        = ""
+     * StringUtils.substringBefore("abc", "a")   = ""
+     * StringUtils.substringBefore("abcba", "b") = "a"
+     * StringUtils.substringBefore("abc", "c")   = "ab"
+     * StringUtils.substringBefore("abc", "d")   = "abc"
+     * StringUtils.substringBefore("abc", "")    = ""
+     * StringUtils.substringBefore("abc", null)  = "abc"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring before the first occurrence of the separator, + * {@code null} if null String input + */ + public static String substringBeforeFirst(final String str, final String separator) { + if (isEmpty(str) || separator == null) { + return str; + } + if (separator.isEmpty()) { + return EMPTY; + } + final int pos = str.indexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return str; + } + return str.substring(0, pos); + } + + public static String substringBeforeLast(final String str, final String separator) { + if (isEmpty(str) || isEmpty(separator)) { + return str; + } + final int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return str; + } + return str.substring(0, pos); + } + + /** + * 从第一个分隔符开始分割 + *

Gets the substring after the first occurrence of a separator. + * The separator is not returned.

+ * + *

A {@code null} string input will return {@code null}. + * An empty ("") string input will return the empty string. + * A {@code null} separator will return the empty string if the + * input string is not {@code null}.

+ * + *

If nothing is found, the empty string is returned.

+ * + *
+     * StringUtils.substringAfter(null, *)      = null
+     * StringUtils.substringAfter("", *)        = ""
+     * StringUtils.substringAfter(*, null)      = ""
+     * StringUtils.substringAfter("abc", "a")   = "bc"
+     * StringUtils.substringAfter("abcba", "b") = "cba"
+     * StringUtils.substringAfter("abc", "c")   = ""
+     * StringUtils.substringAfter("abc", "d")   = ""
+     * StringUtils.substringAfter("abc", "")    = "abc"
+     * 
+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the first occurrence of the separator, + * {@code null} if null String input + */ + public static String substringAfterFirst(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (separator == null) { + return EMPTY; + } + final int pos = str.indexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return EMPTY; + } + return str.substring(pos + separator.length()); + } + + /** + *

Gets the substring after the last occurrence of a separator. + * The separator is not returned.

+ * + * @param str the String to get a substring from, may be null + * @param separator the String to search for, may be null + * @return the substring after the last occurrence of the separator, + * {@code null} if null String input + */ + public static String substringAfterLast(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (isEmpty(separator)) { + return EMPTY; + } + final int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) { + return EMPTY; + } + return str.substring(pos + separator.length()); + } + + + // Replacing + //----------------------------------------------------------------------- + + /** + * Replaces each substring of the source String that matches the given regular expression with the given + * replacement using the {@link Pattern#DOTALL} option. + *

+ * 在 DOTALL 模式中,表达式 .可以匹配任何字符,包括行结束符。默认情况下,此表达式不匹配行结束符。 + * + * @param source the source string + * @param regex the regular expression to which this string is to be matched + * @param replacement the string to be substituted for each match + * @return The resulting {@code String} + */ + public static String replacePattern(final String source, final String regex, final String replacement) { + return Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement); + } + + + // Joining + //----------------------------------------------------------------------- + + public static String defaultString(final String str, final String defaultStr) { + return str == null ? defaultStr : str; + } + + public static byte[] bytes(final String str) { + try { + return str.getBytes(DEFAULT_CHARSET); + } catch (Exception e) { + return EMPTY_BYTES; + } + } + + public static String bytesToString(final byte[] bytes) { + try { + return new String(bytes, DEFAULT_CHARSET); + } catch (Exception e) { + return EMPTY; + } + } + + /** + * Joins the elements of the provided varargs into a single String containing the provided elements. + * No delimiter is added before or after the list. + * null elements and separator are treated as empty Strings (""). + * + *

+     * StringUtils.joinWith(",", {"a", "b"})        = "a,b"
+     * StringUtils.joinWith(",", {"a", "b",""})     = "a,b,"
+     * StringUtils.joinWith(",", {"a", null, "b"})  = "a,,b"
+     * StringUtils.joinWith(null, {"a", "b"})       = "ab"
+     * 
+ * + * @param separator the separator character to use, null treated as "" + * @param objects the varargs providing the values to join together. {@code null} elements are treated as "" + * @return the joined String. + * @throws java.lang.IllegalArgumentException if a null varargs is provided + */ + public static String joinWith(final String separator, final Object... objects) { + if (objects == null) { + throw new IllegalArgumentException("Object varargs must not be null"); + } + + final String sanitizedSeparator = defaultString(separator, EMPTY); + + final StringBuilder result = new StringBuilder(); + + final Iterator iterator = Arrays.asList(objects).iterator(); + while (iterator.hasNext()) { + final String value = Objects.toString(iterator.next(), ""); + result.append(value); + + if (iterator.hasNext()) { + result.append(sanitizedSeparator); + } + } + + return result.toString(); + } + + /** + * 格式化字符串 + * 此方法只是简单将占位符 {} 按照顺序替换为参数 + * 例: + * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b + * + * @param template 字符串模板 + * @param args 参数列表 + * @return 结果 + */ + public static String format(final String template, final Object... args) { + if (isBlank(template) || CollectionUtils.isEmpty(args)) { + return template; + } + + // 初始化定义好的长度以获得更好的性能 + var builder = new StringBuilder(template.length() + 50); + + // 记录已经处理到的位置 + var readIndex = 0; + for (int i = 0; i < args.length; i++) { + // 占位符所在位置 + var placeholderIndex = template.indexOf(EMPTY_JSON, readIndex); + // 剩余部分无占位符 + if (placeholderIndex == -1) { + // 不带占位符的模板直接返回 + if (readIndex == 0) { + return template; + } + break; + } + + builder.append(template, readIndex, placeholderIndex); + builder.append(args[i]); + readIndex = placeholderIndex + 2; + } + + // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + builder.append(template, readIndex, template.length()); + return builder.toString(); + } + + public static boolean isEnglishChar(char ch) { + return ENGLISH_SET.contains(ch); + } + + /** + * 判断指定的词是否是不处理的词。 + * 如果参数为空,则返回true,因为空也属于不处理的字符。 + * + * @param ch 指定的词 + * @return 是否是不处理的词 + */ + public static boolean isStopChar(char ch) { + return Character.isWhitespace(ch) || STOP_WORD.contains(ch); + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/xml/XmlModuleDefinition.java b/protocol/src/main/java/com/zfoo/protocol/xml/XmlModuleDefinition.java new file mode 100644 index 00000000..a3985b02 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/xml/XmlModuleDefinition.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.xml; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@JsonPropertyOrder({"name", "minId", "maxId", "version"}) +public class XmlModuleDefinition { + + @JacksonXmlProperty(isAttribute = true, localName = "id") + private byte id; + + @JacksonXmlProperty(isAttribute = true, localName = "name") + private String name; + + @JacksonXmlProperty(isAttribute = true, localName = "minId") + private short minId; + + @JacksonXmlProperty(isAttribute = true, localName = "maxId") + private short maxId; + + @JacksonXmlProperty(isAttribute = true, localName = "version") + private String version; + + @JacksonXmlProperty(localName = "protocol") + @JacksonXmlElementWrapper(useWrapping = false) + private List protocols; + + public byte getId() { + return id; + } + + public String getName() { + return name; + } + + public short getMinId() { + return minId; + } + + public short getMaxId() { + return maxId; + } + + public String getVersion() { + return version; + } + + public List getProtocols() { + return protocols; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocolDefinition.java b/protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocolDefinition.java new file mode 100644 index 00000000..ba503284 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocolDefinition.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.xml; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; + +@JsonPropertyOrder({"id", "location", "enhance"}) +public class XmlProtocolDefinition { + + @JacksonXmlProperty(isAttribute = true, localName = "id") + private short id; + + @JacksonXmlProperty(isAttribute = true, localName = "location") + private String location; + + @JacksonXmlProperty(isAttribute = true, localName = "enhance") + private boolean enhance = true; + + + public short getId() { + return id; + } + + public void setId(short id) { + this.id = id; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public boolean isEnhance() { + return enhance; + } + + public void setEnhance(boolean enhance) { + this.enhance = enhance; + } +} diff --git a/protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocols.java b/protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocols.java new file mode 100644 index 00000000..7b4d8979 --- /dev/null +++ b/protocol/src/main/java/com/zfoo/protocol/xml/XmlProtocols.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.xml; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +import java.util.List; + +@JsonPropertyOrder({"author", "modules"}) +@JacksonXmlRootElement(localName = "protocols") +public class XmlProtocols { + + @JacksonXmlProperty(isAttribute = true, localName = "author") + private String author; + + @JacksonXmlProperty(localName = "module") + @JacksonXmlElementWrapper(useWrapping = false) + private List modules; + + public String getAuthor() { + return author; + } + + public List getModules() { + return modules; + } +} diff --git a/protocol/src/main/resources/cs/Buffer/BigEndianByteBuffer.cs b/protocol/src/main/resources/cs/Buffer/BigEndianByteBuffer.cs new file mode 100644 index 00000000..b394c899 --- /dev/null +++ b/protocol/src/main/resources/cs/Buffer/BigEndianByteBuffer.cs @@ -0,0 +1,74 @@ +namespace CsProtocol.Buffer +{ + public class BigEndianByteBuffer : ByteBuffer + { + // fast int to byte[] conversion and vice versa + // -> test with 100k conversions: + // BitConverter.GetBytes(ushort): 144ms + // bit shifting: 11ms + // -> 10x speed improvement makes this optimization actually worth it + // -> this way we don't need to allocate BinaryWriter/Reader either + // -> 4 bytes because some people may want to send messages larger than + // 64K bytes + // => big endian is standard for network transmissions, and necessary + // for compatibility with erlang + public static byte[] IntToBytesBigEndian(int value) + { + return new byte[] + { + (byte) (value >> 24), + (byte) (value >> 16), + (byte) (value >> 8), + (byte) value + }; + } + + public static int BytesToIntBigEndian(byte[] bytes) + { + return (bytes[0] << 24) | + (bytes[1] << 16) | + (bytes[2] << 8) | + bytes[3]; + } + + public override void WriteShort(short value) + { + WriteBytes(GetBytes(value)); + } + + public override short ReadShort() + { + return GetInt16(ReadBytes(2)); + } + + public override void WriteRawInt(int value) + { + WriteBytes(IntToBytesBigEndian(value)); + } + + public override int ReadRawInt() + { + return BytesToIntBigEndian(ReadBytes(4)); + } + + public override void WriteFloat(float value) + { + WriteBytes(GetBytes(value)); + } + + public override float ReadFloat() + { + return GetSingle(ReadBytes(4)); + } + + public override void WriteDouble(double value) + { + WriteBytes(GetBytes(value)); + } + + public override double ReadDouble() + { + return GetDouble(ReadBytes(8)); + } + } +} \ No newline at end of file diff --git a/protocol/src/main/resources/cs/Buffer/ByteBuffer.cs b/protocol/src/main/resources/cs/Buffer/ByteBuffer.cs new file mode 100644 index 00000000..faf82c58 --- /dev/null +++ b/protocol/src/main/resources/cs/Buffer/ByteBuffer.cs @@ -0,0 +1,561 @@ +using System; +using System.Collections.Generic; +using System.Text; + +// CSharp字节保存在内存的低地址中是根据操作系统来的,所以有可能是大端模式,也有可能是小端模式 +// 右移操作>>是带符号右移 +namespace CsProtocol.Buffer +{ + public abstract class ByteBuffer + { + private static readonly Queue byteBufferQueue = new Queue(); + + private static readonly int INIT_SIZE = 128; + private static readonly int MAX_SIZE = 655537; + + private byte[] buffer = new byte[INIT_SIZE]; + private int writeOffset = 0; + private int readOffset = 0; + + public static ByteBuffer ValueOf() + { + lock (byteBufferQueue) + { + if (byteBufferQueue.Count > 0) + { + return byteBufferQueue.Dequeue(); + } + } + + if (BitConverter.IsLittleEndian) + { + return new LittleEndianByteBuffer(); + } + + return new BigEndianByteBuffer(); + } + + public void Clear() + { + lock (byteBufferQueue) + { + if (byteBufferQueue.Contains(this)) + { + throw new Exception("The reference has been released."); + } + + byteBufferQueue.Enqueue(this); + writeOffset = 0; + readOffset = 0; + } + } + + // -------------------------------------------------get/set------------------------------------------------- + public int WriteOffset() + { + return writeOffset; + } + + public void SetWriteOffset(int writeIndex) + { + if (writeOffset > buffer.Length) + { + throw new Exception("writeIndex[" + writeIndex + "] out of bounds exception: readerIndex: " + readOffset + + ", writerIndex: " + writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + buffer.Length); + } + + writeOffset = writeIndex; + } + + public void SetReadOffset(int readIndex) + { + if (readIndex > writeOffset) + { + throw new Exception("readIndex[" + readIndex + "] out of bounds exception: readerIndex: " + readOffset + + ", writerIndex: " + writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + buffer.Length); + } + + readOffset = readIndex; + } + + public byte[] ToBytes() + { + var bytes = new byte[writeOffset]; + Array.Copy(buffer, 0, bytes, 0, writeOffset); + return bytes; + } + + + // -------------------------------------------------write/read------------------------------------------------- + public void WriteBool(bool value) + { + EnsureCapacity(1); + buffer[writeOffset] = value ? (byte) 1 : (byte) 0; + writeOffset++; + } + + public bool ReadBool() + { + var byteValue = buffer[readOffset]; + readOffset++; + return byteValue == 1; + } + + public void WriteByte(byte value) + { + EnsureCapacity(1); + buffer[writeOffset] = value; + writeOffset++; + } + + public byte ReadByte() + { + var byteValue = buffer[readOffset]; + readOffset++; + return byteValue; + } + + + public int GetCapacity() + { + return buffer.Length - writeOffset; + } + + public void EnsureCapacity(int capacity) + { + while (capacity - GetCapacity() > 0) + { + var newSize = buffer.Length * 2; + if (newSize > MAX_SIZE) + { + throw new Exception("Bytebuf max size is [655537], out of memory error"); + } + + var newBytes = new byte[newSize]; + Array.Copy(buffer, 0, newBytes, 0, buffer.Length); + this.buffer = newBytes; + } + } + + public void WriteBytes(byte[] bytes) + { + EnsureCapacity(bytes.Length); + var length = bytes.Length; + Array.Copy(bytes, 0, buffer, writeOffset, length); + writeOffset += length; + } + + public byte[] ReadBytes(int count) + { + var bytes = new byte[count]; + Array.Copy(buffer, readOffset, bytes, 0, count); + readOffset += count; + return bytes; + } + + public abstract void WriteShort(short value); + public abstract short ReadShort(); + + + // *******************************************int*************************************************** + public void WriteInt(int intValue) + { + // 用Zigzag算法压缩int和long的值 + // 再用Varint紧凑算法表示数字的有效位 + uint value = (uint) ((intValue << 1) ^ (intValue >> 31)); + + if (value >> 7 == 0) + { + WriteByte((byte) value); + return; + } + + if (value >> 14 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) (value >> 7)); + return; + } + + if (value >> 21 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) (value >> 14)); + return; + } + + if (value >> 28 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) (value >> 21)); + return; + } + + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) (value >> 28)); + } + + public int ReadInt() + { + uint b = ReadByte(); + uint value = b & 0x7F; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 28; + } + } + } + } + + return (int) (value >> 1) ^ -((int) (value) & 1); + } + + // 写入没有压缩的int + public abstract void WriteRawInt(int value); + + // 读取没有压缩的int + public abstract int ReadRawInt(); + + // *******************************************long************************************************** + public long ReadLong() + { + ulong b = ReadByte(); + ulong value = b & 0x7F; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 28; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 35; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 42; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 49; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= b << 56; + } + } + } + } + } + } + } + } + + return (long) (value >> 1) ^ -((long) value & 1); + } + + public void WriteLong(long longValue) + { + ulong value = (ulong) ((longValue << 1) ^ (longValue >> 63)); + + if (value >> 7 == 0) + { + WriteByte((byte) value); + return; + } + + if (value >> 14 == 0) + { + WriteByte((byte) ((value & 0x7F) | 0x80)); + WriteByte((byte) (value >> 7)); + return; + } + + if (value >> 21 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) (value >> 14)); + return; + } + + if ((value >> 28) == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) (value >> 21)); + return; + } + + if (value >> 35 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) (value >> 28)); + return; + } + + if (value >> 42 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) (value >> 35)); + return; + } + + if (value >> 49 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) ((value >> 35) | 0x80)); + WriteByte((byte) (value >> 42)); + return; + } + + if ((value >> 56) == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) ((value >> 35) | 0x80)); + WriteByte((byte) ((value >> 42) | 0x80)); + WriteByte((byte) (value >> 49)); + return; + } + + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) ((value >> 35) | 0x80)); + WriteByte((byte) ((value >> 42) | 0x80)); + WriteByte((byte) ((value >> 49) | 0x80)); + WriteByte((byte) (value >> 56)); + } + + + // *******************************************float*************************************************** + public abstract void WriteFloat(float value); + public abstract float ReadFloat(); + + // *******************************************double*************************************************** + public abstract void WriteDouble(double value); + public abstract double ReadDouble(); + + // *******************************************char*************************************************** + public char ReadChar() + { + // need check + var str = ReadString(); + return string.IsNullOrEmpty(str) ? char.MinValue : str[0]; + } + + public void WriteChar(char value) + { + // need check + WriteString(new string(value, 1)); + } + + // *******************************************String*************************************************** + + public void WriteString(string value) + { + if (string.IsNullOrEmpty(value)) + { + WriteInt(0); + return; + } + + byte[] strBytes = GetBytes(value); + + if (strBytes == null || strBytes.Length <= 0) + { + WriteInt(0); + return; + } + + WriteInt(strBytes.Length); + WriteBytes(strBytes); + } + + public string ReadString() + { + int length = ReadInt(); + if (length <= 0) + { + return string.Empty; + } + + byte[] bytes = new byte[length]; + for (int i = 0; i < length; i++) + { + bytes[i] = ReadByte(); + } + + string str = GetString(bytes); + return str; + } + + + // -------------------------------------------------Converter------------------------------------------------- + private static readonly byte[] EMPTY_BYTE_ARRAY = new byte[] { }; + + /// + /// 以字节数组的形式返回指定的 16 位有符号整数值。 + /// + /// 要转换的数字。 + /// 长度为 2 的字节数组。 + public byte[] GetBytes(short value) + { + return BitConverter.GetBytes(value); + } + + /// + /// 返回由字节数组中前两个字节转换来的 16 位有符号整数。 + /// + /// 字节数组。 + /// 由两个字节构成的 16 位有符号整数。 + public short GetInt16(byte[] value) + { + return BitConverter.ToInt16(value, 0); + } + + + /// + /// 以字节数组的形式返回指定的 32 位有符号整数值。 + /// + /// 要转换的数字。 + /// 长度为 4 的字节数组。 + public byte[] GetBytes(int value) + { + return BitConverter.GetBytes(value); + } + + + /// + /// 返回由字节数组中前四个字节转换来的 32 位有符号整数。 + /// + /// 字节数组。 + /// 由四个字节构成的 32 位有符号整数。 + public int GetInt32(byte[] value) + { + return BitConverter.ToInt32(value, 0); + } + + + /// + /// 以字节数组的形式返回指定的单精度浮点值。 + /// + /// 要转换的数字。 + /// 长度为 4 的字节数组。 + public byte[] GetBytes(float value) + { + return BitConverter.GetBytes(value); + } + + /// + /// 返回由字节数组中前四个字节转换来的单精度浮点数。 + /// + /// 字节数组。 + /// 由四个字节构成的单精度浮点数。 + public float GetSingle(byte[] value) + { + return BitConverter.ToSingle(value, 0); + } + + /// + /// 以字节数组的形式返回指定的双精度浮点值。 + /// + /// 要转换的数字。 + /// 长度为 8 的字节数组。 + public byte[] GetBytes(double value) + { + return BitConverter.GetBytes(value); + } + + /// + /// 返回由字节数组中前八个字节转换来的双精度浮点数。 + /// + /// 字节数组。 + /// 由八个字节构成的双精度浮点数。 + public double GetDouble(byte[] value) + { + return BitConverter.ToDouble(value, 0); + } + + + /// + /// 以 UTF-8 字节数组的形式返回指定的字符串。 + /// + /// 要转换的字符串。 + /// UTF-8 字节数组。 + public byte[] GetBytes(string value) + { + if (value == null) + { + return EMPTY_BYTE_ARRAY; + } + + return Encoding.UTF8.GetBytes(value); + } + + /// + /// 返回由 UTF-8 字节数组转换来的字符串。 + /// + /// UTF-8 字节数组。 + /// 字符串。 + public string GetString(byte[] value) + { + if (value == null) + { + return string.Empty; + } + + return Encoding.UTF8.GetString(value, 0, value.Length); + } + } +} \ No newline at end of file diff --git a/protocol/src/main/resources/cs/Buffer/LittleEndianByteBuffer.cs b/protocol/src/main/resources/cs/Buffer/LittleEndianByteBuffer.cs new file mode 100644 index 00000000..38c42738 --- /dev/null +++ b/protocol/src/main/resources/cs/Buffer/LittleEndianByteBuffer.cs @@ -0,0 +1,56 @@ +using System; + +namespace CsProtocol.Buffer +{ + public class LittleEndianByteBuffer : ByteBuffer + { + /** + * 翻转字节数组,如果本地字节序列为低字节序列,则进行翻转以转换为高字节序列 + */ + private static byte[] reverse(byte[] bytes) + { + Array.Reverse(bytes); + return bytes; + } + + public override void WriteShort(short value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override short ReadShort() + { + return GetInt16(reverse(ReadBytes(2))); + } + + public override void WriteRawInt(int value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override int ReadRawInt() + { + return GetInt32(reverse(ReadBytes(4))); + } + + public override void WriteFloat(float value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override float ReadFloat() + { + return GetSingle(reverse(ReadBytes(4))); + } + + public override void WriteDouble(double value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override double ReadDouble() + { + return GetDouble(reverse(ReadBytes(8))); + } + } +} \ No newline at end of file diff --git a/protocol/src/main/resources/cs/IPacket.cs b/protocol/src/main/resources/cs/IPacket.cs new file mode 100644 index 00000000..c4f5c8c5 --- /dev/null +++ b/protocol/src/main/resources/cs/IPacket.cs @@ -0,0 +1,7 @@ +namespace CsProtocol.Buffer +{ + public interface IPacket + { + short ProtocolId(); + } +} \ No newline at end of file diff --git a/protocol/src/main/resources/cs/IProtocolRegistration.cs b/protocol/src/main/resources/cs/IProtocolRegistration.cs new file mode 100644 index 00000000..7c257d33 --- /dev/null +++ b/protocol/src/main/resources/cs/IProtocolRegistration.cs @@ -0,0 +1,12 @@ +namespace CsProtocol.Buffer +{ + public interface IProtocolRegistration + { + short ProtocolId(); + + void Write(ByteBuffer buffer, IPacket packet); + + IPacket Read(ByteBuffer buffer); + + } +} \ No newline at end of file diff --git a/protocol/src/main/resources/cs/ProtocolManager.cs b/protocol/src/main/resources/cs/ProtocolManager.cs new file mode 100644 index 00000000..fe33cf75 --- /dev/null +++ b/protocol/src/main/resources/cs/ProtocolManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + public class ProtocolManager + { + public static readonly short MAX_PROTOCOL_NUM = short.MaxValue; + + + private static readonly IProtocolRegistration[] protocolList = new IProtocolRegistration[MAX_PROTOCOL_NUM]; + + + public static void InitProtocol() + { + var protocolRegistrationTypeList = new List(); + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.Equals(typeof(ProtocolManager).Assembly)) + { + var results = new List(); + results.AddRange(assembly.GetTypes()); + foreach (var type in results) + { + if (type.IsClass && !type.IsAbstract && typeof(IProtocolRegistration).IsAssignableFrom(type)) + { + protocolRegistrationTypeList.Add(type); + } + } + } + } + + foreach (var protocolRegistrationType in protocolRegistrationTypeList) + { + var protocolRegistration = (IProtocolRegistration) Activator.CreateInstance(protocolRegistrationType); + protocolList[protocolRegistration.ProtocolId()] = protocolRegistration; + } + } + + public static IProtocolRegistration GetProtocol(short protocolId) + { + var protocol = protocolList[protocolId]; + if (protocol == null) + { + throw new Exception("[protocolId:" + protocolId + "]协议不存在"); + } + + return protocol; + } + + public static void Write(ByteBuffer byteBuffer, IPacket packet) + { + var protocolId = packet.ProtocolId(); + // 写入协议号 + byteBuffer.WriteShort(protocolId); + + // 写入包体 + GetProtocol(protocolId).Write(byteBuffer, packet); + } + + public static IPacket Read(ByteBuffer byteBuffer) + { + var protocolId = byteBuffer.ReadShort(); + return GetProtocol(protocolId).Read(byteBuffer); + } + } +} \ No newline at end of file diff --git a/protocol/src/main/resources/go/ByteBuffer.go b/protocol/src/main/resources/go/ByteBuffer.go new file mode 100644 index 00000000..272d4fcc --- /dev/null +++ b/protocol/src/main/resources/go/ByteBuffer.go @@ -0,0 +1,407 @@ +// TODO: go无法支持嵌套的集合类型,所以现在无法支持这套协议 +// Golang字节保存在内存的低地址中默认的使用的是大端模式,和Java一样 +// 右移操作>>是带符号右移 +package buffer + +import ( + "math" + "strconv" + "strings" +) + +const initSize int = 128 +const masSize int = 655537 + +const maxInt int = 2147483647 +const minInt int = -2147483648 + +var initArray []byte = make([]byte, initSize, initSize) + +type ByteBuffer struct { + buffer []byte + writeOffset int + readOffset int +} + +// -------------------------------------------------get/set------------------------------------------------- +func (byteBuffer *ByteBuffer) WriteOffset() int { + return byteBuffer.writeOffset +} + +func (byteBuffer *ByteBuffer) SetWriteOffset(writeIndex int) { + if writeIndex > len(byteBuffer.buffer) { + var builder strings.Builder + builder.WriteString("writeIndex[") + builder.WriteString(strconv.Itoa(writeIndex)) + builder.WriteString("] out of bounds exception: readerIndex: ") + builder.WriteString(strconv.Itoa(byteBuffer.readOffset)) + builder.WriteString(", writerIndex: ") + builder.WriteString(strconv.Itoa(byteBuffer.writeOffset)) + builder.WriteString("(expected: 0 <= readerIndex <= writerIndex <= capacity:") + builder.WriteString(strconv.Itoa(len(byteBuffer.buffer))) + builder.WriteString(")") + panic(builder.String()) + } + byteBuffer.writeOffset = writeIndex +} + +func (byteBuffer *ByteBuffer) SetReadOffset(readIndex int) { + if readIndex > byteBuffer.writeOffset { + var builder strings.Builder + builder.WriteString("readIndex[") + builder.WriteString(strconv.Itoa(readIndex)) + builder.WriteString("] out of bounds exception: readerIndex: ") + builder.WriteString(strconv.Itoa(byteBuffer.readOffset)) + builder.WriteString(", writerIndex: ") + builder.WriteString(strconv.Itoa(byteBuffer.writeOffset)) + builder.WriteString("(expected: 0 <= readerIndex <= writerIndex <= capacity:") + builder.WriteString(strconv.Itoa(len(byteBuffer.buffer))) + builder.WriteString(")") + panic(builder.String()) + } + byteBuffer.readOffset = readIndex +} + +func (byteBuffer *ByteBuffer) ToBytes() []byte { + return byteBuffer.buffer[0:byteBuffer.writeOffset] +} + +func (byteBuffer *ByteBuffer) ToString() string { + var builder strings.Builder + builder.WriteString("writeOffset:") + builder.WriteString(strconv.Itoa(byteBuffer.writeOffset)) + builder.WriteString(", readOffset:") + builder.WriteString(strconv.Itoa(byteBuffer.readOffset)) + builder.WriteString(", len:") + builder.WriteString(strconv.Itoa(len(byteBuffer.buffer))) + builder.WriteString(", cap:") + builder.WriteString(strconv.Itoa(cap(byteBuffer.buffer))) + return builder.String() +} + +// -------------------------------------------------write/read------------------------------------------------- +func (byteBuffer *ByteBuffer) GetCapacity() int { + return len(byteBuffer.buffer) - byteBuffer.writeOffset +} + +func (byteBuffer *ByteBuffer) EnsureCapacity(capacity int) { + for + { + if byteBuffer.GetCapacity()-capacity > 0 { + break + } + + byteBuffer.buffer = append(byteBuffer.buffer, initArray...) + + if len(byteBuffer.buffer) > masSize { + panic("Bytebuf max size is [655537], out of memory error") + } + } +} + +func (byteBuffer *ByteBuffer) WriteBool(value bool) { + byteBuffer.EnsureCapacity(1) + if value { + byteBuffer.buffer[byteBuffer.writeOffset] = 1 + } else { + byteBuffer.buffer[byteBuffer.writeOffset] = 0 + } + byteBuffer.writeOffset++ +} + +func (byteBuffer *ByteBuffer) ReadBool() bool { + var byteValue = byteBuffer.buffer[byteBuffer.readOffset] + byteBuffer.readOffset++ + return byteValue == 1 +} + +func (byteBuffer *ByteBuffer) WriteUByte(value byte) { + byteBuffer.EnsureCapacity(1) + byteBuffer.buffer[byteBuffer.writeOffset] = value + byteBuffer.writeOffset++ +} + +func (byteBuffer *ByteBuffer) ReadUByte() byte { + var byteValue = byteBuffer.buffer[byteBuffer.readOffset] + byteBuffer.readOffset++ + return byteValue +} + +func (byteBuffer *ByteBuffer) WriteUBytes(bytes []byte) { + var length = len(bytes) + byteBuffer.EnsureCapacity(length) + copy(byteBuffer.buffer[byteBuffer.writeOffset:], bytes) + byteBuffer.writeOffset += length +} + +func (byteBuffer *ByteBuffer) ReadUBytes(length int) []byte { + var readOffset = byteBuffer.readOffset + var endOffset = byteBuffer.readOffset + length + var bytes = byteBuffer.buffer[readOffset:endOffset] + return bytes +} + +func (byteBuffer *ByteBuffer) WriteShort(value int16) { + byteBuffer.EnsureCapacity(2) + byteBuffer.WriteUByte(byte(value >> 8)) + byteBuffer.WriteUByte(byte(value)) +} + +func (byteBuffer *ByteBuffer) ReadShort() int16 { + return (int16(byteBuffer.ReadUByte()) << 8) | int16(byteBuffer.ReadUByte()) +} + +func (byteBuffer *ByteBuffer) WriteRawInt32(intValue int32) { + byteBuffer.WriteUByte(byte(intValue >> 24)) + byteBuffer.WriteUByte(byte(intValue >> 16)) + byteBuffer.WriteUByte(byte(intValue >> 8)) + byteBuffer.WriteUByte(byte(intValue)) +} + +func (byteBuffer *ByteBuffer) ReadRawInt32() int32 { + return int32(uint32(byteBuffer.ReadUByte())<<24 | uint32(byteBuffer.ReadUByte())<<16 | uint32(byteBuffer.ReadUByte())<<8 | uint32(byteBuffer.ReadUByte())) +} + +func (byteBuffer *ByteBuffer) WriteInt(intValue int) { + if intValue < minInt || intValue > maxInt { + panic("intValue must range between minInt:-2147483648 and maxInt:2147483647") + } + byteBuffer.WriteInt32(int32(intValue)) +} + +func (byteBuffer *ByteBuffer) ReadInt() int { + return int(byteBuffer.ReadInt32()) +} + +func (byteBuffer *ByteBuffer) WriteInt32(intValue int32) { + var value uint32 = uint32(((intValue << 1) ^ (intValue >> 31))) + + if value>>7 == 0 { + byteBuffer.WriteUByte(byte(value)) + return + } + + if value>>14 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte(value >> 7)) + return + } + + if value>>21 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 14)) + return + } + + if value>>28 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 21)) + return + } + + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 21) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 28)) +} + +func (byteBuffer *ByteBuffer) ReadInt32() int32 { + var b byte = byteBuffer.ReadUByte() + var value uint32 = uint32(b & 0x7F) + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint32(b&0x7F) << 7 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint32(b&0x7F) << 14 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint32(b&0x7F) << 21 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint32(b&0x7F) << 28 + } + } + } + } + + return int32(value>>1) ^ -(int32(value & 1)) +} + +func (byteBuffer *ByteBuffer) WriteLong(longValue int64) { + var value uint64 = uint64(((longValue << 1) ^ (longValue >> 63))) + + if value>>7 == 0 { + byteBuffer.WriteUByte(byte(value)) + return + } + + if value>>14 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte(value >> 7)) + return + } + + if value>>21 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 14)) + return + } + + if value>>28 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte(value>>14) | 0x80) + byteBuffer.WriteUByte(byte(value >> 21)) + return + } + + if value>>35 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 21) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 28)) + return + } + + if value>>42 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte(value>>7) | 0x80) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 21) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 28) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 35)) + return + } + + if value>>49 == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 21) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 28) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 35) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 42)) + return + } + + if (value >> 56) == 0 { + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 21) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 28) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 35) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 42) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 49)) + return + } + + byteBuffer.WriteUByte(byte(value | 0x80)) + byteBuffer.WriteUByte(byte((value >> 7) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 14) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 21) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 28) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 35) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 42) | 0x80)) + byteBuffer.WriteUByte(byte((value >> 49) | 0x80)) + byteBuffer.WriteUByte(byte(value >> 56)) +} + +func (byteBuffer *ByteBuffer) ReadLong() int64 { + var b byte = byteBuffer.ReadUByte() + var value uint64 = uint64(b & 0x7F) + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 7 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 14 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 21 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 28 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 35 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 42 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b&0x7F) << 49 + if (b & 0x80) != 0 { + b = byteBuffer.ReadUByte() + value |= uint64(b) << 56 + } + } + } + } + } + } + } + } + + return int64(value>>1) ^ -(int64(value & 1)) +} + +func (byteBuffer *ByteBuffer) WriteFloat(value float32) { + byteBuffer.EnsureCapacity(4) + var uintValue = math.Float32bits(value) + byteBuffer.WriteRawInt32(int32(uintValue)) +} + +func (byteBuffer *ByteBuffer) ReadFloat() float32 { + var int32Value = byteBuffer.ReadRawInt32() + return math.Float32frombits(uint32(int32Value)) +} + +func (byteBuffer *ByteBuffer) WriteDouble(value float64) { + byteBuffer.EnsureCapacity(4) + var uintValue = math.Float64bits(value) + byteBuffer.WriteRawInt32(int32(uintValue >> 32)) + byteBuffer.WriteRawInt32(int32(uintValue)) +} + +func (byteBuffer *ByteBuffer) ReadDouble() float64 { + var highInt32Value = uint64(byteBuffer.ReadRawInt32()) + var lowInt32Value = uint64(byteBuffer.ReadRawInt32()) + return math.Float64frombits(highInt32Value<<32 | lowInt32Value) +} + +func (byteBuffer *ByteBuffer) WriteString(value string) { + var bytes []byte = []byte(value) + var length = len(bytes) + byteBuffer.EnsureCapacity(length) + byteBuffer.WriteInt(length) + byteBuffer.WriteUBytes(bytes) +} + +func (byteBuffer *ByteBuffer) ReadString() string { + var length = byteBuffer.ReadInt() + var bytes = byteBuffer.ReadUBytes(length) + return string(bytes[:]) +} + +func (byteBuffer *ByteBuffer) WriteChar(value string) { + // 如果为空则写入一个默认的字符0 + if len(value) == 0 { + byteBuffer.WriteInt(0) + byteBuffer.WriteUByte(0) + return + } + var char = value[0:1] + byteBuffer.WriteString(char) +} + +func (byteBuffer *ByteBuffer) ReadChar() string { + return byteBuffer.ReadString() +} \ No newline at end of file diff --git a/protocol/src/main/resources/js/ProtocolManager.js b/protocol/src/main/resources/js/ProtocolManager.js new file mode 100644 index 00000000..d7315498 --- /dev/null +++ b/protocol/src/main/resources/js/ProtocolManager.js @@ -0,0 +1,26 @@ +const protocols = new Map(); + +const ProtocolManager = {}; + +ProtocolManager.getProtocol = function getProtocol(protocolId) { + const protocol = protocols.get(protocolId); + if (protocol === null) { + throw new Error('[protocolId:' + protocolId + ']协议不存在'); + } + return protocol; +}; + +ProtocolManager.write = function write(byteBuffer, packet) { + const protocolId = packet.protocolId(); + byteBuffer.writeShort(protocolId); + const protocol = ProtocolManager.getProtocol(protocolId); + protocol.writeObject(byteBuffer, packet); +}; + +ProtocolManager.read = function read(byteBuffer) { + const protocolId = byteBuffer.readShort(); + const protocol = ProtocolManager.getProtocol(protocolId); + const packet = protocol.readObject(byteBuffer); + return packet; +}; + diff --git a/protocol/src/main/resources/js/buffer/ByteBuffer.js b/protocol/src/main/resources/js/buffer/ByteBuffer.js new file mode 100644 index 00000000..0c77f3c7 --- /dev/null +++ b/protocol/src/main/resources/js/buffer/ByteBuffer.js @@ -0,0 +1,340 @@ +import {readInt64, writeInt64} from './longbits.js'; + +const initSize = 128; +const maxSize = 655537; + +const maxShort = 32767; +const minShort = -32768; + +const maxInt = 2147483647; +const minInt = -2147483648; + +// UTF-8编码与解码 +const encoder = new TextEncoder('utf-8'); +const decoder = new TextDecoder('utf-8'); + +// nodejs的测试环境需要用以下方式特殊处理 +// const util = require('util'); +// const encoder = new util.TextEncoder('utf-8'); +// const decoder = new util.TextDecoder('utf-8'); + +// 在js中long可以支持的最大值 +// const maxLong = 9007199254740992; +// const minLong = -9007199254740992; + +const copy = function copy(original, newLength) { + if (original.byteLength > newLength) { + throw new Error('newLength is too small'); + } + const dst = new ArrayBuffer(newLength); + new Uint8Array(dst).set(new Uint8Array(original)); + return dst; +}; + +function encodeZigzagInt(n) { + // 有效位左移一位+符号位右移31位 + return (n << 1) ^ (n >> 31); +} + +function decodeZigzagInt(n) { + return (n >>> 1) ^ -(n & 1); +} + + +const ByteBuffer = function () { + this.writeOffset = 0; + this.readOffset = 0; + this.buffer = new ArrayBuffer(initSize); + this.bufferView = new DataView(this.buffer, 0, this.buffer.byteLength); + + this.setWriteOffset = function (writeOffset) { + if (writeOffset > this.buffer.byteLength) { + throw new Error('index out of bounds exception: readerIndex: ' + this.readOffset + + ', writerIndex: ' + this.writeOffset + + '(expected: 0 <= readerIndex <= writerIndex <= capacity:' + this.buffer.byteLength); + } + this.writeOffset = writeOffset; + }; + + this.setReadOffset = function (readOffset) { + if (readOffset > this.writeOffset) { + throw new Error('index out of bounds exception: readerIndex: ' + this.readOffset + + ', writerIndex: ' + this.writeOffset + + '(expected: 0 <= readerIndex <= writerIndex <= capacity:' + this.buffer.byteLength); + } + this.readOffset = readOffset; + }; + + this.getCapacity = function () { + return this.buffer.byteLength - this.writeOffset; + }; + + this.ensureCapacity = function (minCapacity) { + while (minCapacity - this.getCapacity() > 0) { + const newSize = this.buffer.byteLength * 2; + if (newSize > maxSize) { + throw new Error('out of memory error'); + } + this.buffer = copy(this.buffer, newSize); + this.bufferView = new DataView(this.buffer, 0, this.buffer.byteLength); + } + }; + + this.writeBoolean = function (value) { + if (!(value === true || value === false)) { + throw new Error('value must be true of false'); + } + this.ensureCapacity(1); + if (value === true) { + this.bufferView.setInt8(this.writeOffset, 1); + } else { + this.bufferView.setInt8(this.writeOffset, 0); + } + this.writeOffset++; + }; + + this.readBoolean = function () { + const value = this.bufferView.getInt8(this.readOffset); + this.readOffset++; + return (value === 1); + }; + + this.writeBytes = function (byteArray) { + const length = byteArray.byteLength; + this.ensureCapacity(length); + new Uint8Array(this.buffer).set(new Uint8Array(byteArray), this.writeOffset); + this.writeOffset += length; + }; + + this.writeByte = function (value) { + this.ensureCapacity(1); + this.bufferView.setInt8(this.writeOffset, value); + this.writeOffset++; + }; + + this.readByte = function () { + const value = this.bufferView.getInt8(this.readOffset); + this.readOffset++; + return value; + }; + + this.writeShort = function (value) { + if (!(minShort <= value && value <= maxShort)) { + throw new Error('value must range between minShort:-32768 and maxShort:32767'); + } + this.ensureCapacity(2); + this.bufferView.setInt16(this.writeOffset, value); + this.writeOffset += 2; + }; + + this.readShort = function () { + const value = this.bufferView.getInt16(this.readOffset); + this.readOffset += 2; + return value; + }; + + this.writeRawInt = function (value) { + if (!(minInt <= value && value <= maxInt)) { + throw new Error('value must range between minInt:-2147483648 and maxInt:2147483647'); + } + this.ensureCapacity(4); + this.bufferView.setInt32(this.writeOffset, value); + this.writeOffset += 4; + }; + + this.readRawInt = function () { + const value = this.bufferView.getInt32(this.readOffset); + this.readOffset += 4; + return value; + }; + + this.writeInt = function (value) { + if (!(minInt <= value && value <= maxInt)) { + throw new Error('value must range between minInt:-2147483648 and maxInt:2147483647'); + } + this.ensureCapacity(5); + + value = encodeZigzagInt(value); + + if (value >>> 7 === 0) { + this.writeByte(value); + return; + } + + if (value >>> 14 === 0) { + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7)); + return; + } + + if (value >>> 21 === 0) { + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7 | 0x80)); + this.writeByte(value >>> 14); + return; + } + + if (value >>> 28 === 0) { + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7 | 0x80)); + this.writeByte((value >>> 14 | 0x80)); + this.writeByte(value >>> 21); + return; + } + + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7 | 0x80)); + this.writeByte((value >>> 14 | 0x80)); + this.writeByte((value >>> 21 | 0x80)); + this.writeByte(value >>> 28); + }; + + this.readInt = function () { + let b = this.readByte(); + let value = b & 0x7F; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 7; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 14; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 21; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 28; + } + } + } + } + + return decodeZigzagInt(value); + }; + + this.writeLong = function (value) { + if (value === null || value === undefined) { + throw new Error('value must not be null'); + } + this.ensureCapacity(9); + + writeInt64(this, value); + }; + + this.readLong = function () { + const buffer = new ArrayBuffer(9); + const bufferView = new DataView(buffer, 0, buffer.byteLength); + + let count = 0; + let b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + } + } + } + } + } + } + } + } + return readInt64(new Uint8Array(buffer.slice(0, count))).toString(); + }; + + this.writeFloat = function (value) { + if (value === null || value === undefined) { + throw new Error('value must not be null'); + } + this.ensureCapacity(4); + this.bufferView.setFloat32(this.writeOffset, value); + this.writeOffset += 4; + }; + + this.readFloat = function () { + const value = this.bufferView.getFloat32(this.readOffset); + this.readOffset += 4; + return value; + }; + + this.writeDouble = function (value) { + if (value === null || value === undefined) { + throw new Error('value must not be null'); + } + this.ensureCapacity(8); + this.bufferView.setFloat64(this.writeOffset, value); + this.writeOffset += 8; + }; + + this.readDouble = function () { + const value = this.bufferView.getFloat64(this.readOffset); + this.readOffset += 8; + return value; + }; + + this.writeChar = function (value) { + if (value === null || value === undefined || value.length === 0) { + this.writeInt(0); + return; + } + this.writeString(value.charAt(0)); + }; + + this.readChar = function () { + return this.readString(); + }; + + this.writeString = function (value) { + if (value === null || value === undefined || value.trim().length === 0) { + this.writeInt(0); + return; + } + + const uint8Array = encoder.encode(value); + + this.ensureCapacity(5 + uint8Array.length); + + this.writeInt(uint8Array.length); + uint8Array.forEach((value) => this.writeByte(value)); + }; + + this.readString = function () { + const length = this.readInt(); + if (length <= 0) { + return ''; + } + const uint8Array = new Uint8Array(this.buffer.slice(this.readOffset, this.readOffset + length)); + const value = decoder.decode(uint8Array); + this.readOffset += length; + return value; + }; + + this.toBytes = function () { + const result = new ArrayBuffer(this.writeOffset); + new Uint8Array(result).set(new Uint8Array(this.buffer.slice(0, this.writeOffset))); + return result; + }; +}; + +export default ByteBuffer; diff --git a/protocol/src/main/resources/js/buffer/long.js b/protocol/src/main/resources/js/buffer/long.js new file mode 100644 index 00000000..31a8e6d7 --- /dev/null +++ b/protocol/src/main/resources/js/buffer/long.js @@ -0,0 +1,1325 @@ +/* eslint-disable */ +// from https://github.com/dcodeIO/long.js/blob/master/src/long.js +module.exports = Long; + +/** + * wasm optimizations, to do native i64 multiplication and divide + */ +var wasm = null; + +try { + wasm = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 13, 2, 96, 0, 1, 127, 96, 4, 127, 127, 127, 127, 1, 127, 3, 7, 6, 0, 1, 1, 1, 1, 1, 6, 6, 1, 127, 1, 65, 0, 11, 7, 50, 6, 3, 109, 117, 108, 0, 1, 5, 100, 105, 118, 95, 115, 0, 2, 5, 100, 105, 118, 95, 117, 0, 3, 5, 114, 101, 109, 95, 115, 0, 4, 5, 114, 101, 109, 95, 117, 0, 5, 8, 103, 101, 116, 95, 104, 105, 103, 104, 0, 0, 10, 191, 1, 6, 4, 0, 35, 0, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 126, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 127, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 128, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 129, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 130, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11 + ])), {}).exports; +} catch (e) { + // no wasm support :( +} + +/** + * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. + * See the from* functions below for more convenient ways of constructing Longs. + * @exports Long + * @class A Long class for representing a 64 bit two's-complement integer value. + * @param {number} low The low (signed) 32 bits of the long + * @param {number} high The high (signed) 32 bits of the long + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @constructor + */ +function Long(low, high, unsigned) { + + /** + * The low 32 bits as a signed value. + * @type {number} + */ + this.low = low | 0; + + /** + * The high 32 bits as a signed value. + * @type {number} + */ + this.high = high | 0; + + /** + * Whether unsigned or not. + * @type {boolean} + */ + this.unsigned = !!unsigned; +} + +// The internal representation of a long is the two given signed, 32-bit values. +// We use 32-bit pieces because these are the size of integers on which +// Javascript performs bit-operations. For operations like addition and +// multiplication, we split each number into 16 bit pieces, which can easily be +// multiplied within Javascript's floating-point representation without overflow +// or change in sign. +// +// In the algorithms below, we frequently reduce the negative case to the +// positive case by negating the input(s) and then post-processing the result. +// Note that we must ALWAYS check specially whether those values are MIN_VALUE +// (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as +// a positive number, it overflows back into a negative). Not handling this +// case would often result in infinite recursion. +// +// Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from* +// methods on which they depend. + +/** + * An indicator used to reliably determine if an object is a Long or not. + * @type {boolean} + * @const + * @private + */ +Long.prototype.__isLong__; + +Object.defineProperty(Long.prototype, "__isLong__", {value: true}); + +/** + * @function + * @param {*} obj Object + * @returns {boolean} + * @inner + */ +function isLong(obj) { + return (obj && obj["__isLong__"]) === true; +} + +/** + * Tests if the specified object is a Long. + * @function + * @param {*} obj Object + * @returns {boolean} + */ +Long.isLong = isLong; + +/** + * A cache of the Long representations of small integer values. + * @type {!Object} + * @inner + */ +var INT_CACHE = {}; + +/** + * A cache of the Long representations of small unsigned integer values. + * @type {!Object} + * @inner + */ +var UINT_CACHE = {}; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromInt(value, unsigned) { + var obj, cachedObj, cache; + if (unsigned) { + value >>>= 0; + if (cache = (0 <= value && value < 256)) { + cachedObj = UINT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); + if (cache) + UINT_CACHE[value] = obj; + return obj; + } else { + value |= 0; + if (cache = (-128 <= value && value < 128)) { + cachedObj = INT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, value < 0 ? -1 : 0, false); + if (cache) + INT_CACHE[value] = obj; + return obj; + } +} + +/** + * Returns a Long representing the given 32 bit integer value. + * @function + * @param {number} value The 32 bit integer in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromInt = fromInt; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromNumber(value, unsigned) { + if (isNaN(value)) + return unsigned ? UZERO : ZERO; + if (unsigned) { + if (value < 0) + return UZERO; + if (value >= TWO_PWR_64_DBL) + return MAX_UNSIGNED_VALUE; + } else { + if (value <= -TWO_PWR_63_DBL) + return MIN_VALUE; + if (value + 1 >= TWO_PWR_63_DBL) + return MAX_VALUE; + } + if (value < 0) + return fromNumber(-value, unsigned).neg(); + return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); +} + +/** + * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * @function + * @param {number} value The number in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromNumber = fromNumber; + +/** + * @param {number} lowBits + * @param {number} highBits + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromBits(lowBits, highBits, unsigned) { + return new Long(lowBits, highBits, unsigned); +} + +/** + * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is + * assumed to use 32 bits. + * @function + * @param {number} lowBits The low 32 bits + * @param {number} highBits The high 32 bits + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromBits = fromBits; + +/** + * @function + * @param {number} base + * @param {number} exponent + * @returns {number} + * @inner + */ +var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4) + +/** + * @param {string} str + * @param {(boolean|number)=} unsigned + * @param {number=} radix + * @returns {!Long} + * @inner + */ +function fromString(str, unsigned, radix) { + if (str.length === 0) + throw Error('empty string'); + if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") + return ZERO; + if (typeof unsigned === 'number') { + // For goog.math.long compatibility + radix = unsigned, + unsigned = false; + } else { + unsigned = !!unsigned; + } + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + + var p; + if ((p = str.indexOf('-')) > 0) + throw Error('interior hyphen'); + else if (p === 0) { + return fromString(str.substring(1), unsigned, radix).neg(); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 8)); + + var result = ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i), + value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = fromNumber(pow_dbl(radix, size)); + result = result.mul(power).add(fromNumber(value)); + } else { + result = result.mul(radixToPower); + result = result.add(fromNumber(value)); + } + } + result.unsigned = unsigned; + return result; +} + +/** + * Returns a Long representation of the given string, written using the specified radix. + * @function + * @param {string} str The textual representation of the Long + * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to signed + * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 + * @returns {!Long} The corresponding Long value + */ +Long.fromString = fromString; + +/** + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromValue(val, unsigned) { + if (typeof val === 'number') + return fromNumber(val, unsigned); + if (typeof val === 'string') + return fromString(val, unsigned); + // Throws for non-objects, converts non-instanceof Long: + return fromBits(val.low, val.high, typeof unsigned === 'boolean' ? unsigned : val.unsigned); +} + +/** + * Converts the specified value to a Long using the appropriate from* function for its type. + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} + */ +Long.fromValue = fromValue; + +// NOTE: the compiler should inline these constant values below and then remove these variables, so there should be +// no runtime penalty for these. + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_16_DBL = 1 << 16; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_24_DBL = 1 << 24; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2; + +/** + * @type {!Long} + * @const + * @inner + */ +var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL); + +/** + * @type {!Long} + * @inner + */ +var ZERO = fromInt(0); + +/** + * Signed zero. + * @type {!Long} + */ +Long.ZERO = ZERO; + +/** + * @type {!Long} + * @inner + */ +var UZERO = fromInt(0, true); + +/** + * Unsigned zero. + * @type {!Long} + */ +Long.UZERO = UZERO; + +/** + * @type {!Long} + * @inner + */ +var ONE = fromInt(1); + +/** + * Signed one. + * @type {!Long} + */ +Long.ONE = ONE; + +/** + * @type {!Long} + * @inner + */ +var UONE = fromInt(1, true); + +/** + * Unsigned one. + * @type {!Long} + */ +Long.UONE = UONE; + +/** + * @type {!Long} + * @inner + */ +var NEG_ONE = fromInt(-1); + +/** + * Signed negative one. + * @type {!Long} + */ +Long.NEG_ONE = NEG_ONE; + +/** + * @type {!Long} + * @inner + */ +var MAX_VALUE = fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0, false); + +/** + * Maximum signed value. + * @type {!Long} + */ +Long.MAX_VALUE = MAX_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF | 0, 0xFFFFFFFF | 0, true); + +/** + * Maximum unsigned value. + * @type {!Long} + */ +Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MIN_VALUE = fromBits(0, 0x80000000 | 0, false); + +/** + * Minimum signed value. + * @type {!Long} + */ +Long.MIN_VALUE = MIN_VALUE; + +/** + * @alias Long.prototype + * @inner + */ +var LongPrototype = Long.prototype; + +/** + * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. + * @returns {number} + */ +LongPrototype.toInt = function toInt() { + return this.unsigned ? this.low >>> 0 : this.low; +}; + +/** + * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). + * @returns {number} + */ +LongPrototype.toNumber = function toNumber() { + if (this.unsigned) + return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0); + return this.high * TWO_PWR_32_DBL + (this.low >>> 0); +}; + +/** + * Converts the Long to a string written in the specified radix. + * @param {number=} radix Radix (2-36), defaults to 10 + * @returns {string} + * @override + * @throws {RangeError} If `radix` is out of range + */ +LongPrototype.toString = function toString(radix) { + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + if (this.isZero()) + return '0'; + if (this.isNegative()) { // Unsigned Longs are never negative + if (this.eq(MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = fromNumber(radix), + div = this.div(radixLong), + rem1 = div.mul(radixLong).sub(this); + return div.toString(radix) + rem1.toInt().toString(radix); + } else + return '-' + this.neg().toString(radix); + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned), + rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower), + intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0, + digits = intval.toString(radix); + rem = remDiv; + if (rem.isZero()) + return digits + result; + else { + while (digits.length < 6) + digits = '0' + digits; + result = '' + digits + result; + } + } +}; + +/** + * Gets the high 32 bits as a signed integer. + * @returns {number} Signed high bits + */ +LongPrototype.getHighBits = function getHighBits() { + return this.high; +}; + +/** + * Gets the high 32 bits as an unsigned integer. + * @returns {number} Unsigned high bits + */ +LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { + return this.high >>> 0; +}; + +/** + * Gets the low 32 bits as a signed integer. + * @returns {number} Signed low bits + */ +LongPrototype.getLowBits = function getLowBits() { + return this.low; +}; + +/** + * Gets the low 32 bits as an unsigned integer. + * @returns {number} Unsigned low bits + */ +LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { + return this.low >>> 0; +}; + +/** + * Gets the number of bits needed to represent the absolute value of this Long. + * @returns {number} + */ +LongPrototype.getNumBitsAbs = function getNumBitsAbs() { + if (this.isNegative()) // Unsigned Longs are never negative + return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs(); + var val = this.high != 0 ? this.high : this.low; + for (var bit = 31; bit > 0; bit--) + if ((val & (1 << bit)) != 0) + break; + return this.high != 0 ? bit + 33 : bit + 1; +}; + +/** + * Tests if this Long's value equals zero. + * @returns {boolean} + */ +LongPrototype.isZero = function isZero() { + return this.high === 0 && this.low === 0; +}; + +/** + * Tests if this Long's value equals zero. This is an alias of {@link Long#isZero}. + * @returns {boolean} + */ +LongPrototype.eqz = LongPrototype.isZero; + +/** + * Tests if this Long's value is negative. + * @returns {boolean} + */ +LongPrototype.isNegative = function isNegative() { + return !this.unsigned && this.high < 0; +}; + +/** + * Tests if this Long's value is positive. + * @returns {boolean} + */ +LongPrototype.isPositive = function isPositive() { + return this.unsigned || this.high >= 0; +}; + +/** + * Tests if this Long's value is odd. + * @returns {boolean} + */ +LongPrototype.isOdd = function isOdd() { + return (this.low & 1) === 1; +}; + +/** + * Tests if this Long's value is even. + * @returns {boolean} + */ +LongPrototype.isEven = function isEven() { + return (this.low & 1) === 0; +}; + +/** + * Tests if this Long's value equals the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.equals = function equals(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1) + return false; + return this.high === other.high && this.low === other.low; +}; + +/** + * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.eq = LongPrototype.equals; + +/** + * Tests if this Long's value differs from the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.notEquals = function notEquals(other) { + return !this.eq(/* validates */ other); +}; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.neq = LongPrototype.notEquals; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ne = LongPrototype.notEquals; + +/** + * Tests if this Long's value is less than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThan = function lessThan(other) { + return this.comp(/* validates */ other) < 0; +}; + +/** + * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lt = LongPrototype.lessThan; + +/** + * Tests if this Long's value is less than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { + return this.comp(/* validates */ other) <= 0; +}; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lte = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.le = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is greater than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThan = function greaterThan(other) { + return this.comp(/* validates */ other) > 0; +}; + +/** + * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gt = LongPrototype.greaterThan; + +/** + * Tests if this Long's value is greater than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { + return this.comp(/* validates */ other) >= 0; +}; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gte = LongPrototype.greaterThanOrEqual; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ge = LongPrototype.greaterThanOrEqual; + +/** + * Compares this Long's value with the specified's. + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.compare = function compare(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.eq(other)) + return 0; + var thisNeg = this.isNegative(), + otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) + return -1; + if (!thisNeg && otherNeg) + return 1; + // At this point the sign bits are the same + if (!this.unsigned) + return this.sub(other).isNegative() ? -1 : 1; + // Both are positive if at least one is unsigned + return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1; +}; + +/** + * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}. + * @function + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.comp = LongPrototype.compare; + +/** + * Negates this Long's value. + * @returns {!Long} Negated Long + */ +LongPrototype.negate = function negate() { + if (!this.unsigned && this.eq(MIN_VALUE)) + return MIN_VALUE; + return this.not().add(ONE); +}; + +/** + * Negates this Long's value. This is an alias of {@link Long#negate}. + * @function + * @returns {!Long} Negated Long + */ +LongPrototype.neg = LongPrototype.negate; + +/** + * Returns the sum of this and the specified Long. + * @param {!Long|number|string} addend Addend + * @returns {!Long} Sum + */ +LongPrototype.add = function add(addend) { + if (!isLong(addend)) + addend = fromValue(addend); + + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = addend.high >>> 16; + var b32 = addend.high & 0xFFFF; + var b16 = addend.low >>> 16; + var b00 = addend.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the difference of this and the specified Long. + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.subtract = function subtract(subtrahend) { + if (!isLong(subtrahend)) + subtrahend = fromValue(subtrahend); + return this.add(subtrahend.neg()); +}; + +/** + * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}. + * @function + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.sub = LongPrototype.subtract; + +/** + * Returns the product of this and the specified Long. + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.multiply = function multiply(multiplier) { + if (this.isZero()) + return ZERO; + if (!isLong(multiplier)) + multiplier = fromValue(multiplier); + + // use wasm support if present + if (wasm) { + var low = wasm.mul(this.low, + this.high, + multiplier.low, + multiplier.high); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (multiplier.isZero()) + return ZERO; + if (this.eq(MIN_VALUE)) + return multiplier.isOdd() ? MIN_VALUE : ZERO; + if (multiplier.eq(MIN_VALUE)) + return this.isOdd() ? MIN_VALUE : ZERO; + + if (this.isNegative()) { + if (multiplier.isNegative()) + return this.neg().mul(multiplier.neg()); + else + return this.neg().mul(multiplier).neg(); + } else if (multiplier.isNegative()) + return this.mul(multiplier.neg()).neg(); + + // If both longs are small, use float multiplication + if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24)) + return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned); + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = multiplier.high >>> 16; + var b32 = multiplier.high & 0xFFFF; + var b16 = multiplier.low >>> 16; + var b00 = multiplier.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}. + * @function + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.mul = LongPrototype.multiply; + +/** + * Returns this Long divided by the specified. The result is signed if this Long is signed or + * unsigned if this Long is unsigned. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.divide = function divide(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + if (divisor.isZero()) + throw Error('division by zero'); + + // use wasm support if present + if (wasm) { + // guard against signed division overflow: the largest + // negative number / -1 would be 1 larger than the largest + // positive number, due to two's complement. + if (!this.unsigned && + this.high === -0x80000000 && + divisor.low === -1 && divisor.high === -1) { + // be consistent with non-wasm code path + return this; + } + var low = (this.unsigned ? wasm.div_u : wasm.div_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (this.isZero()) + return this.unsigned ? UZERO : ZERO; + var approx, rem, res; + if (!this.unsigned) { + // This section is only relevant for signed longs and is derived from the + // closure library as a whole. + if (this.eq(MIN_VALUE)) { + if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) + return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + else if (divisor.eq(MIN_VALUE)) + return ONE; + else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shr(1); + approx = halfThis.div(divisor).shl(1); + if (approx.eq(ZERO)) { + return divisor.isNegative() ? ONE : NEG_ONE; + } else { + rem = this.sub(divisor.mul(approx)); + res = approx.add(rem.div(divisor)); + return res; + } + } + } else if (divisor.eq(MIN_VALUE)) + return this.unsigned ? UZERO : ZERO; + if (this.isNegative()) { + if (divisor.isNegative()) + return this.neg().div(divisor.neg()); + return this.neg().div(divisor).neg(); + } else if (divisor.isNegative()) + return this.div(divisor.neg()).neg(); + res = ZERO; + } else { + // The algorithm below has not been made for unsigned longs. It's therefore + // required to take special care of the MSB prior to running it. + if (!divisor.unsigned) + divisor = divisor.toUnsigned(); + if (divisor.gt(this)) + return UZERO; + if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true + return UONE; + res = UZERO; + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + rem = this; + while (rem.gte(divisor)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2), + delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48), + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + approxRes = fromNumber(approx), + approxRem = approxRes.mul(divisor); + while (approxRem.isNegative() || approxRem.gt(rem)) { + approx -= delta; + approxRes = fromNumber(approx, this.unsigned); + approxRem = approxRes.mul(divisor); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) + approxRes = ONE; + + res = res.add(approxRes); + rem = rem.sub(approxRem); + } + return res; +}; + +/** + * Returns this Long divided by the specified. This is an alias of {@link Long#divide}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.div = LongPrototype.divide; + +/** + * Returns this Long modulo the specified. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.modulo = function modulo(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + + // use wasm support if present + if (wasm) { + var low = (this.unsigned ? wasm.rem_u : wasm.rem_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + return this.sub(this.div(divisor).mul(divisor)); +}; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.mod = LongPrototype.modulo; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.rem = LongPrototype.modulo; + +/** + * Returns the bitwise NOT of this Long. + * @returns {!Long} + */ +LongPrototype.not = function not() { + return fromBits(~this.low, ~this.high, this.unsigned); +}; + +/** + * Returns the bitwise AND of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.and = function and(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low & other.low, this.high & other.high, this.unsigned); +}; + +/** + * Returns the bitwise OR of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.or = function or(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low | other.low, this.high | other.high, this.unsigned); +}; + +/** + * Returns the bitwise XOR of this Long and the given one. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.xor = function xor(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftLeft = function shiftLeft(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned); + else + return fromBits(0, this.low << (numBits - 32), this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shl = LongPrototype.shiftLeft; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRight = function shiftRight(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned); + else + return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned); +}; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr = LongPrototype.shiftRight; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + numBits &= 63; + if (numBits === 0) + return this; + else { + var high = this.high; + if (numBits < 32) { + var low = this.low; + return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned); + } else if (numBits === 32) + return fromBits(high, 0, this.unsigned); + else + return fromBits(high >>> (numBits - 32), 0, this.unsigned); + } +}; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shru = LongPrototype.shiftRightUnsigned; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr_u = LongPrototype.shiftRightUnsigned; + +/** + * Converts this Long to signed. + * @returns {!Long} Signed long + */ +LongPrototype.toSigned = function toSigned() { + if (!this.unsigned) + return this; + return fromBits(this.low, this.high, false); +}; + +/** + * Converts this Long to unsigned. + * @returns {!Long} Unsigned long + */ +LongPrototype.toUnsigned = function toUnsigned() { + if (this.unsigned) + return this; + return fromBits(this.low, this.high, true); +}; + +/** + * Converts this Long to its byte representation. + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {!Array.} Byte representation + */ +LongPrototype.toBytes = function toBytes(le) { + return le ? this.toBytesLE() : this.toBytesBE(); +}; + +/** + * Converts this Long to its little endian byte representation. + * @returns {!Array.} Little endian byte representation + */ +LongPrototype.toBytesLE = function toBytesLE() { + var hi = this.high, + lo = this.low; + return [ + lo & 0xff, + lo >>> 8 & 0xff, + lo >>> 16 & 0xff, + lo >>> 24, + hi & 0xff, + hi >>> 8 & 0xff, + hi >>> 16 & 0xff, + hi >>> 24 + ]; +}; + +/** + * Converts this Long to its big endian byte representation. + * @returns {!Array.} Big endian byte representation + */ +LongPrototype.toBytesBE = function toBytesBE() { + var hi = this.high, + lo = this.low; + return [ + hi >>> 24, + hi >>> 16 & 0xff, + hi >>> 8 & 0xff, + hi & 0xff, + lo >>> 24, + lo >>> 16 & 0xff, + lo >>> 8 & 0xff, + lo & 0xff + ]; +}; + +/** + * Creates a Long from its byte representation. + * @param {!Array.} bytes Byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {Long} The corresponding Long value + */ +Long.fromBytes = function fromBytes(bytes, unsigned, le) { + return le ? Long.fromBytesLE(bytes, unsigned) : Long.fromBytesBE(bytes, unsigned); +}; + +/** + * Creates a Long from its little endian byte representation. + * @param {!Array.} bytes Little endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesLE = function fromBytesLE(bytes, unsigned) { + return new Long( + bytes[0] | + bytes[1] << 8 | + bytes[2] << 16 | + bytes[3] << 24, + bytes[4] | + bytes[5] << 8 | + bytes[6] << 16 | + bytes[7] << 24, + unsigned + ); +}; + +/** + * Creates a Long from its big endian byte representation. + * @param {!Array.} bytes Big endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesBE = function fromBytesBE(bytes, unsigned) { + return new Long( + bytes[4] << 24 | + bytes[5] << 16 | + bytes[6] << 8 | + bytes[7], + bytes[0] << 24 | + bytes[1] << 16 | + bytes[2] << 8 | + bytes[3], + unsigned + ); +}; diff --git a/protocol/src/main/resources/js/buffer/longbits.js b/protocol/src/main/resources/js/buffer/longbits.js new file mode 100644 index 00000000..62e7893a --- /dev/null +++ b/protocol/src/main/resources/js/buffer/longbits.js @@ -0,0 +1,184 @@ +// from protobuf +import Long from './long.js'; + +/** + * Constructs new long bits. + * @classdesc Helper class for working with the low and high bits of a 64 bit value. + * @memberof util + * @constructor + * @param {number} lo Low 32 bits, unsigned + * @param {number} hi High 32 bits, unsigned + */ +function LongBits(lo, hi) { + // note that the casts below are theoretically unnecessary as of today, but older statically + // generated converter code might still call the ctor with signed 32bits. kept for compat. + + /** + * Low bits. + * @type {number} + */ + this.lo = lo >>> 0; + + /** + * High bits. + * @type {number} + */ + this.hi = hi >>> 0; +} + +/** + * Zig-zag encodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzEncode = function zzEncode() { + const mask = this.hi >> 31; + this.hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0; + this.lo = (this.lo << 1 ^ mask) >>> 0; + return this; +}; + +/** + * Zig-zag decodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzDecode = function zzDecode() { + const mask = -(this.lo & 1); + this.lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0; + this.hi = (this.hi >>> 1 ^ mask) >>> 0; + return this; +}; + +/** + * Converts this long bits to a long. + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {Long} Long + */ +LongBits.prototype.toLong = function toLong(unsigned) { + return new Long(this.lo | 0, this.hi | 0, Boolean(unsigned)); +}; + +/** + * Zero bits. + * @memberof util.LongBits + * @type {util.LongBits} + */ +const zero = LongBits.zero = new LongBits(0, 0); + +function from(value) { + if (typeof value === 'number') { + return fromNumber(value); + } + if (typeof value === 'string' || value instanceof String) { + value = Long.fromString(value); + } + return value.low || value.high ? new LongBits(value.low >>> 0, value.high >>> 0) : zero; +} + + +/** + * Constructs new long bits from the specified number. + * @param {number} value Value + * @returns {util.LongBits} Instance + */ +function fromNumber(value) { + if (value === 0) { + return zero; + } + const sign = value < 0; + if (sign) { + value = -value; + } + let lo = value >>> 0; + let hi = (value - lo) / 4294967296 >>> 0; + if (sign) { + hi = ~hi >>> 0; + lo = ~lo >>> 0; + if (++lo > 4294967295) { + lo = 0; + if (++hi > 4294967295) { + hi = 0; + } + } + } + return new LongBits(lo, hi); +} + +function writeVarint64(byteBuffer, value) { + let count = 0; + while (value.hi) { + byteBuffer.writeByte(value.lo & 127 | 128); + value.lo = (value.lo >>> 7 | value.hi << 25) >>> 0; + value.hi >>>= 7; + count = count + 7; + } + while (value.lo > 127) { + if (count >= 56) { + byteBuffer.writeByte(value.lo); + return; + } + byteBuffer.writeByte(value.lo & 127 | 128); + value.lo = value.lo >>> 7; + count = count + 7; + } + byteBuffer.writeByte(value.lo); +} + +function readLongVarint(buffer) { + // tends to deopt with local vars for octet etc. + const bits = new LongBits(0, 0); + let i = 0; + const len = buffer.length; + let pos = 0; + if (len - pos > 4) { // fast route (lo) + for (; i < 4; ++i) { + // 1st..4th + bits.lo = (bits.lo | (buffer[pos] & 127) << i * 7) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + } + // 5th + bits.lo = (bits.lo | (buffer[pos] & 127) << 28) >>> 0; + bits.hi = (bits.hi | (buffer[pos] & 127) >> 4) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + i = 0; + } else { + for (; i < 3; ++i) { + // 1st..3th + bits.lo = (bits.lo | (buffer[pos] & 127) << i * 7) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + } + // 4th + bits.lo = (bits.lo | (buffer[pos++] & 127) << i * 7) >>> 0; + return bits; + } + + // 6th..9th + for (; i < 4; ++i) { + // 最后一位直接写入 + if (pos === 8) { + bits.hi = (bits.hi | buffer[pos] << i * 7 + 3) >>> 0; + return bits; + } + bits.hi = (bits.hi | (buffer[pos] & 127) << i * 7 + 3) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + } + + return bits; +} + + +export function writeInt64(byteBuffer, value) { + const bits = from(value).zzEncode(); + writeVarint64(byteBuffer, bits); +} + +export function readInt64(buffer) { + return readLongVarint(buffer).zzDecode().toLong(false); +} diff --git a/protocol/src/main/resources/lua/Buffer/ByteBuffer.lua b/protocol/src/main/resources/lua/Buffer/ByteBuffer.lua new file mode 100644 index 00000000..8d569932 --- /dev/null +++ b/protocol/src/main/resources/lua/Buffer/ByteBuffer.lua @@ -0,0 +1,453 @@ +--默认为大端模式 +--支持的lua版本为>=5.3 +--支持标准的Lua是使用64-bit的int以及64-bit的双精度float +--当lua只能支持32位的整数类型时,可以考虑用Long来替代,需要修改原代码 +--右移操作>>是无符号右移 +--local Long = require("Long") + +local maxInt = 2147483647 +local minInt = -2147483648 +local initSize = 128 +local zeroByte = string.char(0) + +local ByteBuffer = {} + +local trueBooleanStrValue = string.char(1) +local falseBooleanStrValue = string.char(0) + +-------------------------------------构造器------------------------------------- +function ByteBuffer:new() + --buffer里的每一个元素为一个长度为1的字符串 + local obj = { + buffer = {}, + writeOffset = 1, + readOffset = 1 + } + setmetatable(obj, self) + self.__index = self + + for i = 1, initSize do + table.insert(obj.buffer, zeroByte) + end + return obj +end + + +-------------------------------------UTF8------------------------------------- +-- 判断utf8字符byte长度 +-- 0xxxxxxx - 1 byte +-- 110yxxxx - 192, 2 byte +-- 1110yyyy - 225, 3 byte +-- 11110zzz - 240, 4 byte +local function chsize(char) + if not char then + print("not char") + return 0 + elseif char > 240 then + return 4 + elseif char > 225 then + return 3 + elseif char > 192 then + return 2 + else + return 1 + end +end + + +-- 截取utf8 字符串 +-- str: 要截取的字符串 +-- startChar: 开始字符下标,从1开始 +-- numChars: 要截取的字符长度 +local function utf8sub(str, startChar, numChars) + local startIndex = 1 + while startChar > 1 do + local char = string.byte(str, startIndex) + startIndex = startIndex + chsize(char) + startChar = startChar - 1 + end + + local currentIndex = startIndex + + while numChars > 0 and currentIndex <= #str do + local char = string.byte(str, currentIndex) + currentIndex = currentIndex + chsize(char) + numChars = numChars - 1 + end + return str:sub(startIndex, currentIndex - 1) +end + + +-------------------------------------get和set------------------------------------- +function ByteBuffer:getWriteOffset() + return self.writeOffset +end + +function ByteBuffer:setWriteOffset(writeOffset) + if writeOffset > #self.buffer then + error("index out of bounds exception: readerIndex: " + self.readOffset + + ", writerIndex: " + self.writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + #self.buffer) + end + self.writeOffset = writeOffset + return self +end + +function ByteBuffer:getReadOffset() + return self.readOffset +end + +function ByteBuffer:setReadOffset(readOffset) + if readOffset > self.writeOffset then + error("index out of bounds exception: readerIndex: " + self.readOffset + + ", writerIndex: " + this.writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + #self.buffer) + end + self.readOffset = readOffset + return self +end + +function ByteBuffer:getLen() + return #self.buffer +end + +function ByteBuffer:getAvailable() + return #self.buffer - self.writeOffset + 1 +end + +-------------------------------------write和read------------------------------------- + +--bool +function ByteBuffer:writeBoolean(boolValue) + if boolValue then + self:writeRawByteStr(trueBooleanStrValue) + else + self:writeRawByteStr(falseBooleanStrValue) + end + return self +end + +function ByteBuffer:readBoolean() + -- When char > 256, the readUByte method will show an error. + -- So, we have to use readChar + return self:readRawByteStr() == trueBooleanStrValue +end + + +--- byte +-- The byte is a number between -128 and 127, otherwise, the lua will get an error. +function ByteBuffer:writeByte(byteValue) + local str = string.pack("b", byteValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readByte() + local result = string.unpack("b", self:readRawByteStr()) + return result +end + +-- The byte is a number between 0 and 255, otherwise, the lua will get an error. +function ByteBuffer:writeUByte(ubyteValue) + self:writeRawByteStr(string.char(ubyteValue)) + return self +end + +function ByteBuffer:readUByte() + return string.byte(self:readRawByteStr()) +end + + +-- short +function ByteBuffer:writeShort(shortValue) + local str = string.pack(">h", shortValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readShort() + local byteStrArray = self:readBuffer(2) + local result = string.unpack(">h", byteStrArray) + return result +end + + +-- int +function ByteBuffer:writeInt(intValue) + if (math.type(intValue) ~= "integer") then + error("intValue must be integer") + end + if ((minInt > intValue) or (intValue > maxInt)) then + error("intValue must range between minInt:-2147483648 and maxInt:2147483647") + end + + return self:writeLong(intValue) +end + +function ByteBuffer:readInt() + return self:readLong() +end + +-- int +function ByteBuffer:writeRawInt(intValue) + local str = string.pack(">i", intValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readRawInt() + local byteStrArray = self:readBuffer(4) + local result = string.unpack(">i", byteStrArray) + return result +end + +--long +function ByteBuffer:writeLong(longValue) + --Long:writeLong(self, longValue) + + if (math.type(longValue) ~= "integer") then + error("longValue must be integer") + end + + --lua中的右移为无符号右移,要特殊处理 + local mask = longValue >> 63 + local value = longValue << 1 + if (mask == 1) then + value = value ~ 0xFFFFFFFFFFFFFFFF + end + + if (value >> 7) == 0 then + self:writeUByte(value) + return + end + + if (value >> 14) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte((value >> 7) & 0x7F) + return + end + + if (value >> 21) == 0 then + self:writeUByte((value & 0x7F) | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte((value >> 14) & 0x7F) + return + end + + if (value >> 28) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte((value >> 21) & 0x7F) + return + end + + if (value >> 35) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte((value >> 28) & 0x7F) + return + end + + if (value >> 42) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte((value >> 35) & 0x7F) + return + end + + if (value >> 49) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte(((value >> 35) & 0x7F | 0x80)) + self:writeUByte((value >> 42) & 0x7F) + return + end + + if (value >> 56) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte(((value >> 35) & 0x7F | 0x80)) + self:writeUByte(((value >> 42) & 0x7F | 0x80)) + self:writeUByte((value >> 49) & 0x7F) + return + end + + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte(((value >> 35) & 0x7F | 0x80)) + self:writeUByte(((value >> 42) & 0x7F | 0x80)) + self:writeUByte(((value >> 49) & 0x7F | 0x80)) + self:writeUByte(value >> 56) + return self +end + +function ByteBuffer:readLong() + --return Long:readLong(self):toString() + local b = self:readUByte() + local value = b & 0x7F + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 7) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 14) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 21) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 28) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 35) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 42) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 49) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | (b << 56) + end + end + end + end + end + end + end + end + return (value >> 1) ~ -(value & 1) +end + +--固定8位的lua数字类型 +function ByteBuffer:writeLuaNumber(luaNumberValue) + local str = string.pack(">n", luaNumberValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readLuaNumber() + local result = string.unpack(">n", self:readBuffer(8)) + return result +end + + + + +--float +function ByteBuffer:writeFloat(floatValue) + local str = string.pack(">f", floatValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readFloat() + local byteStrArray = self:readBuffer(4) + local result = string.unpack(">f", byteStrArray) + return result +end + + +--double +function ByteBuffer:writeDouble(doubleValue) + local str = string.pack(">d", doubleValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readDouble() + local byteStrArray = self:readBuffer(8) + local result = string.unpack(">d", byteStrArray) + return result +end + + +--string +function ByteBuffer:writeString(str) + if str == nil or #str == 0 then + self:writeInt(0) + return + end + self:writeInt(#str) + self:writeBuffer(str) + return self + end + +function ByteBuffer:readString() + local length = self:readInt() + return self:readBuffer(length) +end + +--char +function ByteBuffer:writeChar(charValue) + if str == nil or #str == 0 then + self:writeInt(0) + self:writeByte(0) + return + end + local str = utf8sub(charValue, 1, 1) + self:writeString(str) + return self +end + +function ByteBuffer:readChar() + return self:readString() +end + +--- Write a encoded char array into buf +function ByteBuffer:writeBuffer(str) + for i = 1, #str do + self:writeRawByteStr(string.sub(str, i, i)) + end + return self +end + +--- Read a byte array as string from current position, then update the position. +function ByteBuffer:readBuffer(length) + local byteStrArray = self:getBytes(self.readOffset, self.readOffset + length - 1) + self.readOffset = self.readOffset + length + return byteStrArray +end + +function ByteBuffer:writeRawByteStr(byteStrValue) + if self.writeOffset > #self.buffer + 1 then + for i = #self.buffer + 1, self.writeOffset - 1 do + table.insert(self.buffer, zeroByte) + end + end + self.buffer[self.writeOffset] = string.sub(byteStrValue, 1, 1) + self.writeOffset = self.writeOffset + 1 + return self +end + +function ByteBuffer:readRawByteStr() + local byteStrValue = self.buffer[self.readOffset] + self.readOffset = self.readOffset + 1 + return byteStrValue +end + +--- Get all byte array as a lua string. +-- Do not update position. +function ByteBuffer:getBytes(startIndex, endIndex) + startIndex = startIndex or 1 + endIndex = endIndex or #self.buffer + return table.concat(self.buffer, "", startIndex, endIndex) +end + +return ByteBuffer \ No newline at end of file diff --git a/protocol/src/main/resources/lua/Buffer/Long.lua b/protocol/src/main/resources/lua/Buffer/Long.lua new file mode 100644 index 00000000..2dd618f1 --- /dev/null +++ b/protocol/src/main/resources/lua/Buffer/Long.lua @@ -0,0 +1,542 @@ +local MAX_LONG_4BYTE = 1 << 32 +local MIN_INT = -2147483648 +local MAX_INT = 2147483647 +local MIN_LONG = 0x8000000000000000 +local MAX_LONG = 0x7fffffffffffffff +local MIN_LONG_STRING = "-9223372036854775808" +--The natural logarithm of 2. +local LN2 = 0.6931471805599453 + +Long = {} + +function Long:new(low, high) + local obj = { + low = low & 0xFFFFFFFF, + high = high & 0xFFFFFFFF + } + + setmetatable(obj, self) + self.__index = self + return obj +end + +local function clone(value) + return Long:new(value.low, value.high) +end + +local function fromBits(lowBits, highBits) + return Long:new(lowBits, highBits) +end + +local function fromInt(value) + value = math.tointeger(value) + local param = 0 + if value < 0 then + param = -1 + end + return fromBits(value, param) +end + +local ZERO = fromInt(0) +local ONE = fromInt(1) +local NEG_ONE = fromInt(-1) +local MAX_VALUE = fromBits(0xFFFFFFFF, 0x7FFFFFFF) +local MIN_VALUE = fromBits(0, 0x80000000) + +local function fromNumber(value) + if (value <= -MIN_LONG) then + return clone(MIN_VALUE) + + end + + if (value + 1 >= MAX_LONG) then + return clone(MAX_VALUE) + end + + if (value < 0) then + return fromNumber(-value):negate() + end + return fromBits(math.floor((value % MAX_LONG_4BYTE)) | 0, math.floor(value / MAX_LONG_4BYTE) | 0) +end + +function Long:fromString(str, radix) + if type(radix) == "nil" then + radix = 10 + end + + if (type(str) ~= "string") then + error("str不是string类型参数") + end + + --进制必须在2到36 + if radix < 2 or 36 < radix then + error("range radix error") + end + + local p = string.find(str, "-") + if p ~= nil then + if (p > 1) then + error("interior hyphen") + end + + if (p == 1) then + return Long:fromString(string.sub(str, 2), radix):negate() + end + end + + local radixToPower = fromNumber(radix ^ 8) + local result = clone(ZERO) + str = tostring(str) + for i = 1, #str, 8 do + local size = math.min(8, #str - i + 1) + if (size < 8) then + local value = tonumber(string.sub(str, i), radix) + local power = fromNumber(radix ^ size) + result = result:multiply(power):add(fromNumber(value)) + else + local value = tonumber(string.sub(str, i, i + 7), radix) + result = result:multiply(radixToPower):add(fromNumber(value)) + end + end + return result +end + +--转为10进制的string符号的long +function Long:toString() + local radix = 10 + if (Long:isZero()) then + return "0" + end + + if (self:isNegative()) then + if (self:equals(MIN_VALUE)) then + return MIN_LONG_STRING + else + return '-' .. self:negate():toString(radix) + end + end + + local radixToPower = fromNumber(radix ^ 6) + local rem = self + local result = '' + while (true) do + local remDiv = rem:divide(radixToPower) + local digits = tostring(rem:subtract(remDiv:multiply(radixToPower)):toInt() & 0xFFFFFFFF) + rem = remDiv + if (rem:isZero()) then + return digits .. result + else + while (#digits < 6) do + digits = '0' .. digits + end + result = '' .. digits .. result + end + end +end + +--Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). +function Long:toNumber() + return self.high * MAX_LONG_4BYTE + self.low +end + +--Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. +function Long:toInt() + return self.low +end + +function Long:isNegative() + return (self.high & 0x80000000) ~= 0 +end + +function Long:negate() + if self:equals(MIN_VALUE) then + return clone(MIN_VALUE) + end + --正数转为负数的二进制编码,取反加1 + local notSelf = fromBits(~self.low, ~self.high) + return notSelf:add(ONE) +end + +function Long:equals(other) + return self.high == other.high and self.low == other.low +end + +function Long:isZero() + return self.high == 0 and self.low == 0 +end + +function Long:add(addend) + local a48 = (self.high >> 16) + local a32 = (self.high & 0xFFFF) + local a16 = (self.low >> 16) + local a00 = (self.low & 0xFFFF) + + local b48 = (addend.high >> 16) + local b32 = (addend.high & 0xFFFF) + local b16 = (addend.low >> 16) + local b00 = (addend.low & 0xFFFF) + + local c48 = 0 + local c32 = 0 + local c16 = 0 + local c00 = 0 + c00 = c00 + a00 + b00 + c16 = c16 + (c00 >> 16) + c00 = (c00 & 0xFFFF) + c16 = c16 + a16 + b16 + c32 = c32 + (c16 >> 16) + c16 = (c16 & 0xFFFF) + c32 = c32 + a32 + b32 + c48 = c48 + (c32 >> 16) + c32 = (c32 & 0xFFFF) + c48 = c48 + a48 + b48 + c48 = (c48 & 0xFFFF) + return fromBits((c16 << 16) | c00, (c48 << 16) | c32) +end + +function Long:subtract(subtrahend) + return self:add(subtrahend:negate()) +end + +function Long:multiply(multiplier) + if (self:isZero()) then + return clone(ZERO) + end + + if (multiplier:isZero()) then + return clone(ZERO) + end + + local a48 = (self.high >> 16) + local a32 = (self.high & 0xFFFF) + local a16 = (self.low >> 16) + local a00 = (self.low & 0xFFFF) + + local b48 = (multiplier.high >> 16) + local b32 = (multiplier.high & 0xFFFF) + local b16 = (multiplier.low >> 16) + local b00 = (multiplier.low & 0xFFFF) + + local c48 = 0 + local c32 = 0 + local c16 = 0 + local c00 = 0 + c00 = c00 + a00 * b00 + c16 = c16 + (c00 >> 16) + c00 = c00 & 0xFFFF + c16 = c16 + a16 * b00 + c32 = c32 + (c16 >> 16) + c16 = c16 & 0xFFFF + c16 = c16 + a00 * b16 + c32 = c32 + (c16 >> 16) + c16 = c16 & 0xFFFF + c32 = c32 + a32 * b00 + c48 = c48 + (c32 >> 16) + c32 = c32 & 0xFFFF + c32 = c32 + a16 * b16 + c48 = c48 + (c32 >> 16) + c32 = c32 & 0xFFFF + c32 = c32 + a00 * b32 + c48 = c48 + (c32 >> 16) + c32 = c32 & 0xFFFF + c48 = c48 + a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48 + c48 = c48 & 0xFFFF + return fromBits((c16 << 16) | c00, (c48 << 16) | c32) +end + +function Long:divide(divisor) + if (divisor:isZero()) then + error('division by zero') + end + + if (self:isZero()) then + return clone(ZERO) + end + + local approx + local rem + local res + if (self:equals(MIN_VALUE)) then + if (divisor:equals(ONE) or divisor:equals(NEG_ONE)) then + return clone(MIN_VALUE) + elseif (divisor:equals(MIN_VALUE)) then + return clone(ONE) + else + local halfThis = self:shiftRight(1) + approx = halfThis:divide(divisor):shiftLeft(1) + if (approx:equals(ZERO)) then + if (divisor:isNegative()) then + return clone(ONE) + else + return clone(NEG_ONE) + end + else + rem = self:subtract(divisor:multiply(approx)) + res = approx:add(rem:divide(divisor)) + return res + end + end + elseif (divisor:equals(MIN_VALUE)) then + return clone(ZERO) + end + if (self:isNegative()) then + if (divisor:isNegative()) then + return self:neg():divide(divisor:negate()) + end + return self:negate():divide(divisor):negate() + elseif (divisor:isNegative()) then + return self:divide(divisor:negate()):negate() + end + res = clone(ZERO) + + rem = self + while (rem:greaterThanOrEqual(divisor)) do + approx = math.max(1, math.floor(rem:toNumber() / divisor:toNumber())) + + local log2 = math.ceil(math.log(approx) / LN2) + + local delta = 1 + if log2 <= 48 then + delta = 2 ^ (log2 - 48) + end + + local approxRes = fromNumber(approx) + local approxRem = approxRes:multiply(divisor) + while (approxRem:isNegative() or approxRem:greaterThan(rem)) do + approx = approx - delta + approxRes = fromNumber(approx) + approxRem = approxRes:multiply(divisor) + end + + if (approxRes:isZero()) then + approxRes = clone(ONE) + end + + res = res:add(approxRes) + rem = rem:subtract(approxRem) + end + return res +end + +function shiftRight(numBits) + numBits = numBits & 63 + if (numBits == 0) then + return self + elseif (numBits < 32) then + return fromBits((self.low >> numBits) | (self.high << (32 - numBits)), self.high >> numBits) + else + if (self.high >= 0) then + return fromBits(self.high >> (numBits - 32), 0) + else + return fromBits(self.high >> (numBits - 32), -1) + end + end +end + +function shiftLeft(numBits) + numBits = numBits & 63 + if (numBits == 0) then + return self + elseif (numBits < 32) then + return fromBits(self.low << numBits, (self.high << numBits) | (self.low >> (32 - numBits))) + else + return fromBits(0, self.low << (numBits - 32)) + end +end + +function Long:compare(other) + if (self:equals(other)) then + return 0 + end + local thisNeg = self:isNegative() + local otherNeg = other:isNegative() + if (thisNeg and not (otherNeg)) then + return -1 + end + if (not (thisNeg) and otherNeg) then + return 1 + end + if self:subtract(other):isNegative() then + return -1 + else + return 1 + end +end + +function Long:greaterThanOrEqual(other) + return self:compare(other) >= 0 +end + +function Long:greaterThan(other) + return self:compare(other) > 0 +end + +function Long:encodeZigzagLong() + local mask = self.high >> 31 + if mask == 1 then + self.high = ((self.high << 1 | self.low >> 31) ~ 0xFFFFFFFF) & 0xFFFFFFFF + self.low = ((self.low << 1 | mask) ~ 0xFFFFFFFE) & 0xFFFFFFFF + else + self.high = (self.high << 1 | self.low >> 31) & 0xFFFFFFFF + self.low = (self.low << 1) & 0xFFFFFFFF + end + return self +end + +function Long:decodeZigzagLong() + local mask = self.low & 1 + if mask == 1 then + self.low = (((self.low >> 1) | (self.high << 31)) ~ 0xFFFFFFFF) & 0xFFFFFFFF + self.high = ((self.high >> 1 | (0x80000000)) ~ 0x7FFFFFFF) & 0xFFFFFFFF + else + self.low = ((self.low >> 1) | (self.high << 31)) & 0xFFFFFFFF + self.high = (self.high >> 1) & 0xFFFFFFFF + end + return self +end + +function Long:writeLong(byteBuffer, longValue) + if type(longValue) == "string" then + local len = #longValue + if len <= 11 then + local num = tonumber(longValue) + if (MIN_INT <= num) and (num <= MAX_INT) then + byteBuffer:writeInt(num) + return + end + end + end + + if type(longValue) == number then + if (MIN_INT <= longValue) and (longValue <= MAX_INT) then + byteBuffer:writeInt(tonumber(longValue)) + return + end + end + + --写入Long + local value = Long:fromString(longValue) + value:encodeZigzagLong() + local count = 0 + while (value.high ~= 0) do + byteBuffer:writeByte(value.low & 127 | 128) + value.low = ((value.low >> 7) | (value.high << 25)) + value.high = (value.high >> 7) + count = count + 7 + end + while (value.low > 127) do + if count >= 56 then + byteBuffer:writeByte(value.low) + return + end + byteBuffer:writeByte(value.low & 127 | 128) + value.low = value.low >> 7 + count = count + 7 + end + byteBuffer:writeByte(value.low) +end + +local function fromByteBuffer(byteBuffer) + local bits = Long:new(0, 0) + local count = #byteBuffer + local i = 0 + local pos = 1 + if (count > 4) then + --先读入1到4位 + while i < 4 do + bits.low = (bits.low | ((byteBuffer[pos] & 127) << (i * 7))) & 0xFFFFFFFF + i = i + 1 + pos = pos + 1 + end + --读第5位,第5位底位置读到low,高位置读到high + bits.low = (bits.low | ((byteBuffer[pos] & 127) << 28)) & 0xFFFFFFFF + bits.high = (bits.high | ((byteBuffer[pos] & 127) >> 4)) & 0xFFFFFFFF + if (byteBuffer[pos] < 128) then + return bits + end + i = 0 + pos = pos + 1 + else + while i < 3 do + bits.low = (bits.low | ((byteBuffer[pos] & 127) << (i * 7))) & 0xFFFFFFFF + if (byteBuffer[pos] < 128) then + return bits + end + i = i + 1 + pos = pos + 1 + end + bits.low = (bits.low | ((byteBuffer[pos] & 127) << (i * 7))) & 0xFFFFFFFF + return bits + end + + --读最后4位 + while i < 4 do + if (pos == 9) then + bits.high = (bits.high | (byteBuffer[pos] << (i * 7 + 3))) & 0xFFFFFFFF + return bits + end + bits.high = (bits.high | ((byteBuffer[pos] & 127) << (i * 7 + 3))) & 0xFFFFFFFF + if (byteBuffer[pos] < 128) then + return bits + end + i = i + 1 + pos = pos + 1 + end + + return bits +end + +function Long:readLong(buffer) + local byteBuffer = {} + local b = buffer:readByte() + local count = 1 + + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + end + end + end + end + end + end + end + end + + local longValue = fromByteBuffer(byteBuffer) + longValue:decodeZigzagLong() + return longValue +end + +return Long \ No newline at end of file diff --git a/protocol/src/main/resources/lua/ProtocolManager.lua b/protocol/src/main/resources/lua/ProtocolManager.lua new file mode 100644 index 00000000..d475d16e --- /dev/null +++ b/protocol/src/main/resources/lua/ProtocolManager.lua @@ -0,0 +1,53 @@ +local ByteBuffer = require("LuaProtocol.Buffer.ByteBuffer") + +protocols = {} + +ProtocolManager = {} + +-- table扩展方法,后去set和map的大小 +function table.setSize(set) + local size = 0 + for _,_ in pairs(set) do + size = size + 1 + end + return size +end + + +function table.mapSize(map) + local size = 0 + for _,_ in pairs(map) do + size = size + 1 + end + return size +end + +function ProtocolManager.getProtocol(protocolId) + local protocol = protocols[protocolId] + if protocol == nil then + error("[protocolId:" + protocolId + "]协议不存在") + end + return protocol +end + +function ProtocolManager.write(byteBuffer, packet) + local protocolId = packet:protocolId() + -- 写入协议号 + byteBuffer:writeShort(protocolId) + -- 写入包体 + ProtocolManager.getProtocol(protocolId):write(byteBuffer, packet) +end + +function ProtocolManager.read(byteBuffer) + local protocolId = byteBuffer:readShort() + return ProtocolManager.getProtocol(protocolId):read(byteBuffer) +end + +-- C#传进来的byte数组到lua里就会变成string +function readBytes(bytes) + local byteBuffer = ByteBuffer:new() + byteBuffer:writeBuffer(bytes) + local packet = ProtocolManager.read(byteBuffer) + return packet +end + diff --git a/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java b/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java new file mode 100644 index 00000000..f6bf69d4 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/SpeedTest.java @@ -0,0 +1,619 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol; + + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.google.protobuf.ByteString; +import com.zfoo.protocol.collection.ArrayUtils; +import com.zfoo.protocol.generate.GenerateOperation; +import com.zfoo.protocol.packet.*; +import com.zfoo.protocol.util.StringUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.UnpooledHeapByteBuf; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SpeedTest { + + private static int benchmark = 10_0000; + + /** + * 单线程性能测试 + *

+ * 不使用任何JVM参数:zfoo比kryo快40%,zfoo比protobuf快110% + *

+ * 包体大小: + * 简单对象,zfoo包体大小8,kryo包体大小5,protobuf包体大小8 + * 常规对象,zfoo包体大小547,kryo包体大小594,protobuf包体大小984 + * 复杂对象,zfoo包体大小2214,kryo包体大小2525,protobuf包体大小5091 + */ + @Ignore + @Test + public void singleThreadBenchmarks() { + if (benchmark <= 0 || benchmark >= 10_0000_0000) { + return; + } + System.out.println(StringUtils.MULTIPLE_HYPHENS); + System.out.println(StringUtils.format("[单线程性能测试-->[benchmark:{}]]", benchmark)); + + zfooTest(); + kryoTest(); + protobufTest(); + + benchmark = benchmark * 2; + singleThreadBenchmarks(); + } + + /** + * 多线程性能测试 + */ + @Ignore + @Test + public void multipleThreadBenchmarks() throws InterruptedException { + if (benchmark <= 0 || benchmark >= 10_0000_0000) { + return; + } + System.out.println(StringUtils.MULTIPLE_HYPHENS); + System.out.println(StringUtils.format("[多线程性能测试-->[benchmark:{}]]", benchmark)); + + zfooMultipleThreadTest(); + kryoMultipleThreadTest(); + protobufMultipleThreadTest(); + + benchmark = benchmark * 2; + multipleThreadBenchmarks(); + } + + @Ignore + @Test + public void zfooTest() { + ByteBuf buffer = new UnpooledHeapByteBuf(ByteBufAllocator.DEFAULT, 100, 1_0000); + + // 序列化和反序列化简单对象 + long startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + buffer.clear(); + ProtocolManager.write(buffer, simpleObject); + var packet = ProtocolManager.read(buffer); + } + + System.out.println(StringUtils.format("[zfoo] [简单对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), buffer.writerIndex(), System.currentTimeMillis() - startTime)); + + // 序列化和反序列化常规对象 + startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + buffer.clear(); + ProtocolManager.write(buffer, normalObject); + var packet = ProtocolManager.read(buffer); + } + + System.out.println(StringUtils.format("[zfoo] [常规对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), buffer.writerIndex(), System.currentTimeMillis() - startTime)); + + // 序列化和反序列化复杂对象 + startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + buffer.clear(); + ProtocolManager.write(buffer, complexObject); + var packet = ProtocolManager.read(buffer); + } + + System.out.println(StringUtils.format("[zfoo] [复杂对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), buffer.writerIndex(), System.currentTimeMillis() - startTime)); + } + + @Ignore + @Test + public void kryoTest() { + var kryo = kryos.get(); + + var output = new Output(1_0000); + var input = new Input(); + + // 序列化和反序列化简单对象 + long startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + output.reset(); + input.reset(); + + kryo.writeObject(output, simpleObject); + input.setBuffer(output.getBuffer()); + input.setLimit(output.position()); + + var mess = kryo.readObject(input, SimpleObject.class); + } + + System.out.println(StringUtils.format("[kryo] [简单对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), output.position(), System.currentTimeMillis() - startTime)); + + // 序列化和反序列化常规对象 + startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + output.reset(); + input.reset(); + + kryo.writeObject(output, normalObject); + input.setBuffer(output.getBuffer()); + input.setLimit(output.position()); + + var mess = kryo.readObject(input, NormalObject.class); + } + + System.out.println(StringUtils.format("[kryo] [常规对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), output.position(), System.currentTimeMillis() - startTime)); + + // 序列化和反序列化复杂对象 + startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + output.reset(); + input.reset(); + + kryo.writeObject(output, complexObject); + input.setBuffer(output.getBuffer()); + input.setLimit(output.position()); + + var mess = kryo.readObject(input, ComplexObject.class); + } + System.out.println(StringUtils.format("[kryo] [复杂对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), output.position(), System.currentTimeMillis() - startTime)); + } + + @Ignore + @Test + public void protobufTest() { + try { + var output = new Output(1_0000); + var input = new Input(); + + // 序列化和反序列化简单对象 + long startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + output.reset(); + input.reset(); + + protobufSimpleObject.writeTo(output); + input.setBuffer(output.getBuffer()); + input.setLimit(output.position()); + var mess = ProtobufObject.ProtobufSimpleObject.parseFrom(input); + } + + System.out.println(StringUtils.format("[protobuf] [简单对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), output.position(), System.currentTimeMillis() - startTime)); + + + // 序列化和反序列化常规对象 + startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + output.reset(); + input.reset(); + + protobufNormalObject.writeTo(output); + input.setBuffer(output.getBuffer()); + input.setLimit(output.position()); + var mess = ProtobufObject.ProtobufNormalObject.parseFrom(input); + } + + System.out.println(StringUtils.format("[protobuf] [常规对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), output.position(), System.currentTimeMillis() - startTime)); + + + // 序列化和反序列化复杂对象 + startTime = System.currentTimeMillis(); + for (int i = 0; i < benchmark; i++) { + output.reset(); + input.reset(); + + protobufComplexObject.writeTo(output); + input.setBuffer(output.getBuffer()); + input.setLimit(output.position()); + var mess = ProtobufObject.ProtobufComplexObject.parseFrom(input); + } + + System.out.println(StringUtils.format("[protobuf] [复杂对象] [thread:{}] [size:{}] [time:{}]", Thread.currentThread().getName(), output.position(), System.currentTimeMillis() - startTime)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Ignore + @Test + public void zfooMultipleThreadTest() throws InterruptedException { + var countdown = new CountDownLatch(threadNum); + for (var i = 0; i < threadNum; i++) { + executors[i].execute(() -> { + zfooTest(); + countdown.countDown(); + }); + } + countdown.await(); + } + + @Ignore + @Test + public void kryoMultipleThreadTest() throws InterruptedException { + var countdown = new CountDownLatch(threadNum); + for (var i = 0; i < threadNum; i++) { + executors[i].execute(() -> { + kryoTest(); + countdown.countDown(); + }); + } + countdown.await(); + } + + @Ignore + @Test + public void protobufMultipleThreadTest() throws InterruptedException { + var countdown = new CountDownLatch(threadNum); + for (var i = 0; i < threadNum; i++) { + executors[i].execute(() -> { + protobufTest(); + countdown.countDown(); + }); + } + countdown.await(); + } + + private static final int threadNum = Runtime.getRuntime().availableProcessors() - 1; + private static final ExecutorService[] executors = new ExecutorService[threadNum]; + + // kryo协议注册 + private static final ThreadLocal kryos = new ThreadLocal<>() { + @Override + protected Kryo initialValue() { + var kryo = new Kryo(); + kryo.register(ComplexObject.class); + kryo.register(NormalObject.class); + kryo.register(SimpleObject.class); + kryo.register(ObjectA.class); + kryo.register(ObjectB.class); + kryo.register(byte[].class); + kryo.register(Byte[].class); + kryo.register(short[].class); + kryo.register(Short[].class); + kryo.register(int[].class); + kryo.register(Integer[].class); + kryo.register(long[].class); + kryo.register(Long[].class); + kryo.register(float[].class); + kryo.register(Float[].class); + kryo.register(double[].class); + kryo.register(Double[].class); + kryo.register(boolean[].class); + kryo.register(Boolean[].class); + kryo.register(char[].class); + kryo.register(Character[].class); + kryo.register(String[].class); + kryo.register(ObjectA[].class); + kryo.register(ArrayList.class); + kryo.register(HashSet.class); + kryo.register(HashMap.class); + // 关闭循环引用,提高性能 + kryo.setReferences(false); + return kryo; + } + }; + + static { + var op = GenerateOperation.NO_OPERATION; +// op.setGenerateLuaProtocol(true); +// op.setGenerateCsharpProtocol(true); +// op.setGenerateJsProtocol(true); +// op.setFoldProtocol(true); + // zfoo协议注册 + ProtocolManager.initProtocol(Set.of(ComplexObject.class, NormalObject.class, SimpleObject.class, ObjectA.class, ObjectB.class), op); + + for (int i = 0; i < executors.length; i++) { + executors[i] = Executors.newSingleThreadExecutor(); + } + } + + // -------------------------------------------以下为测试用例--------------------------------------------------------------- + private static byte byteValue = 99; + private static short shortValue = 9999; + private static int intValue = 99999999; + private static long longValue = 9999999999999999L; + private static float floatValue = 99999999.9F; + private static double doubleValue = 99999999.9D; + private static char charValue = 'c'; + private static String charValueString = "c"; + private static String stringValue = "hello"; + + + private static boolean[] booleanArray = new boolean[]{true, false, true, false, true}; + private static byte[] byteArray = new byte[]{Byte.MIN_VALUE, -99, 0, 99, Byte.MAX_VALUE}; + private static short[] shortArray = new short[]{Short.MIN_VALUE, -99, 0, 99, Short.MAX_VALUE}; + private static int[] intArray = new int[]{Integer.MIN_VALUE, -99999999, -99, 0, 99, 99999999, Integer.MAX_VALUE}; + private static int[] intArray1 = new int[]{Integer.MIN_VALUE, -99999999, -99, 0, 99, 99999999, Integer.MAX_VALUE - 1}; + private static int[] intArray2 = new int[]{Integer.MIN_VALUE, -99999999, -99, 0, 99, 99999999, Integer.MAX_VALUE - 2}; + private static long[] longArray = new long[]{Long.MIN_VALUE, -9999999999999999L, -99999999L, -99L, 0L, 99L, 99999999L, 9999999999999999L, Long.MAX_VALUE}; + private static float[] floatArray = new float[]{Float.MIN_VALUE, -99999999.9F, -99.9F, 0F, 99.9F, 99999999.9F, Float.MAX_VALUE}; + private static double[] doubleArray = new double[]{Double.MIN_VALUE, -99999999.9F, -99.9D, 0D, 99.9D, 99999999.9F, Double.MAX_VALUE}; + private static char[] charArray = new char[]{'a', 'b', 'c', 'd', 'e'}; + private static String[] stringArray = new String[]{"a", "b", "c", "d", "e"}; + + private static ObjectA objectA = new ObjectA(); + private static ObjectB objectB = new ObjectB(); + private static Map mapWithInteger = new HashMap<>(Map.of(Integer.MIN_VALUE, "a", -99, "b", 0, "c", 99, "d", Integer.MAX_VALUE, "e")); + private static List listWithInteger = new ArrayList<>(ArrayUtils.toList(intArray)); + private static List listWithInteger1 = new ArrayList<>(ArrayUtils.toList(intArray1)); + private static List listWithInteger2 = new ArrayList<>(ArrayUtils.toList(intArray2)); + private static List listWithObject = new ArrayList<>(List.of(objectA, objectA, objectA)); + private static List> listListWithObject = new ArrayList<>(List.of(listWithObject, listWithObject, listWithObject)); + private static List> listListWithInteger = new ArrayList<>(List.of(listWithInteger, listWithInteger, listWithInteger)); + private static List>> listListListWithInteger = new ArrayList<>(List.of(listListWithInteger, listListWithInteger, listListWithInteger)); + private static List listWithString = new ArrayList<>(ArrayUtils.toList(stringArray)); + private static Set setWithInteger = new HashSet<>(ArrayUtils.toList(intArray)); + private static Set>> setSetListWithInteger = new HashSet<>(Set.of(new HashSet<>(Set.of(listWithInteger)), new HashSet<>(Set.of(listWithInteger1)), new HashSet<>(Set.of(listWithInteger2)))); + private static Set> setSetWithObject = new HashSet<>(Set.of(new HashSet<>(Set.of(objectA)))); + private static Set setWithString = new HashSet<>(ArrayUtils.toList(stringArray)); + private static Map mapWithObject = new HashMap<>(Map.of(1, objectA, 2, objectA, 3, objectA)); + private static Map> mapWithList = new HashMap<>(Map.of(objectA, listWithInteger)); + private static Map>, List>>> mapWithListList = new HashMap<>(Map.of(new ArrayList<>(List.of(listWithObject, listWithObject, listWithObject)), listListListWithInteger)); + private static List> listMap = new ArrayList<>(List.of(mapWithInteger, mapWithInteger, mapWithInteger)); + private static Set> setMapWithInteger = new HashSet<>(Set.of(mapWithInteger)); + private static Map>, Set>> mapListSet = new HashMap<>(Map.of(listMap, setMapWithInteger)); + private static Byte[] byteBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(byteArray), Byte.class); + private static Short[] shortBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(shortArray), Short.class); + private static Integer[] integerArray = ArrayUtils.listToArray(ArrayUtils.toList(intArray), Integer.class); + private static Long[] longBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(longArray), Long.class); + private static List listWithLong = ArrayUtils.toList(longArray); + private static Float[] floatBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(floatArray), Float.class); + private static List listWithFloat = ArrayUtils.toList(floatArray); + private static Double[] doubleBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(doubleArray), Double.class); + private static List listWithDouble = ArrayUtils.toList(doubleArray); + private static Boolean[] booleanBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(booleanArray), Boolean.class); + private static List listWithBoolean = ArrayUtils.toList(booleanArray); + private static Character[] charBoxArray = ArrayUtils.listToArray(ArrayUtils.toList(charArray), Character.class); + private static ComplexObject complexObject = new ComplexObject(); + private static NormalObject normalObject = new NormalObject(); + private static SimpleObject simpleObject = new SimpleObject(); + private static ProtobufObject.ProtobufComplexObject protobufComplexObject = null; + private static ProtobufObject.ProtobufNormalObject protobufNormalObject = null; + private static ProtobufObject.ProtobufSimpleObject protobufSimpleObject = null; + + static { + objectA.setA(Integer.MAX_VALUE); + objectA.setM(mapWithInteger); + objectA.setObjectB(objectB); + objectB.setFlag(false); + } + + static { + complexObject.setA(byteValue); + complexObject.setAa(byteValue); + complexObject.setAaa(byteArray); + complexObject.setAaaa(byteBoxArray); + complexObject.setB(shortValue); + complexObject.setBb(shortValue); + complexObject.setBbb(shortArray); + complexObject.setBbbb(shortBoxArray); + complexObject.setC(intValue); + complexObject.setCc(intValue); + complexObject.setCcc(intArray); + complexObject.setCccc(integerArray); + complexObject.setD(longValue); + complexObject.setDd(longValue); + complexObject.setDdd(longArray); + complexObject.setDddd(longBoxArray); + complexObject.setE(floatValue); + complexObject.setEe(floatValue); + complexObject.setEee(floatArray); + complexObject.setEeee(floatBoxArray); + complexObject.setF(doubleValue); + complexObject.setFf(doubleValue); + complexObject.setFff(doubleArray); + complexObject.setFfff(doubleBoxArray); + complexObject.setG(true); + complexObject.setGg(true); + complexObject.setGgg(booleanArray); + complexObject.setGggg(booleanBoxArray); + complexObject.setH(charValue); + complexObject.setHh(charValue); + complexObject.setHhh(charArray); + complexObject.setHhhh(charBoxArray); + complexObject.setJj(stringValue); + complexObject.setJjj(stringArray); + complexObject.setKk(objectA); + complexObject.setKkk(new ObjectA[]{objectA, objectA}); + + complexObject.setL(listWithInteger); + complexObject.setLl(listListListWithInteger); + complexObject.setLll(listListWithObject); + complexObject.setLlll(listWithString); + complexObject.setLllll(listMap); + + complexObject.setM(mapWithInteger); + complexObject.setMm(mapWithObject); + complexObject.setMmm(mapWithList); + complexObject.setMmmm(mapWithListList); + complexObject.setMmmmm(mapListSet); + + complexObject.setS(setWithInteger); + complexObject.setSs(setSetListWithInteger); + complexObject.setSss(setSetWithObject); + complexObject.setSsss(setWithString); + complexObject.setSssss(setMapWithInteger); + + normalObject.setA(byteValue); + normalObject.setAaa(byteArray); + normalObject.setB(shortValue); + normalObject.setC(intValue); + normalObject.setD(longValue); + normalObject.setE(floatValue); + normalObject.setF(doubleValue); + normalObject.setG(true); + normalObject.setJj(stringValue); + normalObject.setKk(objectA); + + normalObject.setL(listWithInteger); + normalObject.setLl(listWithLong); + normalObject.setLll(listWithObject); + normalObject.setLlll(listWithString); + + normalObject.setM(mapWithInteger); + normalObject.setMm(mapWithObject); + + normalObject.setS(setWithInteger); + normalObject.setSsss(setWithString); + + simpleObject.setC(intValue); + simpleObject.setG(true); + + // protobuf相关 + var protobufObjectB = ProtobufObject.ObjectB.newBuilder().setFlag(false).build(); + var protobufObjectA = ProtobufObject.ObjectA.newBuilder() + .setA(Integer.MAX_VALUE) + .putAllM(mapWithInteger) + .setObjectB(protobufObjectB) + .build(); + var protobufListInteger = ProtobufObject.ListInteger.newBuilder().addAllA(listWithInteger).build(); + var protobufListListInteger = ProtobufObject.ListListInteger.newBuilder() + .addAllA(List.of(protobufListInteger, protobufListInteger, protobufListInteger)) + .build(); + var protobufListListListInteger = ProtobufObject.ListListListInteger.newBuilder() + .addAllA(List.of(protobufListListInteger, protobufListListInteger, protobufListListInteger)) + .build(); + var protobufListObjectA = ProtobufObject.ListObjectA.newBuilder() + .addAllA(List.of(protobufObjectA, protobufObjectA, protobufObjectA)) + .build(); + var protobufListListObjectA = ProtobufObject.ListListObjectA.newBuilder() + .addAllA(List.of(protobufListObjectA, protobufListObjectA, protobufListObjectA)) + .build(); + var rawProtobufListListObjectA = List.of(protobufListObjectA, protobufListObjectA, protobufListObjectA); + var rawProtobufListListListInteger = List.of(protobufListListInteger, protobufListListInteger, protobufListListInteger); + var rawProtobufListWithObject = new ArrayList<>(List.of(protobufObjectA, protobufObjectA, protobufObjectA)); + var protobufMapIntegerString = ProtobufObject.MapIntegerString.newBuilder().putAllA(mapWithInteger).build(); + var rawProtobufListMapIntegerString = List.of(protobufMapIntegerString, protobufMapIntegerString, protobufMapIntegerString); + var protobufMapObjectA = ProtobufObject.MapObjectA.newBuilder() + .setKey(protobufObjectA) + .setValue(protobufListInteger) + .build(); + var protobufMapListListObjectA = ProtobufObject.MapListListObjectA.newBuilder() + .setKey(protobufListListObjectA) + .setValue(protobufListListListInteger) + .build(); + var protobufListMapIntegerStringKey = ProtobufObject.ListMapIntegerString.newBuilder() + .addAllA(rawProtobufListMapIntegerString) + .build(); + var protobufListMapIntegerStringValue = ProtobufObject.ListMapIntegerString.newBuilder() + .addAllA(List.of(protobufMapIntegerString)) + .build(); + var protobufMapListMapInteger = ProtobufObject.MapListMapInteger.newBuilder() + .setKey(protobufListMapIntegerStringKey) + .setValue(protobufListMapIntegerStringValue) + .build(); + + + var protobufComplexBuilder = ProtobufObject.ProtobufComplexObject.newBuilder(); + var protobufNormalBuilder = ProtobufObject.ProtobufNormalObject.newBuilder(); + var protobufSimpleBuilder = ProtobufObject.ProtobufSimpleObject.newBuilder(); + protobufComplexBuilder.setA(byteValue); + protobufComplexBuilder.setAa(byteValue); + protobufComplexBuilder.setAaa(ByteString.copyFrom(byteArray)); + protobufComplexBuilder.setAaaa(ByteString.copyFrom(byteArray)); + protobufComplexBuilder.setB(shortValue); + protobufComplexBuilder.setBb(shortValue); + protobufComplexBuilder.setBbb(ByteString.copyFrom(byteArray)); + protobufComplexBuilder.setBbbb(ByteString.copyFrom(byteArray)); + protobufComplexBuilder.setC(intValue); + protobufComplexBuilder.setCc(intValue); + protobufComplexBuilder.addAllCcc(listWithInteger); + protobufComplexBuilder.addAllCccc(listWithInteger); + protobufComplexBuilder.setD(longValue); + protobufComplexBuilder.setDd(longValue); + protobufComplexBuilder.addAllDdd(listWithLong); + protobufComplexBuilder.addAllDddd(listWithLong); + protobufComplexBuilder.setE(floatValue); + protobufComplexBuilder.setEe(floatValue); + protobufComplexBuilder.addAllEee(listWithFloat); + protobufComplexBuilder.addAllEeee(listWithFloat); + protobufComplexBuilder.setF(doubleValue); + protobufComplexBuilder.setFf(doubleValue); + protobufComplexBuilder.addAllFff(listWithDouble); + protobufComplexBuilder.addAllFfff(listWithDouble); + protobufComplexBuilder.setG(true); + protobufComplexBuilder.setGg(true); + protobufComplexBuilder.addAllGgg(listWithBoolean); + protobufComplexBuilder.addAllGggg(listWithBoolean); + protobufComplexBuilder.setH(charValueString); + protobufComplexBuilder.setHh(charValueString); + protobufComplexBuilder.addAllHhh(listWithString); + protobufComplexBuilder.addAllHhhh(listWithString); + protobufComplexBuilder.setJj(stringValue); + protobufComplexBuilder.addAllJjj(listWithString); + protobufComplexBuilder.setKk(protobufObjectA); + protobufComplexBuilder.addAllKkk(rawProtobufListWithObject); + protobufComplexBuilder.addAllL(listWithInteger); + protobufComplexBuilder.addAllLl(rawProtobufListListListInteger); + protobufComplexBuilder.addAllLll(rawProtobufListListObjectA); + protobufComplexBuilder.addAllLlll(listWithString); + protobufComplexBuilder.addAllLllll(rawProtobufListMapIntegerString); + protobufComplexBuilder.putAllM(mapWithInteger); + protobufComplexBuilder.putAllMm(Map.of(1, protobufObjectA, 2, protobufObjectA, 3, protobufObjectA)); + protobufComplexBuilder.addMmm(protobufMapObjectA); + protobufComplexBuilder.addMmmm(protobufMapListListObjectA); + protobufComplexBuilder.addMmmmm(protobufMapListMapInteger); + protobufComplexBuilder.addAllS(listWithInteger); + protobufComplexBuilder.addAllSs(rawProtobufListListListInteger); + protobufComplexBuilder.addAllSss(rawProtobufListListObjectA); + protobufComplexBuilder.addAllSsss(listWithString); + protobufComplexBuilder.addAllSssss(rawProtobufListMapIntegerString); + protobufComplexObject = protobufComplexBuilder.build(); + + protobufNormalBuilder.setA(byteValue); + protobufNormalBuilder.setAaa(ByteString.copyFrom(byteArray)); + protobufNormalBuilder.setB(shortValue); + protobufNormalBuilder.setC(intValue); + protobufNormalBuilder.setD(intValue); + protobufNormalBuilder.setE(longValue); + protobufNormalBuilder.setF(doubleValue); + protobufNormalBuilder.setG(true); + protobufNormalBuilder.setJj(stringValue); + protobufNormalBuilder.setKk(protobufObjectA); + protobufNormalBuilder.addAllL(listWithInteger); + protobufNormalBuilder.addAllLl(listWithLong); + protobufNormalBuilder.addAllLll(rawProtobufListWithObject); + protobufNormalBuilder.addAllLlll(listWithString); + protobufNormalBuilder.putAllM(mapWithInteger); + protobufNormalBuilder.putAllMm(Map.of(1, protobufObjectA, 2, protobufObjectA, 3, protobufObjectA)); + protobufNormalBuilder.addAllS(listWithInteger); + protobufNormalBuilder.addAllSsss(listWithString); + protobufNormalObject = protobufNormalBuilder.build(); + + protobufSimpleBuilder.setC(intValue); + protobufSimpleBuilder.setG(true); + protobufSimpleObject = protobufSimpleBuilder.build(); + } + + + @Test + public void cmEnhanceMessTest() { + var buffer = new UnpooledHeapByteBuf(ByteBufAllocator.DEFAULT, 100, 1_0000); + // 序列化和反序列化简单对象 + ProtocolManager.write(buffer, normalObject); + var packet = ProtocolManager.read(buffer); + buffer.clear(); + + ProtocolManager.write(buffer, complexObject); + packet = ProtocolManager.read(buffer); + buffer.clear(); + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/A.java b/protocol/src/test/java/com/zfoo/protocol/javassist/A.java new file mode 100644 index 00000000..5eaae9b7 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/A.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +/** + * @author jaysunxiao + * @version 3.0 + */ + +public class A { + public int a; + public int b; + public int c; + + public A() { + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/B.java b/protocol/src/test/java/com/zfoo/protocol/javassist/B.java new file mode 100644 index 00000000..3caa9334 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/B.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class B { + private int a; + private int b; + private int c; + + public B() { + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/C.java b/protocol/src/test/java/com/zfoo/protocol/javassist/C.java new file mode 100644 index 00000000..a10ad0c3 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/C.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class C { + private int a; + private int b; + private int c; + + public C() { + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/D.java b/protocol/src/test/java/com/zfoo/protocol/javassist/D.java new file mode 100644 index 00000000..441ec5a4 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/D.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class D { + private int a; + private int b; + private int c; + + public D() { + } + +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/Hello.java b/protocol/src/test/java/com/zfoo/protocol/javassist/Hello.java new file mode 100644 index 00000000..770eb879 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/Hello.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Hello { + public void say() { + System.out.println("Hello"); + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/IDGet.java b/protocol/src/test/java/com/zfoo/protocol/javassist/IDGet.java new file mode 100644 index 00000000..e24c7c8c --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/IDGet.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IDGet { + int getA(); + + void setA(int a); + + int getB(); + + void setB(int b); + + int getC(); + + void setC(int c); +} diff --git a/protocol/src/test/java/com/zfoo/protocol/javassist/JavassistTest.java b/protocol/src/test/java/com/zfoo/protocol/javassist/JavassistTest.java new file mode 100644 index 00000000..8f10db1f --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/javassist/JavassistTest.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.javassist; + +import javassist.*; +import org.junit.Ignore; +import org.junit.Test; + +import java.lang.reflect.Modifier; +import java.lang.reflect.*; + +/* + 测试javassist生成的类和普通用new关键字创建出来的类之间的区别


+ 测试发现两者从访问方法和访问变量的速度几乎没有什么区别,可以用javassist代理其它的类,不会影响速度
+ */ + +public class JavassistTest { + + + public static final int INTERVAL_TIME = 1000; + + public static final int A_CONSTANT = 99999; + public static final int B_CONSTANT = 888888888; + public static final int C_CONSTANT = 7777777; + + public static final int INT_CONSTANT = 999999; + + + /** + * 直接访问public的访问速度 + */ + public void testA() { + A a = new A(); + + long start = System.currentTimeMillis(); + long count = 0; + while (System.currentTimeMillis() - start <= INTERVAL_TIME) { + a.a = A_CONSTANT; + a.b = B_CONSTANT; + a.c = C_CONSTANT; + count++; + } + + System.out.println(count); + } + + /** + * 用get和set方法访问成员变量的速度 + */ + public void testB() { + B b = new B(); + + long start = System.currentTimeMillis(); + long count = 0; + while (System.currentTimeMillis() - start <= INTERVAL_TIME) { + b.setA(A_CONSTANT); + b.setB(B_CONSTANT); + b.setC(C_CONSTANT); + count++; + } + + System.out.println(count); + } + + /* + 通过反射直接操作属性访问变量的速度 + */ + public void testC() throws NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + Field aField = C.class.getDeclaredField("a"); + aField.setAccessible(true); + Field bField = C.class.getDeclaredField("b"); + bField.setAccessible(true); + Field cField = C.class.getDeclaredField("c"); + cField.setAccessible(true); + + Method aSetMethod = C.class.getDeclaredMethod("setA", int.class); + aSetMethod.setAccessible(true); + Method bSetMethod = C.class.getDeclaredMethod("setB", int.class); + bSetMethod.setAccessible(true); + Method cSetMethod = C.class.getDeclaredMethod("setC", int.class); + cSetMethod.setAccessible(true); + + Constructor constructor = C.class.getDeclaredConstructor(null); + constructor.setAccessible(true); + C c = (C) constructor.newInstance(null); + + long start = System.currentTimeMillis(); + long count = 0; + while (System.currentTimeMillis() - start <= INTERVAL_TIME) { + //C c = C.class.newInstance(); + aField.set(c, A_CONSTANT); + bField.set(c, B_CONSTANT); + cField.set(c, C_CONSTANT); + // aSetMethod.invoke(c, A_CONSTANT); + // bSetMethod.invoke(c, B_CONSTANT); + // cSetMethod.invoke(c, C_CONSTANT); + count++; + } + + System.out.println(count); + } + + /* + 通过javassist访问成员变量的速度 + */ + @Ignore + @Test + public void testD() throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + ClassPool classPool = ClassPool.getDefault(); + + CtClass ctClass = classPool.makeClass("com.zfoo.protocol.javassist.DGetClass"); + + ctClass.addInterface(classPool.get(IDGet.class.getCanonicalName())); + + CtField a = new CtField(classPool.get(int.class.getCanonicalName()), "a", ctClass); + a.setModifiers(Modifier.PRIVATE); + ctClass.addField(a); + + CtField b = new CtField(classPool.get(int.class.getCanonicalName()), "b", ctClass); + b.setModifiers(Modifier.PRIVATE); + ctClass.addField(b); + + CtField c = new CtField(classPool.get(int.class.getCanonicalName()), "c", ctClass); + c.setModifiers(Modifier.PRIVATE); + ctClass.addField(c); + + CtConstructor ctConstructor = new CtConstructor(null, ctClass); + ctConstructor.setBody("{}"); + ctClass.addConstructor(ctConstructor); + + CtMethod aGetMethod = new CtMethod(classPool.get(int.class.getCanonicalName()), "getA", null, ctClass); + aGetMethod.setModifiers(Modifier.PUBLIC); + StringBuilder aGetBuilder = new StringBuilder(); + aGetBuilder.append("{return this.a;}"); + aGetMethod.setBody(aGetBuilder.toString()); + ctClass.addMethod(aGetMethod); + + CtMethod aSetMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "setA", classPool.get(new String[]{int.class.getCanonicalName()}), ctClass); + aGetMethod.setModifiers(Modifier.PUBLIC); + StringBuilder aSetBuilder = new StringBuilder(); + aSetBuilder.append("{this.a=$1;}"); + aSetMethod.setBody(aSetBuilder.toString()); + ctClass.addMethod(aSetMethod); + + CtMethod bGetMethod = new CtMethod(classPool.get(int.class.getCanonicalName()), "getB", null, ctClass); + bGetMethod.setModifiers(Modifier.PUBLIC); + StringBuilder bGetBuilder = new StringBuilder(); + bGetBuilder.append("{return this.b;}"); + bGetMethod.setBody(bGetBuilder.toString()); + ctClass.addMethod(bGetMethod); + + CtMethod bSetMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "setB", classPool.get(new String[]{int.class.getCanonicalName()}), ctClass); + aGetMethod.setModifiers(Modifier.PUBLIC); + StringBuilder bSetBuilder = new StringBuilder(); + bSetBuilder.append("{this.b=$1;}"); + bSetMethod.setBody(bSetBuilder.toString()); + ctClass.addMethod(bSetMethod); + + CtMethod cGetMethod = new CtMethod(classPool.get(int.class.getCanonicalName()), "getC", null, ctClass); + cGetMethod.setModifiers(Modifier.PUBLIC); + StringBuilder cGetBuilder = new StringBuilder(); + cGetBuilder.append("{return this.c;}"); + cGetMethod.setBody(cGetBuilder.toString()); + ctClass.addMethod(cGetMethod); + + CtMethod cSetMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "setC", classPool.get(new String[]{int.class.getCanonicalName()}), ctClass); + aGetMethod.setModifiers(Modifier.PUBLIC); + StringBuilder cSetBuilder = new StringBuilder(); + cSetBuilder.append("{this.c=$1;}"); + cSetMethod.setBody(cSetBuilder.toString()); + ctClass.addMethod(cSetMethod); + + Class clazz = ctClass.toClass(IDGet.class); + Constructor constructor = clazz.getConstructor(null); + IDGet d = (IDGet) constructor.newInstance(null); + + long start = System.currentTimeMillis(); + long count = 0; + while (System.currentTimeMillis() - start <= INTERVAL_TIME) { + d.setA(A_CONSTANT); + d.setB(B_CONSTANT); + d.setC(C_CONSTANT); + count++; + } + + System.out.println(count); + + // d.setA(A_CONSTANT); + // System.out.println(d.getA()); + // d.setB(B_CONSTANT); + // System.out.println(d.getB()); + // d.setC(C_CONSTANT); + // System.out.println(d.getC()); + } + + + @Ignore + @Test + public void testAll() throws NoSuchMethodException, IllegalAccessException, InstantiationException, CannotCompileException, NotFoundException, InvocationTargetException, NoSuchFieldException { + testA(); + testB(); + testC(); + testD(); + } + + +} + diff --git a/protocol/src/test/java/com/zfoo/protocol/packet/ComplexObject.java b/protocol/src/test/java/com/zfoo/protocol/packet/ComplexObject.java new file mode 100644 index 00000000..27f69372 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/packet/ComplexObject.java @@ -0,0 +1,596 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.*; + +/** + * 复杂的对象 + * 包括了各种复杂的结构,数组,List,Set,Map + * + * @author jaysunxiao + * @version 3.0 + */ +public class ComplexObject implements IPacket { + + public static final transient short PROTOCOL_ID = 1160; + + // byte类型,最简单的整形 + private byte a; + // byte的包装类型 + // 优先使用基础类型,包装类型会有装箱拆箱 + private Byte aa; + /** + * 数组类型 + */ + private byte[] aaa; + private Byte[] aaaa; + + private short b; + private Short bb; + private short[] bbb; + private Short[] bbbb; + + private int c; + private Integer cc; + private int[] ccc; + private Integer[] cccc; + + private long d; + private Long dd; + private long[] ddd; + private Long[] dddd; + + private float e; + private Float ee; + private float[] eee; + private Float[] eeee; + + private double f; + private Double ff; + private double[] fff; + private Double[] ffff; + + private boolean g; + private Boolean gg; + private boolean[] ggg; + private Boolean[] gggg; + + private char h; + private Character hh; + private char[] hhh; + private Character[] hhhh; + + private String jj; + private String[] jjj; + + private ObjectA kk; + private ObjectA[] kkk; + + + private List l; + private List>> ll; + private List> lll; + private List llll; + private List> lllll; + + private Map m; + private Map mm; + private Map> mmm; + private Map>, List>>> mmmm; + private Map>, Set>> mmmmm; + + private Set s; + private Set>> ss; + private Set> sss; + private Set ssss; + private Set> sssss; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public byte getA() { + return a; + } + + public void setA(byte a) { + this.a = a; + } + + public Byte getAa() { + return aa; + } + + public void setAa(Byte aa) { + this.aa = aa; + } + + public byte[] getAaa() { + return aaa; + } + + public void setAaa(byte[] aaa) { + this.aaa = aaa; + } + + public Byte[] getAaaa() { + return aaaa; + } + + public void setAaaa(Byte[] aaaa) { + this.aaaa = aaaa; + } + + public short getB() { + return b; + } + + public void setB(short b) { + this.b = b; + } + + public Short getBb() { + return bb; + } + + public void setBb(Short bb) { + this.bb = bb; + } + + public short[] getBbb() { + return bbb; + } + + public void setBbb(short[] bbb) { + this.bbb = bbb; + } + + public Short[] getBbbb() { + return bbbb; + } + + public void setBbbb(Short[] bbbb) { + this.bbbb = bbbb; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public Integer getCc() { + return cc; + } + + public void setCc(Integer cc) { + this.cc = cc; + } + + public int[] getCcc() { + return ccc; + } + + public void setCcc(int[] ccc) { + this.ccc = ccc; + } + + public Integer[] getCccc() { + return cccc; + } + + public void setCccc(Integer[] cccc) { + this.cccc = cccc; + } + + public long getD() { + return d; + } + + public void setD(long d) { + this.d = d; + } + + public Long getDd() { + return dd; + } + + public void setDd(Long dd) { + this.dd = dd; + } + + public long[] getDdd() { + return ddd; + } + + public void setDdd(long[] ddd) { + this.ddd = ddd; + } + + public Long[] getDddd() { + return dddd; + } + + public void setDddd(Long[] dddd) { + this.dddd = dddd; + } + + public float getE() { + return e; + } + + public void setE(float e) { + this.e = e; + } + + public Float getEe() { + return ee; + } + + public void setEe(Float ee) { + this.ee = ee; + } + + public float[] getEee() { + return eee; + } + + public void setEee(float[] eee) { + this.eee = eee; + } + + public Float[] getEeee() { + return eeee; + } + + public void setEeee(Float[] eeee) { + this.eeee = eeee; + } + + public double getF() { + return f; + } + + public void setF(double f) { + this.f = f; + } + + public Double getFf() { + return ff; + } + + public void setFf(Double ff) { + this.ff = ff; + } + + public double[] getFff() { + return fff; + } + + public void setFff(double[] fff) { + this.fff = fff; + } + + public Double[] getFfff() { + return ffff; + } + + public void setFfff(Double[] ffff) { + this.ffff = ffff; + } + + public boolean isG() { + return g; + } + + public void setG(boolean g) { + this.g = g; + } + + public Boolean getGg() { + return gg; + } + + public void setGg(Boolean gg) { + this.gg = gg; + } + + public boolean[] getGgg() { + return ggg; + } + + public void setGgg(boolean[] ggg) { + this.ggg = ggg; + } + + public Boolean[] getGggg() { + return gggg; + } + + public void setGggg(Boolean[] gggg) { + this.gggg = gggg; + } + + public char getH() { + return h; + } + + public void setH(char h) { + this.h = h; + } + + public Character getHh() { + return hh; + } + + public void setHh(Character hh) { + this.hh = hh; + } + + public char[] getHhh() { + return hhh; + } + + public void setHhh(char[] hhh) { + this.hhh = hhh; + } + + public Character[] getHhhh() { + return hhhh; + } + + public void setHhhh(Character[] hhhh) { + this.hhhh = hhhh; + } + + public String getJj() { + return jj; + } + + public void setJj(String jj) { + this.jj = jj; + } + + public String[] getJjj() { + return jjj; + } + + public void setJjj(String[] jjj) { + this.jjj = jjj; + } + + public ObjectA getKk() { + return kk; + } + + public void setKk(ObjectA kk) { + this.kk = kk; + } + + public ObjectA[] getKkk() { + return kkk; + } + + public void setKkk(ObjectA[] kkk) { + this.kkk = kkk; + } + + public List getL() { + return l; + } + + public void setL(List l) { + this.l = l; + } + + public List>> getLl() { + return ll; + } + + public void setLl(List>> ll) { + this.ll = ll; + } + + public List> getLll() { + return lll; + } + + public void setLll(List> lll) { + this.lll = lll; + } + + public List getLlll() { + return llll; + } + + public void setLlll(List llll) { + this.llll = llll; + } + + public List> getLllll() { + return lllll; + } + + public void setLllll(List> lllll) { + this.lllll = lllll; + } + + public Map getM() { + return m; + } + + public void setM(Map m) { + this.m = m; + } + + public Map getMm() { + return mm; + } + + public void setMm(Map mm) { + this.mm = mm; + } + + public Map> getMmm() { + return mmm; + } + + public void setMmm(Map> mmm) { + this.mmm = mmm; + } + + public Map>, List>>> getMmmm() { + return mmmm; + } + + public void setMmmm(Map>, List>>> mmmm) { + this.mmmm = mmmm; + } + + public Map>, Set>> getMmmmm() { + return mmmmm; + } + + public void setMmmmm(Map>, Set>> mmmmm) { + this.mmmmm = mmmmm; + } + + public Set getS() { + return s; + } + + public void setS(Set s) { + this.s = s; + } + + public Set>> getSs() { + return ss; + } + + public void setSs(Set>> ss) { + this.ss = ss; + } + + public Set> getSss() { + return sss; + } + + public void setSss(Set> sss) { + this.sss = sss; + } + + public Set getSsss() { + return ssss; + } + + public void setSsss(Set ssss) { + this.ssss = ssss; + } + + public Set> getSssss() { + return sssss; + } + + public void setSssss(Set> sssss) { + this.sssss = sssss; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ComplexObject that = (ComplexObject) o; + return a == that.a && + b == that.b && + c == that.c && + d == that.d && + Float.compare(that.e, e) == 0 && + Double.compare(that.f, f) == 0 && + g == that.g && + h == that.h && + Objects.equals(aa, that.aa) && + Arrays.equals(aaa, that.aaa) && + Arrays.equals(aaaa, that.aaaa) && + Objects.equals(bb, that.bb) && + Arrays.equals(bbb, that.bbb) && + Arrays.equals(bbbb, that.bbbb) && + Objects.equals(cc, that.cc) && + Arrays.equals(ccc, that.ccc) && + Arrays.equals(cccc, that.cccc) && + Objects.equals(dd, that.dd) && + Arrays.equals(ddd, that.ddd) && + Arrays.equals(dddd, that.dddd) && + Objects.equals(ee, that.ee) && + Arrays.equals(eee, that.eee) && + Arrays.equals(eeee, that.eeee) && + Objects.equals(ff, that.ff) && + Arrays.equals(fff, that.fff) && + Arrays.equals(ffff, that.ffff) && + Objects.equals(gg, that.gg) && + Arrays.equals(ggg, that.ggg) && + Arrays.equals(gggg, that.gggg) && + Objects.equals(hh, that.hh) && + Arrays.equals(hhh, that.hhh) && + Arrays.equals(hhhh, that.hhhh) && + Objects.equals(jj, that.jj) && + Arrays.equals(jjj, that.jjj) && + Objects.equals(kk, that.kk) && + Arrays.equals(kkk, that.kkk) && + Objects.equals(l, that.l) && + Objects.equals(ll, that.ll) && + Objects.equals(lll, that.lll) && + Objects.equals(llll, that.llll) && + Objects.equals(lllll, that.lllll) && + Objects.equals(m, that.m) && + Objects.equals(mm, that.mm) && + Objects.equals(mmm, that.mmm) && + Objects.equals(mmmm, that.mmmm) && + Objects.equals(mmmmm, that.mmmmm) && + Objects.equals(s, that.s) && + Objects.equals(ss, that.ss) && + Objects.equals(sss, that.sss) && + Objects.equals(ssss, that.ssss) && + Objects.equals(sssss, that.sssss); + } + + @Override + public int hashCode() { + int result = Objects.hash(a, aa, b, bb, c, cc, d, dd, e, ee, f, ff, g, gg, h, hh, jj, kk, l, ll, lll, llll, lllll, m, mm, mmm, mmmm, mmmmm, s, ss, sss, ssss, sssss); + result = 31 * result + Arrays.hashCode(aaa); + result = 31 * result + Arrays.hashCode(aaaa); + result = 31 * result + Arrays.hashCode(bbb); + result = 31 * result + Arrays.hashCode(bbbb); + result = 31 * result + Arrays.hashCode(ccc); + result = 31 * result + Arrays.hashCode(cccc); + result = 31 * result + Arrays.hashCode(ddd); + result = 31 * result + Arrays.hashCode(dddd); + result = 31 * result + Arrays.hashCode(eee); + result = 31 * result + Arrays.hashCode(eeee); + result = 31 * result + Arrays.hashCode(fff); + result = 31 * result + Arrays.hashCode(ffff); + result = 31 * result + Arrays.hashCode(ggg); + result = 31 * result + Arrays.hashCode(gggg); + result = 31 * result + Arrays.hashCode(hhh); + result = 31 * result + Arrays.hashCode(hhhh); + result = 31 * result + Arrays.hashCode(jjj); + result = 31 * result + Arrays.hashCode(kkk); + return result; + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/packet/NormalObject.java b/protocol/src/test/java/com/zfoo/protocol/packet/NormalObject.java new file mode 100644 index 00000000..0e165377 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/packet/NormalObject.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NormalObject implements IPacket { + + public static final transient short PROTOCOL_ID = 1161; + + private byte a; + private byte[] aaa; + + private short b; + + private int c; + + private long d; + + private float e; + + private double f; + + private boolean g; + + private String jj; + + private ObjectA kk; + + + private List l; + private List ll; + private List lll; + private List llll; + + private Map m; + private Map mm; + + private Set s; + private Set ssss; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public byte getA() { + return a; + } + + public void setA(byte a) { + this.a = a; + } + + public byte[] getAaa() { + return aaa; + } + + public void setAaa(byte[] aaa) { + this.aaa = aaa; + } + + public short getB() { + return b; + } + + public void setB(short b) { + this.b = b; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public long getD() { + return d; + } + + public void setD(long d) { + this.d = d; + } + + public float getE() { + return e; + } + + public void setE(float e) { + this.e = e; + } + + public double getF() { + return f; + } + + public void setF(double f) { + this.f = f; + } + + public boolean isG() { + return g; + } + + public void setG(boolean g) { + this.g = g; + } + + public String getJj() { + return jj; + } + + public void setJj(String jj) { + this.jj = jj; + } + + public ObjectA getKk() { + return kk; + } + + public void setKk(ObjectA kk) { + this.kk = kk; + } + + public List getL() { + return l; + } + + public void setL(List l) { + this.l = l; + } + + public List getLl() { + return ll; + } + + public void setLl(List ll) { + this.ll = ll; + } + + public List getLll() { + return lll; + } + + public void setLll(List lll) { + this.lll = lll; + } + + public List getLlll() { + return llll; + } + + public void setLlll(List llll) { + this.llll = llll; + } + + public Map getM() { + return m; + } + + public void setM(Map m) { + this.m = m; + } + + public Map getMm() { + return mm; + } + + public void setMm(Map mm) { + this.mm = mm; + } + + public Set getS() { + return s; + } + + public void setS(Set s) { + this.s = s; + } + + public Set getSsss() { + return ssss; + } + + public void setSsss(Set ssss) { + this.ssss = ssss; + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/packet/ObjectA.java b/protocol/src/test/java/com/zfoo/protocol/packet/ObjectA.java new file mode 100644 index 00000000..e092c79a --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/packet/ObjectA.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Map; +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ObjectA implements IPacket { + + public static final transient short PROTOCOL_ID = 1116; + + private int a; + + private Map m; + + private ObjectB objectB; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public Map getM() { + return m; + } + + public void setM(Map m) { + this.m = m; + } + + public ObjectB getObjectB() { + return objectB; + } + + public void setObjectB(ObjectB objectB) { + this.objectB = objectB; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ObjectA objectA = (ObjectA) o; + return a == objectA.a && + Objects.equals(m, objectA.m) && + Objects.equals(objectB, objectA.objectB); + } + + @Override + public int hashCode() { + return Objects.hash(a, m, objectB); + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/packet/ObjectB.java b/protocol/src/test/java/com/zfoo/protocol/packet/ObjectB.java new file mode 100644 index 00000000..4eb35f64 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/packet/ObjectB.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.packet; + +import com.zfoo.protocol.IPacket; + +import java.util.Objects; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ObjectB implements IPacket { + + public static final transient short PROTOCOL_ID = 1117; + + private boolean flag; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public boolean isFlag() { + return flag; + } + + public void setFlag(boolean flag) { + this.flag = flag; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ObjectB objectB = (ObjectB) o; + return flag == objectB.flag; + } + + @Override + public int hashCode() { + return Objects.hash(flag); + } +} + diff --git a/protocol/src/test/java/com/zfoo/protocol/packet/ProtobufObject.java b/protocol/src/test/java/com/zfoo/protocol/packet/ProtobufObject.java new file mode 100644 index 00000000..58eb9845 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/packet/ProtobufObject.java @@ -0,0 +1,23534 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: speed.proto + +package com.zfoo.protocol.packet; + +public final class ProtobufObject { + private ProtobufObject() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface ObjectBOrBuilder extends + // @@protoc_insertion_point(interface_extends:ObjectB) + com.google.protobuf.MessageOrBuilder { + + /** + * bool flag = 1; + */ + boolean getFlag(); + } + /** + * Protobuf type {@code ObjectB} + */ + public static final class ObjectB extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ObjectB) + ObjectBOrBuilder { + private static final long serialVersionUID = 0L; + // Use ObjectB.newBuilder() to construct. + private ObjectB(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ObjectB() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ObjectB(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ObjectB( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + flag_ = input.readBool(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectB_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectB_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ObjectB.class, com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder.class); + } + + public static final int FLAG_FIELD_NUMBER = 1; + private boolean flag_; + /** + * bool flag = 1; + */ + public boolean getFlag() { + return flag_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (flag_ != false) { + output.writeBool(1, flag_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (flag_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(1, flag_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ObjectB)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ObjectB other = (com.zfoo.protocol.packet.ProtobufObject.ObjectB) obj; + + if (getFlag() + != other.getFlag()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + FLAG_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getFlag()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ObjectB prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ObjectB} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ObjectB) + com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectB_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectB_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ObjectB.class, com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ObjectB.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + flag_ = false; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectB_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectB getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ObjectB.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectB build() { + com.zfoo.protocol.packet.ProtobufObject.ObjectB result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectB buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ObjectB result = new com.zfoo.protocol.packet.ProtobufObject.ObjectB(this); + result.flag_ = flag_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ObjectB) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ObjectB)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ObjectB other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ObjectB.getDefaultInstance()) return this; + if (other.getFlag() != false) { + setFlag(other.getFlag()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ObjectB parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ObjectB) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private boolean flag_ ; + /** + * bool flag = 1; + */ + public boolean getFlag() { + return flag_; + } + /** + * bool flag = 1; + */ + public Builder setFlag(boolean value) { + + flag_ = value; + onChanged(); + return this; + } + /** + * bool flag = 1; + */ + public Builder clearFlag() { + + flag_ = false; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ObjectB) + } + + // @@protoc_insertion_point(class_scope:ObjectB) + private static final com.zfoo.protocol.packet.ProtobufObject.ObjectB DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ObjectB(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ObjectB getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ObjectB parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ObjectB(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectB getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ObjectAOrBuilder extends + // @@protoc_insertion_point(interface_extends:ObjectA) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 a = 1; + */ + int getA(); + + /** + * map<int32, string> m = 2; + */ + int getMCount(); + /** + * map<int32, string> m = 2; + */ + boolean containsM( + int key); + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getM(); + /** + * map<int32, string> m = 2; + */ + java.util.Map + getMMap(); + /** + * map<int32, string> m = 2; + */ + + java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue); + /** + * map<int32, string> m = 2; + */ + + java.lang.String getMOrThrow( + int key); + + /** + * .ObjectB objectB = 3; + */ + boolean hasObjectB(); + /** + * .ObjectB objectB = 3; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectB getObjectB(); + /** + * .ObjectB objectB = 3; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder getObjectBOrBuilder(); + } + /** + * Protobuf type {@code ObjectA} + */ + public static final class ObjectA extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ObjectA) + ObjectAOrBuilder { + private static final long serialVersionUID = 0L; + // Use ObjectA.newBuilder() to construct. + private ObjectA(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ObjectA() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ObjectA(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ObjectA( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + a_ = input.readInt32(); + break; + } + case 18: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + m_ = com.google.protobuf.MapField.newMapField( + MDefaultEntryHolder.defaultEntry); + mutable_bitField0_ |= 0x00000001; + } + com.google.protobuf.MapEntry + m__ = input.readMessage( + MDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + m_.getMutableMap().put( + m__.getKey(), m__.getValue()); + break; + } + case 26: { + com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder subBuilder = null; + if (objectB_ != null) { + subBuilder = objectB_.toBuilder(); + } + objectB_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectB.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(objectB_); + objectB_ = subBuilder.buildPartial(); + } + + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectA_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + @java.lang.Override + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 2: + return internalGetM(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.class, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private int a_; + /** + * int32 a = 1; + */ + public int getA() { + return a_; + } + + public static final int M_FIELD_NUMBER = 2; + private static final class MDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.Integer, java.lang.String> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectA_MEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.INT32, + 0, + com.google.protobuf.WireFormat.FieldType.STRING, + ""); + } + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> m_; + private com.google.protobuf.MapField + internalGetM() { + if (m_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MDefaultEntryHolder.defaultEntry); + } + return m_; + } + + public int getMCount() { + return internalGetM().getMap().size(); + } + /** + * map<int32, string> m = 2; + */ + + public boolean containsM( + int key) { + + return internalGetM().getMap().containsKey(key); + } + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getM() { + return getMMap(); + } + /** + * map<int32, string> m = 2; + */ + + public java.util.Map getMMap() { + return internalGetM().getMap(); + } + /** + * map<int32, string> m = 2; + */ + + public java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetM().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> m = 2; + */ + + public java.lang.String getMOrThrow( + int key) { + + java.util.Map map = + internalGetM().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public static final int OBJECTB_FIELD_NUMBER = 3; + private com.zfoo.protocol.packet.ProtobufObject.ObjectB objectB_; + /** + * .ObjectB objectB = 3; + */ + public boolean hasObjectB() { + return objectB_ != null; + } + /** + * .ObjectB objectB = 3; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectB getObjectB() { + return objectB_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectB.getDefaultInstance() : objectB_; + } + /** + * .ObjectB objectB = 3; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder getObjectBOrBuilder() { + return getObjectB(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (a_ != 0) { + output.writeInt32(1, a_); + } + com.google.protobuf.GeneratedMessageV3 + .serializeIntegerMapTo( + output, + internalGetM(), + MDefaultEntryHolder.defaultEntry, + 2); + if (objectB_ != null) { + output.writeMessage(3, getObjectB()); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (a_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, a_); + } + for (java.util.Map.Entry entry + : internalGetM().getMap().entrySet()) { + com.google.protobuf.MapEntry + m__ = MDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, m__); + } + if (objectB_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, getObjectB()); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ObjectA)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ObjectA other = (com.zfoo.protocol.packet.ProtobufObject.ObjectA) obj; + + if (getA() + != other.getA()) return false; + if (!internalGetM().equals( + other.internalGetM())) return false; + if (hasObjectB() != other.hasObjectB()) return false; + if (hasObjectB()) { + if (!getObjectB() + .equals(other.getObjectB())) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getA(); + if (!internalGetM().getMap().isEmpty()) { + hash = (37 * hash) + M_FIELD_NUMBER; + hash = (53 * hash) + internalGetM().hashCode(); + } + if (hasObjectB()) { + hash = (37 * hash) + OBJECTB_FIELD_NUMBER; + hash = (53 * hash) + getObjectB().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ObjectA prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ObjectA} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ObjectA) + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectA_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 2: + return internalGetM(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMutableMapField( + int number) { + switch (number) { + case 2: + return internalGetMutableM(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.class, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ObjectA.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + a_ = 0; + + internalGetMutableM().clear(); + if (objectBBuilder_ == null) { + objectB_ = null; + } else { + objectB_ = null; + objectBBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ObjectA_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectA build() { + com.zfoo.protocol.packet.ProtobufObject.ObjectA result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectA buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ObjectA result = new com.zfoo.protocol.packet.ProtobufObject.ObjectA(this); + int from_bitField0_ = bitField0_; + result.a_ = a_; + result.m_ = internalGetM(); + result.m_.makeImmutable(); + if (objectBBuilder_ == null) { + result.objectB_ = objectB_; + } else { + result.objectB_ = objectBBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ObjectA) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ObjectA)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ObjectA other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()) return this; + if (other.getA() != 0) { + setA(other.getA()); + } + internalGetMutableM().mergeFrom( + other.internalGetM()); + if (other.hasObjectB()) { + mergeObjectB(other.getObjectB()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ObjectA parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ObjectA) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int a_ ; + /** + * int32 a = 1; + */ + public int getA() { + return a_; + } + /** + * int32 a = 1; + */ + public Builder setA(int value) { + + a_ = value; + onChanged(); + return this; + } + /** + * int32 a = 1; + */ + public Builder clearA() { + + a_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> m_; + private com.google.protobuf.MapField + internalGetM() { + if (m_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MDefaultEntryHolder.defaultEntry); + } + return m_; + } + private com.google.protobuf.MapField + internalGetMutableM() { + onChanged();; + if (m_ == null) { + m_ = com.google.protobuf.MapField.newMapField( + MDefaultEntryHolder.defaultEntry); + } + if (!m_.isMutable()) { + m_ = m_.copy(); + } + return m_; + } + + public int getMCount() { + return internalGetM().getMap().size(); + } + /** + * map<int32, string> m = 2; + */ + + public boolean containsM( + int key) { + + return internalGetM().getMap().containsKey(key); + } + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getM() { + return getMMap(); + } + /** + * map<int32, string> m = 2; + */ + + public java.util.Map getMMap() { + return internalGetM().getMap(); + } + /** + * map<int32, string> m = 2; + */ + + public java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetM().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> m = 2; + */ + + public java.lang.String getMOrThrow( + int key) { + + java.util.Map map = + internalGetM().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public Builder clearM() { + internalGetMutableM().getMutableMap() + .clear(); + return this; + } + /** + * map<int32, string> m = 2; + */ + + public Builder removeM( + int key) { + + internalGetMutableM().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableM() { + return internalGetMutableM().getMutableMap(); + } + /** + * map<int32, string> m = 2; + */ + public Builder putM( + int key, + java.lang.String value) { + + if (value == null) { throw new java.lang.NullPointerException(); } + internalGetMutableM().getMutableMap() + .put(key, value); + return this; + } + /** + * map<int32, string> m = 2; + */ + + public Builder putAllM( + java.util.Map values) { + internalGetMutableM().getMutableMap() + .putAll(values); + return this; + } + + private com.zfoo.protocol.packet.ProtobufObject.ObjectB objectB_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectB, com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder> objectBBuilder_; + /** + * .ObjectB objectB = 3; + */ + public boolean hasObjectB() { + return objectBBuilder_ != null || objectB_ != null; + } + /** + * .ObjectB objectB = 3; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectB getObjectB() { + if (objectBBuilder_ == null) { + return objectB_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectB.getDefaultInstance() : objectB_; + } else { + return objectBBuilder_.getMessage(); + } + } + /** + * .ObjectB objectB = 3; + */ + public Builder setObjectB(com.zfoo.protocol.packet.ProtobufObject.ObjectB value) { + if (objectBBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + objectB_ = value; + onChanged(); + } else { + objectBBuilder_.setMessage(value); + } + + return this; + } + /** + * .ObjectB objectB = 3; + */ + public Builder setObjectB( + com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder builderForValue) { + if (objectBBuilder_ == null) { + objectB_ = builderForValue.build(); + onChanged(); + } else { + objectBBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ObjectB objectB = 3; + */ + public Builder mergeObjectB(com.zfoo.protocol.packet.ProtobufObject.ObjectB value) { + if (objectBBuilder_ == null) { + if (objectB_ != null) { + objectB_ = + com.zfoo.protocol.packet.ProtobufObject.ObjectB.newBuilder(objectB_).mergeFrom(value).buildPartial(); + } else { + objectB_ = value; + } + onChanged(); + } else { + objectBBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ObjectB objectB = 3; + */ + public Builder clearObjectB() { + if (objectBBuilder_ == null) { + objectB_ = null; + onChanged(); + } else { + objectB_ = null; + objectBBuilder_ = null; + } + + return this; + } + /** + * .ObjectB objectB = 3; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder getObjectBBuilder() { + + onChanged(); + return getObjectBFieldBuilder().getBuilder(); + } + /** + * .ObjectB objectB = 3; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder getObjectBOrBuilder() { + if (objectBBuilder_ != null) { + return objectBBuilder_.getMessageOrBuilder(); + } else { + return objectB_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ObjectB.getDefaultInstance() : objectB_; + } + } + /** + * .ObjectB objectB = 3; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectB, com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder> + getObjectBFieldBuilder() { + if (objectBBuilder_ == null) { + objectBBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectB, com.zfoo.protocol.packet.ProtobufObject.ObjectB.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectBOrBuilder>( + getObjectB(), + getParentForChildren(), + isClean()); + objectB_ = null; + } + return objectBBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ObjectA) + } + + // @@protoc_insertion_point(class_scope:ObjectA) + private static final com.zfoo.protocol.packet.ProtobufObject.ObjectA DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ObjectA(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ObjectA getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ObjectA parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ObjectA(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ListIntegerOrBuilder extends + // @@protoc_insertion_point(interface_extends:ListInteger) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated int32 a = 1; + */ + java.util.List getAList(); + /** + * repeated int32 a = 1; + */ + int getACount(); + /** + * repeated int32 a = 1; + */ + int getA(int index); + } + /** + * Protobuf type {@code ListInteger} + */ + public static final class ListInteger extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ListInteger) + ListIntegerOrBuilder { + private static final long serialVersionUID = 0L; + // Use ListInteger.newBuilder() to construct. + private ListInteger(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ListInteger() { + a_ = emptyIntList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ListInteger(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ListInteger( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = newIntList(); + mutable_bitField0_ |= 0x00000001; + } + a_.addInt(input.readInt32()); + break; + } + case 10: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000001) != 0) && input.getBytesUntilLimit() > 0) { + a_ = newIntList(); + mutable_bitField0_ |= 0x00000001; + } + while (input.getBytesUntilLimit() > 0) { + a_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + a_.makeImmutable(); // C + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListInteger.class, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private com.google.protobuf.Internal.IntList a_; + /** + * repeated int32 a = 1; + */ + public java.util.List + getAList() { + return a_; + } + /** + * repeated int32 a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated int32 a = 1; + */ + public int getA(int index) { + return a_.getInt(index); + } + private int aMemoizedSerializedSize = -1; + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (getAList().size() > 0) { + output.writeUInt32NoTag(10); + output.writeUInt32NoTag(aMemoizedSerializedSize); + } + for (int i = 0; i < a_.size(); i++) { + output.writeInt32NoTag(a_.getInt(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + { + int dataSize = 0; + for (int i = 0; i < a_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(a_.getInt(i)); + } + size += dataSize; + if (!getAList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + aMemoizedSerializedSize = dataSize; + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ListInteger)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ListInteger other = (com.zfoo.protocol.packet.ProtobufObject.ListInteger) obj; + + if (!getAList() + .equals(other.getAList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getACount() > 0) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getAList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ListInteger prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ListInteger} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ListInteger) + com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListInteger.class, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ListInteger.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + a_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListInteger_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListInteger getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListInteger build() { + com.zfoo.protocol.packet.ProtobufObject.ListInteger result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListInteger buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ListInteger result = new com.zfoo.protocol.packet.ProtobufObject.ListInteger(this); + int from_bitField0_ = bitField0_; + if (((bitField0_ & 0x00000001) != 0)) { + a_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.a_ = a_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ListInteger) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ListInteger)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ListInteger other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance()) return this; + if (!other.a_.isEmpty()) { + if (a_.isEmpty()) { + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAIsMutable(); + a_.addAll(other.a_); + } + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ListInteger parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ListInteger) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private com.google.protobuf.Internal.IntList a_ = emptyIntList(); + private void ensureAIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + a_ = mutableCopy(a_); + bitField0_ |= 0x00000001; + } + } + /** + * repeated int32 a = 1; + */ + public java.util.List + getAList() { + return ((bitField0_ & 0x00000001) != 0) ? + java.util.Collections.unmodifiableList(a_) : a_; + } + /** + * repeated int32 a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated int32 a = 1; + */ + public int getA(int index) { + return a_.getInt(index); + } + /** + * repeated int32 a = 1; + */ + public Builder setA( + int index, int value) { + ensureAIsMutable(); + a_.setInt(index, value); + onChanged(); + return this; + } + /** + * repeated int32 a = 1; + */ + public Builder addA(int value) { + ensureAIsMutable(); + a_.addInt(value); + onChanged(); + return this; + } + /** + * repeated int32 a = 1; + */ + public Builder addAllA( + java.lang.Iterable values) { + ensureAIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, a_); + onChanged(); + return this; + } + /** + * repeated int32 a = 1; + */ + public Builder clearA() { + a_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ListInteger) + } + + // @@protoc_insertion_point(class_scope:ListInteger) + private static final com.zfoo.protocol.packet.ProtobufObject.ListInteger DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ListInteger(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListInteger getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ListInteger parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ListInteger(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListInteger getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ListListIntegerOrBuilder extends + // @@protoc_insertion_point(interface_extends:ListListInteger) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .ListInteger a = 1; + */ + java.util.List + getAList(); + /** + * repeated .ListInteger a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListInteger getA(int index); + /** + * repeated .ListInteger a = 1; + */ + int getACount(); + /** + * repeated .ListInteger a = 1; + */ + java.util.List + getAOrBuilderList(); + /** + * repeated .ListInteger a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder getAOrBuilder( + int index); + } + /** + * Protobuf type {@code ListListInteger} + */ + public static final class ListListInteger extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ListListInteger) + ListListIntegerOrBuilder { + private static final long serialVersionUID = 0L; + // Use ListListInteger.newBuilder() to construct. + private ListListInteger(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ListListInteger() { + a_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ListListInteger(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ListListInteger( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + a_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListInteger.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.class, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private java.util.List a_; + /** + * repeated .ListInteger a = 1; + */ + public java.util.List getAList() { + return a_; + } + /** + * repeated .ListInteger a = 1; + */ + public java.util.List + getAOrBuilderList() { + return a_; + } + /** + * repeated .ListInteger a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger getA(int index) { + return a_.get(index); + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder getAOrBuilder( + int index) { + return a_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < a_.size(); i++) { + output.writeMessage(1, a_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < a_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, a_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ListListInteger)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ListListInteger other = (com.zfoo.protocol.packet.ProtobufObject.ListListInteger) obj; + + if (!getAList() + .equals(other.getAList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getACount() > 0) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getAList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ListListInteger prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ListListInteger} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ListListInteger) + com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.class, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ListListInteger.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getAFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + aBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListInteger_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger build() { + com.zfoo.protocol.packet.ProtobufObject.ListListInteger result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ListListInteger result = new com.zfoo.protocol.packet.ProtobufObject.ListListInteger(this); + int from_bitField0_ = bitField0_; + if (aBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.a_ = a_; + } else { + result.a_ = aBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ListListInteger) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ListListInteger)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ListListInteger other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()) return this; + if (aBuilder_ == null) { + if (!other.a_.isEmpty()) { + if (a_.isEmpty()) { + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAIsMutable(); + a_.addAll(other.a_); + } + onChanged(); + } + } else { + if (!other.a_.isEmpty()) { + if (aBuilder_.isEmpty()) { + aBuilder_.dispose(); + aBuilder_ = null; + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + aBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getAFieldBuilder() : null; + } else { + aBuilder_.addAllMessages(other.a_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ListListInteger parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ListListInteger) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List a_ = + java.util.Collections.emptyList(); + private void ensureAIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(a_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListInteger, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder> aBuilder_; + + /** + * repeated .ListInteger a = 1; + */ + public java.util.List getAList() { + if (aBuilder_ == null) { + return java.util.Collections.unmodifiableList(a_); + } else { + return aBuilder_.getMessageList(); + } + } + /** + * repeated .ListInteger a = 1; + */ + public int getACount() { + if (aBuilder_ == null) { + return a_.size(); + } else { + return aBuilder_.getCount(); + } + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger getA(int index) { + if (aBuilder_ == null) { + return a_.get(index); + } else { + return aBuilder_.getMessage(index); + } + } + /** + * repeated .ListInteger a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListInteger value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.set(index, value); + onChanged(); + } else { + aBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.set(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder addA(com.zfoo.protocol.packet.ProtobufObject.ListInteger value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(value); + onChanged(); + } else { + aBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListInteger value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(index, value); + onChanged(); + } else { + aBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder addA( + com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder addAllA( + java.lang.Iterable values) { + if (aBuilder_ == null) { + ensureAIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, a_); + onChanged(); + } else { + aBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder clearA() { + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + aBuilder_.clear(); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public Builder removeA(int index) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.remove(index); + onChanged(); + } else { + aBuilder_.remove(index); + } + return this; + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder getABuilder( + int index) { + return getAFieldBuilder().getBuilder(index); + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder getAOrBuilder( + int index) { + if (aBuilder_ == null) { + return a_.get(index); } else { + return aBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ListInteger a = 1; + */ + public java.util.List + getAOrBuilderList() { + if (aBuilder_ != null) { + return aBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(a_); + } + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder addABuilder() { + return getAFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance()); + } + /** + * repeated .ListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder addABuilder( + int index) { + return getAFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance()); + } + /** + * repeated .ListInteger a = 1; + */ + public java.util.List + getABuilderList() { + return getAFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListInteger, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder> + getAFieldBuilder() { + if (aBuilder_ == null) { + aBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListInteger, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder>( + a_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + a_ = null; + } + return aBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ListListInteger) + } + + // @@protoc_insertion_point(class_scope:ListListInteger) + private static final com.zfoo.protocol.packet.ProtobufObject.ListListInteger DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ListListInteger(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListListInteger getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ListListInteger parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ListListInteger(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ListListListIntegerOrBuilder extends + // @@protoc_insertion_point(interface_extends:ListListListInteger) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .ListListInteger a = 1; + */ + java.util.List + getAList(); + /** + * repeated .ListListInteger a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListInteger getA(int index); + /** + * repeated .ListListInteger a = 1; + */ + int getACount(); + /** + * repeated .ListListInteger a = 1; + */ + java.util.List + getAOrBuilderList(); + /** + * repeated .ListListInteger a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getAOrBuilder( + int index); + } + /** + * Protobuf type {@code ListListListInteger} + */ + public static final class ListListListInteger extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ListListListInteger) + ListListListIntegerOrBuilder { + private static final long serialVersionUID = 0L; + // Use ListListListInteger.newBuilder() to construct. + private ListListListInteger(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ListListListInteger() { + a_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ListListListInteger(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ListListListInteger( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + a_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListListInteger.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListListInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListListInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.class, com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private java.util.List a_; + /** + * repeated .ListListInteger a = 1; + */ + public java.util.List getAList() { + return a_; + } + /** + * repeated .ListListInteger a = 1; + */ + public java.util.List + getAOrBuilderList() { + return a_; + } + /** + * repeated .ListListInteger a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getA(int index) { + return a_.get(index); + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getAOrBuilder( + int index) { + return a_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < a_.size(); i++) { + output.writeMessage(1, a_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < a_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, a_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ListListListInteger)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger other = (com.zfoo.protocol.packet.ProtobufObject.ListListListInteger) obj; + + if (!getAList() + .equals(other.getAList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getACount() > 0) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getAList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ListListListInteger prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ListListListInteger} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ListListListInteger) + com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListListInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListListInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.class, com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getAFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + aBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListListInteger_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger build() { + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger result = new com.zfoo.protocol.packet.ProtobufObject.ListListListInteger(this); + int from_bitField0_ = bitField0_; + if (aBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.a_ = a_; + } else { + result.a_ = aBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ListListListInteger) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ListListListInteger)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ListListListInteger other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.getDefaultInstance()) return this; + if (aBuilder_ == null) { + if (!other.a_.isEmpty()) { + if (a_.isEmpty()) { + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAIsMutable(); + a_.addAll(other.a_); + } + onChanged(); + } + } else { + if (!other.a_.isEmpty()) { + if (aBuilder_.isEmpty()) { + aBuilder_.dispose(); + aBuilder_ = null; + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + aBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getAFieldBuilder() : null; + } else { + aBuilder_.addAllMessages(other.a_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ListListListInteger) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List a_ = + java.util.Collections.emptyList(); + private void ensureAIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(a_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder> aBuilder_; + + /** + * repeated .ListListInteger a = 1; + */ + public java.util.List getAList() { + if (aBuilder_ == null) { + return java.util.Collections.unmodifiableList(a_); + } else { + return aBuilder_.getMessageList(); + } + } + /** + * repeated .ListListInteger a = 1; + */ + public int getACount() { + if (aBuilder_ == null) { + return a_.size(); + } else { + return aBuilder_.getCount(); + } + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getA(int index) { + if (aBuilder_ == null) { + return a_.get(index); + } else { + return aBuilder_.getMessage(index); + } + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.set(index, value); + onChanged(); + } else { + aBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.set(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder addA(com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(value); + onChanged(); + } else { + aBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(index, value); + onChanged(); + } else { + aBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder addA( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder addAllA( + java.lang.Iterable values) { + if (aBuilder_ == null) { + ensureAIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, a_); + onChanged(); + } else { + aBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder clearA() { + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + aBuilder_.clear(); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public Builder removeA(int index) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.remove(index); + onChanged(); + } else { + aBuilder_.remove(index); + } + return this; + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder getABuilder( + int index) { + return getAFieldBuilder().getBuilder(index); + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getAOrBuilder( + int index) { + if (aBuilder_ == null) { + return a_.get(index); } else { + return aBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ListListInteger a = 1; + */ + public java.util.List + getAOrBuilderList() { + if (aBuilder_ != null) { + return aBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(a_); + } + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder addABuilder() { + return getAFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()); + } + /** + * repeated .ListListInteger a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder addABuilder( + int index) { + return getAFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()); + } + /** + * repeated .ListListInteger a = 1; + */ + public java.util.List + getABuilderList() { + return getAFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder> + getAFieldBuilder() { + if (aBuilder_ == null) { + aBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder>( + a_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + a_ = null; + } + return aBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ListListListInteger) + } + + // @@protoc_insertion_point(class_scope:ListListListInteger) + private static final com.zfoo.protocol.packet.ProtobufObject.ListListListInteger DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ListListListInteger(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListListListInteger getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ListListListInteger parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ListListListInteger(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ListObjectAOrBuilder extends + // @@protoc_insertion_point(interface_extends:ListObjectA) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .ObjectA a = 1; + */ + java.util.List + getAList(); + /** + * repeated .ObjectA a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectA getA(int index); + /** + * repeated .ObjectA a = 1; + */ + int getACount(); + /** + * repeated .ObjectA a = 1; + */ + java.util.List + getAOrBuilderList(); + /** + * repeated .ObjectA a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getAOrBuilder( + int index); + } + /** + * Protobuf type {@code ListObjectA} + */ + public static final class ListObjectA extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ListObjectA) + ListObjectAOrBuilder { + private static final long serialVersionUID = 0L; + // Use ListObjectA.newBuilder() to construct. + private ListObjectA(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ListObjectA() { + a_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ListObjectA(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ListObjectA( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + a_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectA.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.class, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private java.util.List a_; + /** + * repeated .ObjectA a = 1; + */ + public java.util.List getAList() { + return a_; + } + /** + * repeated .ObjectA a = 1; + */ + public java.util.List + getAOrBuilderList() { + return a_; + } + /** + * repeated .ObjectA a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getA(int index) { + return a_.get(index); + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getAOrBuilder( + int index) { + return a_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < a_.size(); i++) { + output.writeMessage(1, a_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < a_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, a_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ListObjectA)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ListObjectA other = (com.zfoo.protocol.packet.ProtobufObject.ListObjectA) obj; + + if (!getAList() + .equals(other.getAList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getACount() > 0) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getAList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ListObjectA prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ListObjectA} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ListObjectA) + com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.class, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ListObjectA.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getAFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + aBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListObjectA_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA build() { + com.zfoo.protocol.packet.ProtobufObject.ListObjectA result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ListObjectA result = new com.zfoo.protocol.packet.ProtobufObject.ListObjectA(this); + int from_bitField0_ = bitField0_; + if (aBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.a_ = a_; + } else { + result.a_ = aBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ListObjectA) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ListObjectA)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ListObjectA other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()) return this; + if (aBuilder_ == null) { + if (!other.a_.isEmpty()) { + if (a_.isEmpty()) { + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAIsMutable(); + a_.addAll(other.a_); + } + onChanged(); + } + } else { + if (!other.a_.isEmpty()) { + if (aBuilder_.isEmpty()) { + aBuilder_.dispose(); + aBuilder_ = null; + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + aBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getAFieldBuilder() : null; + } else { + aBuilder_.addAllMessages(other.a_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ListObjectA parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ListObjectA) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List a_ = + java.util.Collections.emptyList(); + private void ensureAIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(a_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> aBuilder_; + + /** + * repeated .ObjectA a = 1; + */ + public java.util.List getAList() { + if (aBuilder_ == null) { + return java.util.Collections.unmodifiableList(a_); + } else { + return aBuilder_.getMessageList(); + } + } + /** + * repeated .ObjectA a = 1; + */ + public int getACount() { + if (aBuilder_ == null) { + return a_.size(); + } else { + return aBuilder_.getCount(); + } + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getA(int index) { + if (aBuilder_ == null) { + return a_.get(index); + } else { + return aBuilder_.getMessage(index); + } + } + /** + * repeated .ObjectA a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.set(index, value); + onChanged(); + } else { + aBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.set(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder addA(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(value); + onChanged(); + } else { + aBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(index, value); + onChanged(); + } else { + aBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder addA( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder addAllA( + java.lang.Iterable values) { + if (aBuilder_ == null) { + ensureAIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, a_); + onChanged(); + } else { + aBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder clearA() { + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + aBuilder_.clear(); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public Builder removeA(int index) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.remove(index); + onChanged(); + } else { + aBuilder_.remove(index); + } + return this; + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder getABuilder( + int index) { + return getAFieldBuilder().getBuilder(index); + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getAOrBuilder( + int index) { + if (aBuilder_ == null) { + return a_.get(index); } else { + return aBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ObjectA a = 1; + */ + public java.util.List + getAOrBuilderList() { + if (aBuilder_ != null) { + return aBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(a_); + } + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder addABuilder() { + return getAFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + /** + * repeated .ObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder addABuilder( + int index) { + return getAFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + /** + * repeated .ObjectA a = 1; + */ + public java.util.List + getABuilderList() { + return getAFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> + getAFieldBuilder() { + if (aBuilder_ == null) { + aBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder>( + a_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + a_ = null; + } + return aBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ListObjectA) + } + + // @@protoc_insertion_point(class_scope:ListObjectA) + private static final com.zfoo.protocol.packet.ProtobufObject.ListObjectA DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ListObjectA(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListObjectA getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ListObjectA parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ListObjectA(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ListListObjectAOrBuilder extends + // @@protoc_insertion_point(interface_extends:ListListObjectA) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .ListObjectA a = 1; + */ + java.util.List + getAList(); + /** + * repeated .ListObjectA a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListObjectA getA(int index); + /** + * repeated .ListObjectA a = 1; + */ + int getACount(); + /** + * repeated .ListObjectA a = 1; + */ + java.util.List + getAOrBuilderList(); + /** + * repeated .ListObjectA a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getAOrBuilder( + int index); + } + /** + * Protobuf type {@code ListListObjectA} + */ + public static final class ListListObjectA extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ListListObjectA) + ListListObjectAOrBuilder { + private static final long serialVersionUID = 0L; + // Use ListListObjectA.newBuilder() to construct. + private ListListObjectA(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ListListObjectA() { + a_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ListListObjectA(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ListListObjectA( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + a_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListObjectA.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.class, com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private java.util.List a_; + /** + * repeated .ListObjectA a = 1; + */ + public java.util.List getAList() { + return a_; + } + /** + * repeated .ListObjectA a = 1; + */ + public java.util.List + getAOrBuilderList() { + return a_; + } + /** + * repeated .ListObjectA a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getA(int index) { + return a_.get(index); + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getAOrBuilder( + int index) { + return a_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < a_.size(); i++) { + output.writeMessage(1, a_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < a_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, a_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ListListObjectA)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA other = (com.zfoo.protocol.packet.ProtobufObject.ListListObjectA) obj; + + if (!getAList() + .equals(other.getAList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getACount() > 0) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getAList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ListListObjectA prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ListListObjectA} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ListListObjectA) + com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.class, com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getAFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + aBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListListObjectA_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA build() { + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA result = new com.zfoo.protocol.packet.ProtobufObject.ListListObjectA(this); + int from_bitField0_ = bitField0_; + if (aBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.a_ = a_; + } else { + result.a_ = aBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ListListObjectA) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ListListObjectA)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ListListObjectA other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.getDefaultInstance()) return this; + if (aBuilder_ == null) { + if (!other.a_.isEmpty()) { + if (a_.isEmpty()) { + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAIsMutable(); + a_.addAll(other.a_); + } + onChanged(); + } + } else { + if (!other.a_.isEmpty()) { + if (aBuilder_.isEmpty()) { + aBuilder_.dispose(); + aBuilder_ = null; + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + aBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getAFieldBuilder() : null; + } else { + aBuilder_.addAllMessages(other.a_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ListListObjectA) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List a_ = + java.util.Collections.emptyList(); + private void ensureAIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(a_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder> aBuilder_; + + /** + * repeated .ListObjectA a = 1; + */ + public java.util.List getAList() { + if (aBuilder_ == null) { + return java.util.Collections.unmodifiableList(a_); + } else { + return aBuilder_.getMessageList(); + } + } + /** + * repeated .ListObjectA a = 1; + */ + public int getACount() { + if (aBuilder_ == null) { + return a_.size(); + } else { + return aBuilder_.getCount(); + } + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getA(int index) { + if (aBuilder_ == null) { + return a_.get(index); + } else { + return aBuilder_.getMessage(index); + } + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.set(index, value); + onChanged(); + } else { + aBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.set(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder addA(com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(value); + onChanged(); + } else { + aBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(index, value); + onChanged(); + } else { + aBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder addA( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder addAllA( + java.lang.Iterable values) { + if (aBuilder_ == null) { + ensureAIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, a_); + onChanged(); + } else { + aBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder clearA() { + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + aBuilder_.clear(); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public Builder removeA(int index) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.remove(index); + onChanged(); + } else { + aBuilder_.remove(index); + } + return this; + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder getABuilder( + int index) { + return getAFieldBuilder().getBuilder(index); + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getAOrBuilder( + int index) { + if (aBuilder_ == null) { + return a_.get(index); } else { + return aBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ListObjectA a = 1; + */ + public java.util.List + getAOrBuilderList() { + if (aBuilder_ != null) { + return aBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(a_); + } + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder addABuilder() { + return getAFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()); + } + /** + * repeated .ListObjectA a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder addABuilder( + int index) { + return getAFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()); + } + /** + * repeated .ListObjectA a = 1; + */ + public java.util.List + getABuilderList() { + return getAFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder> + getAFieldBuilder() { + if (aBuilder_ == null) { + aBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder>( + a_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + a_ = null; + } + return aBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ListListObjectA) + } + + // @@protoc_insertion_point(class_scope:ListListObjectA) + private static final com.zfoo.protocol.packet.ProtobufObject.ListListObjectA DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ListListObjectA(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListListObjectA getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ListListObjectA parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ListListObjectA(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface MapObjectAOrBuilder extends + // @@protoc_insertion_point(interface_extends:MapObjectA) + com.google.protobuf.MessageOrBuilder { + + /** + * .ObjectA key = 1; + */ + boolean hasKey(); + /** + * .ObjectA key = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectA getKey(); + /** + * .ObjectA key = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKeyOrBuilder(); + + /** + * .ListInteger value = 2; + */ + boolean hasValue(); + /** + * .ListInteger value = 2; + */ + com.zfoo.protocol.packet.ProtobufObject.ListInteger getValue(); + /** + * .ListInteger value = 2; + */ + com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder getValueOrBuilder(); + } + /** + * Protobuf type {@code MapObjectA} + */ + public static final class MapObjectA extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:MapObjectA) + MapObjectAOrBuilder { + private static final long serialVersionUID = 0L; + // Use MapObjectA.newBuilder() to construct. + private MapObjectA(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private MapObjectA() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new MapObjectA(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private MapObjectA( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder subBuilder = null; + if (key_ != null) { + subBuilder = key_.toBuilder(); + } + key_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectA.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(key_); + key_ = subBuilder.buildPartial(); + } + + break; + } + case 18: { + com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder subBuilder = null; + if (value_ != null) { + subBuilder = value_.toBuilder(); + } + value_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListInteger.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(value_); + value_ = subBuilder.buildPartial(); + } + + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapObjectA.class, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder.class); + } + + public static final int KEY_FIELD_NUMBER = 1; + private com.zfoo.protocol.packet.ProtobufObject.ObjectA key_; + /** + * .ObjectA key = 1; + */ + public boolean hasKey() { + return key_ != null; + } + /** + * .ObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKey() { + return key_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : key_; + } + /** + * .ObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKeyOrBuilder() { + return getKey(); + } + + public static final int VALUE_FIELD_NUMBER = 2; + private com.zfoo.protocol.packet.ProtobufObject.ListInteger value_; + /** + * .ListInteger value = 2; + */ + public boolean hasValue() { + return value_ != null; + } + /** + * .ListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger getValue() { + return value_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance() : value_; + } + /** + * .ListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder getValueOrBuilder() { + return getValue(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (key_ != null) { + output.writeMessage(1, getKey()); + } + if (value_ != null) { + output.writeMessage(2, getValue()); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (key_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, getKey()); + } + if (value_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, getValue()); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.MapObjectA)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.MapObjectA other = (com.zfoo.protocol.packet.ProtobufObject.MapObjectA) obj; + + if (hasKey() != other.hasKey()) return false; + if (hasKey()) { + if (!getKey() + .equals(other.getKey())) return false; + } + if (hasValue() != other.hasValue()) return false; + if (hasValue()) { + if (!getValue() + .equals(other.getValue())) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasKey()) { + hash = (37 * hash) + KEY_FIELD_NUMBER; + hash = (53 * hash) + getKey().hashCode(); + } + if (hasValue()) { + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.MapObjectA prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code MapObjectA} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:MapObjectA) + com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapObjectA.class, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.MapObjectA.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (keyBuilder_ == null) { + key_ = null; + } else { + key_ = null; + keyBuilder_ = null; + } + if (valueBuilder_ == null) { + value_ = null; + } else { + value_ = null; + valueBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapObjectA_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.MapObjectA.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA build() { + com.zfoo.protocol.packet.ProtobufObject.MapObjectA result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.MapObjectA result = new com.zfoo.protocol.packet.ProtobufObject.MapObjectA(this); + if (keyBuilder_ == null) { + result.key_ = key_; + } else { + result.key_ = keyBuilder_.build(); + } + if (valueBuilder_ == null) { + result.value_ = value_; + } else { + result.value_ = valueBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.MapObjectA) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.MapObjectA)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.MapObjectA other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.MapObjectA.getDefaultInstance()) return this; + if (other.hasKey()) { + mergeKey(other.getKey()); + } + if (other.hasValue()) { + mergeValue(other.getValue()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.MapObjectA parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.MapObjectA) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private com.zfoo.protocol.packet.ProtobufObject.ObjectA key_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> keyBuilder_; + /** + * .ObjectA key = 1; + */ + public boolean hasKey() { + return keyBuilder_ != null || key_ != null; + } + /** + * .ObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKey() { + if (keyBuilder_ == null) { + return key_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : key_; + } else { + return keyBuilder_.getMessage(); + } + } + /** + * .ObjectA key = 1; + */ + public Builder setKey(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (keyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + key_ = value; + onChanged(); + } else { + keyBuilder_.setMessage(value); + } + + return this; + } + /** + * .ObjectA key = 1; + */ + public Builder setKey( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (keyBuilder_ == null) { + key_ = builderForValue.build(); + onChanged(); + } else { + keyBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ObjectA key = 1; + */ + public Builder mergeKey(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (keyBuilder_ == null) { + if (key_ != null) { + key_ = + com.zfoo.protocol.packet.ProtobufObject.ObjectA.newBuilder(key_).mergeFrom(value).buildPartial(); + } else { + key_ = value; + } + onChanged(); + } else { + keyBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ObjectA key = 1; + */ + public Builder clearKey() { + if (keyBuilder_ == null) { + key_ = null; + onChanged(); + } else { + key_ = null; + keyBuilder_ = null; + } + + return this; + } + /** + * .ObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder getKeyBuilder() { + + onChanged(); + return getKeyFieldBuilder().getBuilder(); + } + /** + * .ObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKeyOrBuilder() { + if (keyBuilder_ != null) { + return keyBuilder_.getMessageOrBuilder(); + } else { + return key_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : key_; + } + } + /** + * .ObjectA key = 1; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> + getKeyFieldBuilder() { + if (keyBuilder_ == null) { + keyBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder>( + getKey(), + getParentForChildren(), + isClean()); + key_ = null; + } + return keyBuilder_; + } + + private com.zfoo.protocol.packet.ProtobufObject.ListInteger value_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListInteger, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder> valueBuilder_; + /** + * .ListInteger value = 2; + */ + public boolean hasValue() { + return valueBuilder_ != null || value_ != null; + } + /** + * .ListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger getValue() { + if (valueBuilder_ == null) { + return value_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance() : value_; + } else { + return valueBuilder_.getMessage(); + } + } + /** + * .ListInteger value = 2; + */ + public Builder setValue(com.zfoo.protocol.packet.ProtobufObject.ListInteger value) { + if (valueBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + value_ = value; + onChanged(); + } else { + valueBuilder_.setMessage(value); + } + + return this; + } + /** + * .ListInteger value = 2; + */ + public Builder setValue( + com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder builderForValue) { + if (valueBuilder_ == null) { + value_ = builderForValue.build(); + onChanged(); + } else { + valueBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ListInteger value = 2; + */ + public Builder mergeValue(com.zfoo.protocol.packet.ProtobufObject.ListInteger value) { + if (valueBuilder_ == null) { + if (value_ != null) { + value_ = + com.zfoo.protocol.packet.ProtobufObject.ListInteger.newBuilder(value_).mergeFrom(value).buildPartial(); + } else { + value_ = value; + } + onChanged(); + } else { + valueBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ListInteger value = 2; + */ + public Builder clearValue() { + if (valueBuilder_ == null) { + value_ = null; + onChanged(); + } else { + value_ = null; + valueBuilder_ = null; + } + + return this; + } + /** + * .ListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder getValueBuilder() { + + onChanged(); + return getValueFieldBuilder().getBuilder(); + } + /** + * .ListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder getValueOrBuilder() { + if (valueBuilder_ != null) { + return valueBuilder_.getMessageOrBuilder(); + } else { + return value_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ListInteger.getDefaultInstance() : value_; + } + } + /** + * .ListInteger value = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListInteger, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder> + getValueFieldBuilder() { + if (valueBuilder_ == null) { + valueBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListInteger, com.zfoo.protocol.packet.ProtobufObject.ListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListIntegerOrBuilder>( + getValue(), + getParentForChildren(), + isClean()); + value_ = null; + } + return valueBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:MapObjectA) + } + + // @@protoc_insertion_point(class_scope:MapObjectA) + private static final com.zfoo.protocol.packet.ProtobufObject.MapObjectA DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.MapObjectA(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapObjectA getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public MapObjectA parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new MapObjectA(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface MapListListObjectAOrBuilder extends + // @@protoc_insertion_point(interface_extends:MapListListObjectA) + com.google.protobuf.MessageOrBuilder { + + /** + * .ListListObjectA key = 1; + */ + boolean hasKey(); + /** + * .ListListObjectA key = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA getKey(); + /** + * .ListListObjectA key = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder getKeyOrBuilder(); + + /** + * .ListListListInteger value = 2; + */ + boolean hasValue(); + /** + * .ListListListInteger value = 2; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger getValue(); + /** + * .ListListListInteger value = 2; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder getValueOrBuilder(); + } + /** + * Protobuf type {@code MapListListObjectA} + */ + public static final class MapListListObjectA extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:MapListListObjectA) + MapListListObjectAOrBuilder { + private static final long serialVersionUID = 0L; + // Use MapListListObjectA.newBuilder() to construct. + private MapListListObjectA(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private MapListListObjectA() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new MapListListObjectA(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private MapListListObjectA( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder subBuilder = null; + if (key_ != null) { + subBuilder = key_.toBuilder(); + } + key_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(key_); + key_ = subBuilder.buildPartial(); + } + + break; + } + case 18: { + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder subBuilder = null; + if (value_ != null) { + subBuilder = value_.toBuilder(); + } + value_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(value_); + value_ = subBuilder.buildPartial(); + } + + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListListObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListListObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.class, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder.class); + } + + public static final int KEY_FIELD_NUMBER = 1; + private com.zfoo.protocol.packet.ProtobufObject.ListListObjectA key_; + /** + * .ListListObjectA key = 1; + */ + public boolean hasKey() { + return key_ != null; + } + /** + * .ListListObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA getKey() { + return key_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.getDefaultInstance() : key_; + } + /** + * .ListListObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder getKeyOrBuilder() { + return getKey(); + } + + public static final int VALUE_FIELD_NUMBER = 2; + private com.zfoo.protocol.packet.ProtobufObject.ListListListInteger value_; + /** + * .ListListListInteger value = 2; + */ + public boolean hasValue() { + return value_ != null; + } + /** + * .ListListListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger getValue() { + return value_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.getDefaultInstance() : value_; + } + /** + * .ListListListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder getValueOrBuilder() { + return getValue(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (key_ != null) { + output.writeMessage(1, getKey()); + } + if (value_ != null) { + output.writeMessage(2, getValue()); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (key_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, getKey()); + } + if (value_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, getValue()); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA other = (com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA) obj; + + if (hasKey() != other.hasKey()) return false; + if (hasKey()) { + if (!getKey() + .equals(other.getKey())) return false; + } + if (hasValue() != other.hasValue()) return false; + if (hasValue()) { + if (!getValue() + .equals(other.getValue())) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasKey()) { + hash = (37 * hash) + KEY_FIELD_NUMBER; + hash = (53 * hash) + getKey().hashCode(); + } + if (hasValue()) { + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code MapListListObjectA} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:MapListListObjectA) + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListListObjectA_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListListObjectA_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.class, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (keyBuilder_ == null) { + key_ = null; + } else { + key_ = null; + keyBuilder_ = null; + } + if (valueBuilder_ == null) { + value_ = null; + } else { + value_ = null; + valueBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListListObjectA_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA build() { + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA result = new com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA(this); + if (keyBuilder_ == null) { + result.key_ = key_; + } else { + result.key_ = keyBuilder_.build(); + } + if (valueBuilder_ == null) { + result.value_ = value_; + } else { + result.value_ = valueBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.getDefaultInstance()) return this; + if (other.hasKey()) { + mergeKey(other.getKey()); + } + if (other.hasValue()) { + mergeValue(other.getValue()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private com.zfoo.protocol.packet.ProtobufObject.ListListObjectA key_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder> keyBuilder_; + /** + * .ListListObjectA key = 1; + */ + public boolean hasKey() { + return keyBuilder_ != null || key_ != null; + } + /** + * .ListListObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA getKey() { + if (keyBuilder_ == null) { + return key_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.getDefaultInstance() : key_; + } else { + return keyBuilder_.getMessage(); + } + } + /** + * .ListListObjectA key = 1; + */ + public Builder setKey(com.zfoo.protocol.packet.ProtobufObject.ListListObjectA value) { + if (keyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + key_ = value; + onChanged(); + } else { + keyBuilder_.setMessage(value); + } + + return this; + } + /** + * .ListListObjectA key = 1; + */ + public Builder setKey( + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder builderForValue) { + if (keyBuilder_ == null) { + key_ = builderForValue.build(); + onChanged(); + } else { + keyBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ListListObjectA key = 1; + */ + public Builder mergeKey(com.zfoo.protocol.packet.ProtobufObject.ListListObjectA value) { + if (keyBuilder_ == null) { + if (key_ != null) { + key_ = + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.newBuilder(key_).mergeFrom(value).buildPartial(); + } else { + key_ = value; + } + onChanged(); + } else { + keyBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ListListObjectA key = 1; + */ + public Builder clearKey() { + if (keyBuilder_ == null) { + key_ = null; + onChanged(); + } else { + key_ = null; + keyBuilder_ = null; + } + + return this; + } + /** + * .ListListObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder getKeyBuilder() { + + onChanged(); + return getKeyFieldBuilder().getBuilder(); + } + /** + * .ListListObjectA key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder getKeyOrBuilder() { + if (keyBuilder_ != null) { + return keyBuilder_.getMessageOrBuilder(); + } else { + return key_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.getDefaultInstance() : key_; + } + } + /** + * .ListListObjectA key = 1; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder> + getKeyFieldBuilder() { + if (keyBuilder_ == null) { + keyBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListObjectAOrBuilder>( + getKey(), + getParentForChildren(), + isClean()); + key_ = null; + } + return keyBuilder_; + } + + private com.zfoo.protocol.packet.ProtobufObject.ListListListInteger value_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder> valueBuilder_; + /** + * .ListListListInteger value = 2; + */ + public boolean hasValue() { + return valueBuilder_ != null || value_ != null; + } + /** + * .ListListListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger getValue() { + if (valueBuilder_ == null) { + return value_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.getDefaultInstance() : value_; + } else { + return valueBuilder_.getMessage(); + } + } + /** + * .ListListListInteger value = 2; + */ + public Builder setValue(com.zfoo.protocol.packet.ProtobufObject.ListListListInteger value) { + if (valueBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + value_ = value; + onChanged(); + } else { + valueBuilder_.setMessage(value); + } + + return this; + } + /** + * .ListListListInteger value = 2; + */ + public Builder setValue( + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder builderForValue) { + if (valueBuilder_ == null) { + value_ = builderForValue.build(); + onChanged(); + } else { + valueBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ListListListInteger value = 2; + */ + public Builder mergeValue(com.zfoo.protocol.packet.ProtobufObject.ListListListInteger value) { + if (valueBuilder_ == null) { + if (value_ != null) { + value_ = + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.newBuilder(value_).mergeFrom(value).buildPartial(); + } else { + value_ = value; + } + onChanged(); + } else { + valueBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ListListListInteger value = 2; + */ + public Builder clearValue() { + if (valueBuilder_ == null) { + value_ = null; + onChanged(); + } else { + value_ = null; + valueBuilder_ = null; + } + + return this; + } + /** + * .ListListListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder getValueBuilder() { + + onChanged(); + return getValueFieldBuilder().getBuilder(); + } + /** + * .ListListListInteger value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder getValueOrBuilder() { + if (valueBuilder_ != null) { + return valueBuilder_.getMessageOrBuilder(); + } else { + return value_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.getDefaultInstance() : value_; + } + } + /** + * .ListListListInteger value = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder> + getValueFieldBuilder() { + if (valueBuilder_ == null) { + valueBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListListIntegerOrBuilder>( + getValue(), + getParentForChildren(), + isClean()); + value_ = null; + } + return valueBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:MapListListObjectA) + } + + // @@protoc_insertion_point(class_scope:MapListListObjectA) + private static final com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public MapListListObjectA parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new MapListListObjectA(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface MapIntegerStringOrBuilder extends + // @@protoc_insertion_point(interface_extends:MapIntegerString) + com.google.protobuf.MessageOrBuilder { + + /** + * map<int32, string> a = 1; + */ + int getACount(); + /** + * map<int32, string> a = 1; + */ + boolean containsA( + int key); + /** + * Use {@link #getAMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getA(); + /** + * map<int32, string> a = 1; + */ + java.util.Map + getAMap(); + /** + * map<int32, string> a = 1; + */ + + java.lang.String getAOrDefault( + int key, + java.lang.String defaultValue); + /** + * map<int32, string> a = 1; + */ + + java.lang.String getAOrThrow( + int key); + } + /** + * Protobuf type {@code MapIntegerString} + */ + public static final class MapIntegerString extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:MapIntegerString) + MapIntegerStringOrBuilder { + private static final long serialVersionUID = 0L; + // Use MapIntegerString.newBuilder() to construct. + private MapIntegerString(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private MapIntegerString() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new MapIntegerString(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private MapIntegerString( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = com.google.protobuf.MapField.newMapField( + ADefaultEntryHolder.defaultEntry); + mutable_bitField0_ |= 0x00000001; + } + com.google.protobuf.MapEntry + a__ = input.readMessage( + ADefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + a_.getMutableMap().put( + a__.getKey(), a__.getValue()); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapIntegerString_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + @java.lang.Override + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 1: + return internalGetA(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapIntegerString_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.class, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private static final class ADefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.Integer, java.lang.String> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.zfoo.protocol.packet.ProtobufObject.internal_static_MapIntegerString_AEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.INT32, + 0, + com.google.protobuf.WireFormat.FieldType.STRING, + ""); + } + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> a_; + private com.google.protobuf.MapField + internalGetA() { + if (a_ == null) { + return com.google.protobuf.MapField.emptyMapField( + ADefaultEntryHolder.defaultEntry); + } + return a_; + } + + public int getACount() { + return internalGetA().getMap().size(); + } + /** + * map<int32, string> a = 1; + */ + + public boolean containsA( + int key) { + + return internalGetA().getMap().containsKey(key); + } + /** + * Use {@link #getAMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getA() { + return getAMap(); + } + /** + * map<int32, string> a = 1; + */ + + public java.util.Map getAMap() { + return internalGetA().getMap(); + } + /** + * map<int32, string> a = 1; + */ + + public java.lang.String getAOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetA().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> a = 1; + */ + + public java.lang.String getAOrThrow( + int key) { + + java.util.Map map = + internalGetA().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + com.google.protobuf.GeneratedMessageV3 + .serializeIntegerMapTo( + output, + internalGetA(), + ADefaultEntryHolder.defaultEntry, + 1); + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (java.util.Map.Entry entry + : internalGetA().getMap().entrySet()) { + com.google.protobuf.MapEntry + a__ = ADefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, a__); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.MapIntegerString)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString other = (com.zfoo.protocol.packet.ProtobufObject.MapIntegerString) obj; + + if (!internalGetA().equals( + other.internalGetA())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (!internalGetA().getMap().isEmpty()) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + internalGetA().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code MapIntegerString} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:MapIntegerString) + com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapIntegerString_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 1: + return internalGetA(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMutableMapField( + int number) { + switch (number) { + case 1: + return internalGetMutableA(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapIntegerString_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.class, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + internalGetMutableA().clear(); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapIntegerString_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString build() { + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString result = new com.zfoo.protocol.packet.ProtobufObject.MapIntegerString(this); + int from_bitField0_ = bitField0_; + result.a_ = internalGetA(); + result.a_.makeImmutable(); + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.MapIntegerString) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.MapIntegerString)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()) return this; + internalGetMutableA().mergeFrom( + other.internalGetA()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.MapIntegerString) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> a_; + private com.google.protobuf.MapField + internalGetA() { + if (a_ == null) { + return com.google.protobuf.MapField.emptyMapField( + ADefaultEntryHolder.defaultEntry); + } + return a_; + } + private com.google.protobuf.MapField + internalGetMutableA() { + onChanged();; + if (a_ == null) { + a_ = com.google.protobuf.MapField.newMapField( + ADefaultEntryHolder.defaultEntry); + } + if (!a_.isMutable()) { + a_ = a_.copy(); + } + return a_; + } + + public int getACount() { + return internalGetA().getMap().size(); + } + /** + * map<int32, string> a = 1; + */ + + public boolean containsA( + int key) { + + return internalGetA().getMap().containsKey(key); + } + /** + * Use {@link #getAMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getA() { + return getAMap(); + } + /** + * map<int32, string> a = 1; + */ + + public java.util.Map getAMap() { + return internalGetA().getMap(); + } + /** + * map<int32, string> a = 1; + */ + + public java.lang.String getAOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetA().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> a = 1; + */ + + public java.lang.String getAOrThrow( + int key) { + + java.util.Map map = + internalGetA().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public Builder clearA() { + internalGetMutableA().getMutableMap() + .clear(); + return this; + } + /** + * map<int32, string> a = 1; + */ + + public Builder removeA( + int key) { + + internalGetMutableA().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableA() { + return internalGetMutableA().getMutableMap(); + } + /** + * map<int32, string> a = 1; + */ + public Builder putA( + int key, + java.lang.String value) { + + if (value == null) { throw new java.lang.NullPointerException(); } + internalGetMutableA().getMutableMap() + .put(key, value); + return this; + } + /** + * map<int32, string> a = 1; + */ + + public Builder putAllA( + java.util.Map values) { + internalGetMutableA().getMutableMap() + .putAll(values); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:MapIntegerString) + } + + // @@protoc_insertion_point(class_scope:MapIntegerString) + private static final com.zfoo.protocol.packet.ProtobufObject.MapIntegerString DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.MapIntegerString(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public MapIntegerString parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new MapIntegerString(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ListMapIntegerStringOrBuilder extends + // @@protoc_insertion_point(interface_extends:ListMapIntegerString) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .MapIntegerString a = 1; + */ + java.util.List + getAList(); + /** + * repeated .MapIntegerString a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getA(int index); + /** + * repeated .MapIntegerString a = 1; + */ + int getACount(); + /** + * repeated .MapIntegerString a = 1; + */ + java.util.List + getAOrBuilderList(); + /** + * repeated .MapIntegerString a = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getAOrBuilder( + int index); + } + /** + * Protobuf type {@code ListMapIntegerString} + */ + public static final class ListMapIntegerString extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ListMapIntegerString) + ListMapIntegerStringOrBuilder { + private static final long serialVersionUID = 0L; + // Use ListMapIntegerString.newBuilder() to construct. + private ListMapIntegerString(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ListMapIntegerString() { + a_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ListMapIntegerString(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ListMapIntegerString( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + a_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListMapIntegerString_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListMapIntegerString_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.class, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private java.util.List a_; + /** + * repeated .MapIntegerString a = 1; + */ + public java.util.List getAList() { + return a_; + } + /** + * repeated .MapIntegerString a = 1; + */ + public java.util.List + getAOrBuilderList() { + return a_; + } + /** + * repeated .MapIntegerString a = 1; + */ + public int getACount() { + return a_.size(); + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getA(int index) { + return a_.get(index); + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getAOrBuilder( + int index) { + return a_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < a_.size(); i++) { + output.writeMessage(1, a_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < a_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, a_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString other = (com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString) obj; + + if (!getAList() + .equals(other.getAList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getACount() > 0) { + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getAList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ListMapIntegerString} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ListMapIntegerString) + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListMapIntegerString_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListMapIntegerString_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.class, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getAFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + aBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ListMapIntegerString_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString build() { + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString result = new com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString(this); + int from_bitField0_ = bitField0_; + if (aBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + a_ = java.util.Collections.unmodifiableList(a_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.a_ = a_; + } else { + result.a_ = aBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance()) return this; + if (aBuilder_ == null) { + if (!other.a_.isEmpty()) { + if (a_.isEmpty()) { + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureAIsMutable(); + a_.addAll(other.a_); + } + onChanged(); + } + } else { + if (!other.a_.isEmpty()) { + if (aBuilder_.isEmpty()) { + aBuilder_.dispose(); + aBuilder_ = null; + a_ = other.a_; + bitField0_ = (bitField0_ & ~0x00000001); + aBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getAFieldBuilder() : null; + } else { + aBuilder_.addAllMessages(other.a_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List a_ = + java.util.Collections.emptyList(); + private void ensureAIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + a_ = new java.util.ArrayList(a_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder> aBuilder_; + + /** + * repeated .MapIntegerString a = 1; + */ + public java.util.List getAList() { + if (aBuilder_ == null) { + return java.util.Collections.unmodifiableList(a_); + } else { + return aBuilder_.getMessageList(); + } + } + /** + * repeated .MapIntegerString a = 1; + */ + public int getACount() { + if (aBuilder_ == null) { + return a_.size(); + } else { + return aBuilder_.getCount(); + } + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getA(int index) { + if (aBuilder_ == null) { + return a_.get(index); + } else { + return aBuilder_.getMessage(index); + } + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.set(index, value); + onChanged(); + } else { + aBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder setA( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.set(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder addA(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(value); + onChanged(); + } else { + aBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (aBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAIsMutable(); + a_.add(index, value); + onChanged(); + } else { + aBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder addA( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder addA( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.add(index, builderForValue.build()); + onChanged(); + } else { + aBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder addAllA( + java.lang.Iterable values) { + if (aBuilder_ == null) { + ensureAIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, a_); + onChanged(); + } else { + aBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder clearA() { + if (aBuilder_ == null) { + a_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + aBuilder_.clear(); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public Builder removeA(int index) { + if (aBuilder_ == null) { + ensureAIsMutable(); + a_.remove(index); + onChanged(); + } else { + aBuilder_.remove(index); + } + return this; + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder getABuilder( + int index) { + return getAFieldBuilder().getBuilder(index); + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getAOrBuilder( + int index) { + if (aBuilder_ == null) { + return a_.get(index); } else { + return aBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .MapIntegerString a = 1; + */ + public java.util.List + getAOrBuilderList() { + if (aBuilder_ != null) { + return aBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(a_); + } + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder addABuilder() { + return getAFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()); + } + /** + * repeated .MapIntegerString a = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder addABuilder( + int index) { + return getAFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()); + } + /** + * repeated .MapIntegerString a = 1; + */ + public java.util.List + getABuilderList() { + return getAFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder> + getAFieldBuilder() { + if (aBuilder_ == null) { + aBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder>( + a_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + a_ = null; + } + return aBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ListMapIntegerString) + } + + // @@protoc_insertion_point(class_scope:ListMapIntegerString) + private static final com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ListMapIntegerString parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ListMapIntegerString(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface MapListMapIntegerOrBuilder extends + // @@protoc_insertion_point(interface_extends:MapListMapInteger) + com.google.protobuf.MessageOrBuilder { + + /** + * .ListMapIntegerString key = 1; + */ + boolean hasKey(); + /** + * .ListMapIntegerString key = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getKey(); + /** + * .ListMapIntegerString key = 1; + */ + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder getKeyOrBuilder(); + + /** + * .ListMapIntegerString value = 2; + */ + boolean hasValue(); + /** + * .ListMapIntegerString value = 2; + */ + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getValue(); + /** + * .ListMapIntegerString value = 2; + */ + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder getValueOrBuilder(); + } + /** + * Protobuf type {@code MapListMapInteger} + */ + public static final class MapListMapInteger extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:MapListMapInteger) + MapListMapIntegerOrBuilder { + private static final long serialVersionUID = 0L; + // Use MapListMapInteger.newBuilder() to construct. + private MapListMapInteger(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private MapListMapInteger() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new MapListMapInteger(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private MapListMapInteger( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder subBuilder = null; + if (key_ != null) { + subBuilder = key_.toBuilder(); + } + key_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(key_); + key_ = subBuilder.buildPartial(); + } + + break; + } + case 18: { + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder subBuilder = null; + if (value_ != null) { + subBuilder = value_.toBuilder(); + } + value_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(value_); + value_ = subBuilder.buildPartial(); + } + + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListMapInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListMapInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.class, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder.class); + } + + public static final int KEY_FIELD_NUMBER = 1; + private com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString key_; + /** + * .ListMapIntegerString key = 1; + */ + public boolean hasKey() { + return key_ != null; + } + /** + * .ListMapIntegerString key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getKey() { + return key_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance() : key_; + } + /** + * .ListMapIntegerString key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder getKeyOrBuilder() { + return getKey(); + } + + public static final int VALUE_FIELD_NUMBER = 2; + private com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString value_; + /** + * .ListMapIntegerString value = 2; + */ + public boolean hasValue() { + return value_ != null; + } + /** + * .ListMapIntegerString value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getValue() { + return value_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance() : value_; + } + /** + * .ListMapIntegerString value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder getValueOrBuilder() { + return getValue(); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (key_ != null) { + output.writeMessage(1, getKey()); + } + if (value_ != null) { + output.writeMessage(2, getValue()); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (key_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, getKey()); + } + if (value_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, getValue()); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger other = (com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger) obj; + + if (hasKey() != other.hasKey()) return false; + if (hasKey()) { + if (!getKey() + .equals(other.getKey())) return false; + } + if (hasValue() != other.hasValue()) return false; + if (hasValue()) { + if (!getValue() + .equals(other.getValue())) return false; + } + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasKey()) { + hash = (37 * hash) + KEY_FIELD_NUMBER; + hash = (53 * hash) + getKey().hashCode(); + } + if (hasValue()) { + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code MapListMapInteger} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:MapListMapInteger) + com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListMapInteger_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListMapInteger_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.class, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + if (keyBuilder_ == null) { + key_ = null; + } else { + key_ = null; + keyBuilder_ = null; + } + if (valueBuilder_ == null) { + value_ = null; + } else { + value_ = null; + valueBuilder_ = null; + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_MapListMapInteger_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger build() { + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger result = new com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger(this); + if (keyBuilder_ == null) { + result.key_ = key_; + } else { + result.key_ = keyBuilder_.build(); + } + if (valueBuilder_ == null) { + result.value_ = value_; + } else { + result.value_ = valueBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.getDefaultInstance()) return this; + if (other.hasKey()) { + mergeKey(other.getKey()); + } + if (other.hasValue()) { + mergeValue(other.getValue()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString key_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder> keyBuilder_; + /** + * .ListMapIntegerString key = 1; + */ + public boolean hasKey() { + return keyBuilder_ != null || key_ != null; + } + /** + * .ListMapIntegerString key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getKey() { + if (keyBuilder_ == null) { + return key_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance() : key_; + } else { + return keyBuilder_.getMessage(); + } + } + /** + * .ListMapIntegerString key = 1; + */ + public Builder setKey(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString value) { + if (keyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + key_ = value; + onChanged(); + } else { + keyBuilder_.setMessage(value); + } + + return this; + } + /** + * .ListMapIntegerString key = 1; + */ + public Builder setKey( + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder builderForValue) { + if (keyBuilder_ == null) { + key_ = builderForValue.build(); + onChanged(); + } else { + keyBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ListMapIntegerString key = 1; + */ + public Builder mergeKey(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString value) { + if (keyBuilder_ == null) { + if (key_ != null) { + key_ = + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.newBuilder(key_).mergeFrom(value).buildPartial(); + } else { + key_ = value; + } + onChanged(); + } else { + keyBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ListMapIntegerString key = 1; + */ + public Builder clearKey() { + if (keyBuilder_ == null) { + key_ = null; + onChanged(); + } else { + key_ = null; + keyBuilder_ = null; + } + + return this; + } + /** + * .ListMapIntegerString key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder getKeyBuilder() { + + onChanged(); + return getKeyFieldBuilder().getBuilder(); + } + /** + * .ListMapIntegerString key = 1; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder getKeyOrBuilder() { + if (keyBuilder_ != null) { + return keyBuilder_.getMessageOrBuilder(); + } else { + return key_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance() : key_; + } + } + /** + * .ListMapIntegerString key = 1; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder> + getKeyFieldBuilder() { + if (keyBuilder_ == null) { + keyBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder>( + getKey(), + getParentForChildren(), + isClean()); + key_ = null; + } + return keyBuilder_; + } + + private com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString value_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder> valueBuilder_; + /** + * .ListMapIntegerString value = 2; + */ + public boolean hasValue() { + return valueBuilder_ != null || value_ != null; + } + /** + * .ListMapIntegerString value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString getValue() { + if (valueBuilder_ == null) { + return value_ == null ? com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance() : value_; + } else { + return valueBuilder_.getMessage(); + } + } + /** + * .ListMapIntegerString value = 2; + */ + public Builder setValue(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString value) { + if (valueBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + value_ = value; + onChanged(); + } else { + valueBuilder_.setMessage(value); + } + + return this; + } + /** + * .ListMapIntegerString value = 2; + */ + public Builder setValue( + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder builderForValue) { + if (valueBuilder_ == null) { + value_ = builderForValue.build(); + onChanged(); + } else { + valueBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ListMapIntegerString value = 2; + */ + public Builder mergeValue(com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString value) { + if (valueBuilder_ == null) { + if (value_ != null) { + value_ = + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.newBuilder(value_).mergeFrom(value).buildPartial(); + } else { + value_ = value; + } + onChanged(); + } else { + valueBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ListMapIntegerString value = 2; + */ + public Builder clearValue() { + if (valueBuilder_ == null) { + value_ = null; + onChanged(); + } else { + value_ = null; + valueBuilder_ = null; + } + + return this; + } + /** + * .ListMapIntegerString value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder getValueBuilder() { + + onChanged(); + return getValueFieldBuilder().getBuilder(); + } + /** + * .ListMapIntegerString value = 2; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder getValueOrBuilder() { + if (valueBuilder_ != null) { + return valueBuilder_.getMessageOrBuilder(); + } else { + return value_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.getDefaultInstance() : value_; + } + } + /** + * .ListMapIntegerString value = 2; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder> + getValueFieldBuilder() { + if (valueBuilder_ == null) { + valueBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.ListMapIntegerStringOrBuilder>( + getValue(), + getParentForChildren(), + isClean()); + value_ = null; + } + return valueBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:MapListMapInteger) + } + + // @@protoc_insertion_point(class_scope:MapListMapInteger) + private static final com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public MapListMapInteger parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new MapListMapInteger(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ProtobufComplexObjectOrBuilder extends + // @@protoc_insertion_point(interface_extends:ProtobufComplexObject) + com.google.protobuf.MessageOrBuilder { + + /** + *
+     * protobuf不支持单个byte,用int代替,增加了一点性能开销
+     * 
+ * + * int32 a = 1; + */ + int getA(); + + /** + * int32 aa = 2; + */ + int getAa(); + + /** + * bytes aaa = 3; + */ + com.google.protobuf.ByteString getAaa(); + + /** + * bytes aaaa = 4; + */ + com.google.protobuf.ByteString getAaaa(); + + /** + *
+     * protobuf不支持单个short,用int代替,增加了一点性能开销
+     * 
+ * + * int32 b = 5; + */ + int getB(); + + /** + * int32 bb = 6; + */ + int getBb(); + + /** + *
+     * protobuf不支持单个short,用bytes代替,减少了一点性能开销
+     * 
+ * + * bytes bbb = 7; + */ + com.google.protobuf.ByteString getBbb(); + + /** + * bytes bbbb = 8; + */ + com.google.protobuf.ByteString getBbbb(); + + /** + * int32 c = 9; + */ + int getC(); + + /** + * int32 cc = 10; + */ + int getCc(); + + /** + * repeated int32 ccc = 11; + */ + java.util.List getCccList(); + /** + * repeated int32 ccc = 11; + */ + int getCccCount(); + /** + * repeated int32 ccc = 11; + */ + int getCcc(int index); + + /** + * repeated int32 cccc = 12; + */ + java.util.List getCcccList(); + /** + * repeated int32 cccc = 12; + */ + int getCcccCount(); + /** + * repeated int32 cccc = 12; + */ + int getCccc(int index); + + /** + * int64 d = 13; + */ + long getD(); + + /** + * int64 dd = 14; + */ + long getDd(); + + /** + * repeated int64 ddd = 15; + */ + java.util.List getDddList(); + /** + * repeated int64 ddd = 15; + */ + int getDddCount(); + /** + * repeated int64 ddd = 15; + */ + long getDdd(int index); + + /** + * repeated int64 dddd = 16; + */ + java.util.List getDdddList(); + /** + * repeated int64 dddd = 16; + */ + int getDdddCount(); + /** + * repeated int64 dddd = 16; + */ + long getDddd(int index); + + /** + * float e = 17; + */ + float getE(); + + /** + * float ee = 18; + */ + float getEe(); + + /** + * repeated float eee = 19; + */ + java.util.List getEeeList(); + /** + * repeated float eee = 19; + */ + int getEeeCount(); + /** + * repeated float eee = 19; + */ + float getEee(int index); + + /** + * repeated float eeee = 20; + */ + java.util.List getEeeeList(); + /** + * repeated float eeee = 20; + */ + int getEeeeCount(); + /** + * repeated float eeee = 20; + */ + float getEeee(int index); + + /** + * double f = 21; + */ + double getF(); + + /** + * double ff = 22; + */ + double getFf(); + + /** + * repeated double fff = 23; + */ + java.util.List getFffList(); + /** + * repeated double fff = 23; + */ + int getFffCount(); + /** + * repeated double fff = 23; + */ + double getFff(int index); + + /** + * repeated double ffff = 24; + */ + java.util.List getFfffList(); + /** + * repeated double ffff = 24; + */ + int getFfffCount(); + /** + * repeated double ffff = 24; + */ + double getFfff(int index); + + /** + * bool g = 25; + */ + boolean getG(); + + /** + * bool gg = 26; + */ + boolean getGg(); + + /** + * repeated bool ggg = 27; + */ + java.util.List getGggList(); + /** + * repeated bool ggg = 27; + */ + int getGggCount(); + /** + * repeated bool ggg = 27; + */ + boolean getGgg(int index); + + /** + * repeated bool gggg = 28; + */ + java.util.List getGgggList(); + /** + * repeated bool gggg = 28; + */ + int getGgggCount(); + /** + * repeated bool gggg = 28; + */ + boolean getGggg(int index); + + /** + *
+     * protobuf不支持char,用string代替,增加了一点性能开销
+     * 
+ * + * string h = 29; + */ + java.lang.String getH(); + /** + *
+     * protobuf不支持char,用string代替,增加了一点性能开销
+     * 
+ * + * string h = 29; + */ + com.google.protobuf.ByteString + getHBytes(); + + /** + * string hh = 30; + */ + java.lang.String getHh(); + /** + * string hh = 30; + */ + com.google.protobuf.ByteString + getHhBytes(); + + /** + * repeated string hhh = 31; + */ + java.util.List + getHhhList(); + /** + * repeated string hhh = 31; + */ + int getHhhCount(); + /** + * repeated string hhh = 31; + */ + java.lang.String getHhh(int index); + /** + * repeated string hhh = 31; + */ + com.google.protobuf.ByteString + getHhhBytes(int index); + + /** + * repeated string hhhh = 32; + */ + java.util.List + getHhhhList(); + /** + * repeated string hhhh = 32; + */ + int getHhhhCount(); + /** + * repeated string hhhh = 32; + */ + java.lang.String getHhhh(int index); + /** + * repeated string hhhh = 32; + */ + com.google.protobuf.ByteString + getHhhhBytes(int index); + + /** + * string jj = 33; + */ + java.lang.String getJj(); + /** + * string jj = 33; + */ + com.google.protobuf.ByteString + getJjBytes(); + + /** + * repeated string jjj = 34; + */ + java.util.List + getJjjList(); + /** + * repeated string jjj = 34; + */ + int getJjjCount(); + /** + * repeated string jjj = 34; + */ + java.lang.String getJjj(int index); + /** + * repeated string jjj = 34; + */ + com.google.protobuf.ByteString + getJjjBytes(int index); + + /** + * .ObjectA kk = 35; + */ + boolean hasKk(); + /** + * .ObjectA kk = 35; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectA getKk(); + /** + * .ObjectA kk = 35; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkOrBuilder(); + + /** + * repeated .ObjectA kkk = 36; + */ + java.util.List + getKkkList(); + /** + * repeated .ObjectA kkk = 36; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectA getKkk(int index); + /** + * repeated .ObjectA kkk = 36; + */ + int getKkkCount(); + /** + * repeated .ObjectA kkk = 36; + */ + java.util.List + getKkkOrBuilderList(); + /** + * repeated .ObjectA kkk = 36; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkkOrBuilder( + int index); + + /** + * repeated int32 l = 37; + */ + java.util.List getLList(); + /** + * repeated int32 l = 37; + */ + int getLCount(); + /** + * repeated int32 l = 37; + */ + int getL(int index); + + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + java.util.List + getLlList(); + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListInteger getLl(int index); + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + int getLlCount(); + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + java.util.List + getLlOrBuilderList(); + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getLlOrBuilder( + int index); + + /** + * repeated .ListObjectA lll = 39; + */ + java.util.List + getLllList(); + /** + * repeated .ListObjectA lll = 39; + */ + com.zfoo.protocol.packet.ProtobufObject.ListObjectA getLll(int index); + /** + * repeated .ListObjectA lll = 39; + */ + int getLllCount(); + /** + * repeated .ListObjectA lll = 39; + */ + java.util.List + getLllOrBuilderList(); + /** + * repeated .ListObjectA lll = 39; + */ + com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getLllOrBuilder( + int index); + + /** + * repeated string llll = 40; + */ + java.util.List + getLlllList(); + /** + * repeated string llll = 40; + */ + int getLlllCount(); + /** + * repeated string llll = 40; + */ + java.lang.String getLlll(int index); + /** + * repeated string llll = 40; + */ + com.google.protobuf.ByteString + getLlllBytes(int index); + + /** + * repeated .MapIntegerString lllll = 41; + */ + java.util.List + getLllllList(); + /** + * repeated .MapIntegerString lllll = 41; + */ + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getLllll(int index); + /** + * repeated .MapIntegerString lllll = 41; + */ + int getLllllCount(); + /** + * repeated .MapIntegerString lllll = 41; + */ + java.util.List + getLllllOrBuilderList(); + /** + * repeated .MapIntegerString lllll = 41; + */ + com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getLllllOrBuilder( + int index); + + /** + * map<int32, string> m = 51; + */ + int getMCount(); + /** + * map<int32, string> m = 51; + */ + boolean containsM( + int key); + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getM(); + /** + * map<int32, string> m = 51; + */ + java.util.Map + getMMap(); + /** + * map<int32, string> m = 51; + */ + + java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue); + /** + * map<int32, string> m = 51; + */ + + java.lang.String getMOrThrow( + int key); + + /** + * map<int32, .ObjectA> mm = 52; + */ + int getMmCount(); + /** + * map<int32, .ObjectA> mm = 52; + */ + boolean containsMm( + int key); + /** + * Use {@link #getMmMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getMm(); + /** + * map<int32, .ObjectA> mm = 52; + */ + java.util.Map + getMmMap(); + /** + * map<int32, .ObjectA> mm = 52; + */ + + com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrDefault( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA defaultValue); + /** + * map<int32, .ObjectA> mm = 52; + */ + + com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrThrow( + int key); + + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + java.util.List + getMmmList(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + com.zfoo.protocol.packet.ProtobufObject.MapObjectA getMmm(int index); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + int getMmmCount(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + java.util.List + getMmmOrBuilderList(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder getMmmOrBuilder( + int index); + + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + java.util.List + getMmmmList(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA getMmmm(int index); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + int getMmmmCount(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + java.util.List + getMmmmOrBuilderList(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder getMmmmOrBuilder( + int index); + + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + java.util.List + getMmmmmList(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger getMmmmm(int index); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + int getMmmmmCount(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + java.util.List + getMmmmmOrBuilderList(); + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder getMmmmmOrBuilder( + int index); + + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated int32 s = 61; + */ + java.util.List getSList(); + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated int32 s = 61; + */ + int getSCount(); + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated int32 s = 61; + */ + int getS(int index); + + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + java.util.List + getSsList(); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListInteger getSs(int index); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + int getSsCount(); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + java.util.List + getSsOrBuilderList(); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getSsOrBuilder( + int index); + + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + java.util.List + getSssList(); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + com.zfoo.protocol.packet.ProtobufObject.ListObjectA getSss(int index); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + int getSssCount(); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + java.util.List + getSssOrBuilderList(); + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getSssOrBuilder( + int index); + + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + java.util.List + getSsssList(); + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + int getSsssCount(); + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + java.lang.String getSsss(int index); + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + com.google.protobuf.ByteString + getSsssBytes(int index); + + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + java.util.List + getSssssList(); + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getSssss(int index); + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + int getSssssCount(); + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + java.util.List + getSssssOrBuilderList(); + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getSssssOrBuilder( + int index); + } + /** + * Protobuf type {@code ProtobufComplexObject} + */ + public static final class ProtobufComplexObject extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ProtobufComplexObject) + ProtobufComplexObjectOrBuilder { + private static final long serialVersionUID = 0L; + // Use ProtobufComplexObject.newBuilder() to construct. + private ProtobufComplexObject(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ProtobufComplexObject() { + aaa_ = com.google.protobuf.ByteString.EMPTY; + aaaa_ = com.google.protobuf.ByteString.EMPTY; + bbb_ = com.google.protobuf.ByteString.EMPTY; + bbbb_ = com.google.protobuf.ByteString.EMPTY; + ccc_ = emptyIntList(); + cccc_ = emptyIntList(); + ddd_ = emptyLongList(); + dddd_ = emptyLongList(); + eee_ = emptyFloatList(); + eeee_ = emptyFloatList(); + fff_ = emptyDoubleList(); + ffff_ = emptyDoubleList(); + ggg_ = emptyBooleanList(); + gggg_ = emptyBooleanList(); + h_ = ""; + hh_ = ""; + hhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + hhhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + jj_ = ""; + jjj_ = com.google.protobuf.LazyStringArrayList.EMPTY; + kkk_ = java.util.Collections.emptyList(); + l_ = emptyIntList(); + ll_ = java.util.Collections.emptyList(); + lll_ = java.util.Collections.emptyList(); + llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + lllll_ = java.util.Collections.emptyList(); + mmm_ = java.util.Collections.emptyList(); + mmmm_ = java.util.Collections.emptyList(); + mmmmm_ = java.util.Collections.emptyList(); + s_ = emptyIntList(); + ss_ = java.util.Collections.emptyList(); + sss_ = java.util.Collections.emptyList(); + ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + sssss_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ProtobufComplexObject(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ProtobufComplexObject( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + a_ = input.readInt32(); + break; + } + case 16: { + + aa_ = input.readInt32(); + break; + } + case 26: { + + aaa_ = input.readBytes(); + break; + } + case 34: { + + aaaa_ = input.readBytes(); + break; + } + case 40: { + + b_ = input.readInt32(); + break; + } + case 48: { + + bb_ = input.readInt32(); + break; + } + case 58: { + + bbb_ = input.readBytes(); + break; + } + case 66: { + + bbbb_ = input.readBytes(); + break; + } + case 72: { + + c_ = input.readInt32(); + break; + } + case 80: { + + cc_ = input.readInt32(); + break; + } + case 88: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + ccc_ = newIntList(); + mutable_bitField0_ |= 0x00000001; + } + ccc_.addInt(input.readInt32()); + break; + } + case 90: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000001) != 0) && input.getBytesUntilLimit() > 0) { + ccc_ = newIntList(); + mutable_bitField0_ |= 0x00000001; + } + while (input.getBytesUntilLimit() > 0) { + ccc_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + case 96: { + if (!((mutable_bitField0_ & 0x00000002) != 0)) { + cccc_ = newIntList(); + mutable_bitField0_ |= 0x00000002; + } + cccc_.addInt(input.readInt32()); + break; + } + case 98: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000002) != 0) && input.getBytesUntilLimit() > 0) { + cccc_ = newIntList(); + mutable_bitField0_ |= 0x00000002; + } + while (input.getBytesUntilLimit() > 0) { + cccc_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + case 104: { + + d_ = input.readInt64(); + break; + } + case 112: { + + dd_ = input.readInt64(); + break; + } + case 120: { + if (!((mutable_bitField0_ & 0x00000004) != 0)) { + ddd_ = newLongList(); + mutable_bitField0_ |= 0x00000004; + } + ddd_.addLong(input.readInt64()); + break; + } + case 122: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000004) != 0) && input.getBytesUntilLimit() > 0) { + ddd_ = newLongList(); + mutable_bitField0_ |= 0x00000004; + } + while (input.getBytesUntilLimit() > 0) { + ddd_.addLong(input.readInt64()); + } + input.popLimit(limit); + break; + } + case 128: { + if (!((mutable_bitField0_ & 0x00000008) != 0)) { + dddd_ = newLongList(); + mutable_bitField0_ |= 0x00000008; + } + dddd_.addLong(input.readInt64()); + break; + } + case 130: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000008) != 0) && input.getBytesUntilLimit() > 0) { + dddd_ = newLongList(); + mutable_bitField0_ |= 0x00000008; + } + while (input.getBytesUntilLimit() > 0) { + dddd_.addLong(input.readInt64()); + } + input.popLimit(limit); + break; + } + case 141: { + + e_ = input.readFloat(); + break; + } + case 149: { + + ee_ = input.readFloat(); + break; + } + case 157: { + if (!((mutable_bitField0_ & 0x00000010) != 0)) { + eee_ = newFloatList(); + mutable_bitField0_ |= 0x00000010; + } + eee_.addFloat(input.readFloat()); + break; + } + case 154: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000010) != 0) && input.getBytesUntilLimit() > 0) { + eee_ = newFloatList(); + mutable_bitField0_ |= 0x00000010; + } + while (input.getBytesUntilLimit() > 0) { + eee_.addFloat(input.readFloat()); + } + input.popLimit(limit); + break; + } + case 165: { + if (!((mutable_bitField0_ & 0x00000020) != 0)) { + eeee_ = newFloatList(); + mutable_bitField0_ |= 0x00000020; + } + eeee_.addFloat(input.readFloat()); + break; + } + case 162: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000020) != 0) && input.getBytesUntilLimit() > 0) { + eeee_ = newFloatList(); + mutable_bitField0_ |= 0x00000020; + } + while (input.getBytesUntilLimit() > 0) { + eeee_.addFloat(input.readFloat()); + } + input.popLimit(limit); + break; + } + case 169: { + + f_ = input.readDouble(); + break; + } + case 177: { + + ff_ = input.readDouble(); + break; + } + case 185: { + if (!((mutable_bitField0_ & 0x00000040) != 0)) { + fff_ = newDoubleList(); + mutable_bitField0_ |= 0x00000040; + } + fff_.addDouble(input.readDouble()); + break; + } + case 186: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000040) != 0) && input.getBytesUntilLimit() > 0) { + fff_ = newDoubleList(); + mutable_bitField0_ |= 0x00000040; + } + while (input.getBytesUntilLimit() > 0) { + fff_.addDouble(input.readDouble()); + } + input.popLimit(limit); + break; + } + case 193: { + if (!((mutable_bitField0_ & 0x00000080) != 0)) { + ffff_ = newDoubleList(); + mutable_bitField0_ |= 0x00000080; + } + ffff_.addDouble(input.readDouble()); + break; + } + case 194: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000080) != 0) && input.getBytesUntilLimit() > 0) { + ffff_ = newDoubleList(); + mutable_bitField0_ |= 0x00000080; + } + while (input.getBytesUntilLimit() > 0) { + ffff_.addDouble(input.readDouble()); + } + input.popLimit(limit); + break; + } + case 200: { + + g_ = input.readBool(); + break; + } + case 208: { + + gg_ = input.readBool(); + break; + } + case 216: { + if (!((mutable_bitField0_ & 0x00000100) != 0)) { + ggg_ = newBooleanList(); + mutable_bitField0_ |= 0x00000100; + } + ggg_.addBoolean(input.readBool()); + break; + } + case 218: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000100) != 0) && input.getBytesUntilLimit() > 0) { + ggg_ = newBooleanList(); + mutable_bitField0_ |= 0x00000100; + } + while (input.getBytesUntilLimit() > 0) { + ggg_.addBoolean(input.readBool()); + } + input.popLimit(limit); + break; + } + case 224: { + if (!((mutable_bitField0_ & 0x00000200) != 0)) { + gggg_ = newBooleanList(); + mutable_bitField0_ |= 0x00000200; + } + gggg_.addBoolean(input.readBool()); + break; + } + case 226: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000200) != 0) && input.getBytesUntilLimit() > 0) { + gggg_ = newBooleanList(); + mutable_bitField0_ |= 0x00000200; + } + while (input.getBytesUntilLimit() > 0) { + gggg_.addBoolean(input.readBool()); + } + input.popLimit(limit); + break; + } + case 234: { + java.lang.String s = input.readStringRequireUtf8(); + + h_ = s; + break; + } + case 242: { + java.lang.String s = input.readStringRequireUtf8(); + + hh_ = s; + break; + } + case 250: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00000400) != 0)) { + hhh_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000400; + } + hhh_.add(s); + break; + } + case 258: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00000800) != 0)) { + hhhh_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000800; + } + hhhh_.add(s); + break; + } + case 266: { + java.lang.String s = input.readStringRequireUtf8(); + + jj_ = s; + break; + } + case 274: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00001000) != 0)) { + jjj_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00001000; + } + jjj_.add(s); + break; + } + case 282: { + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder subBuilder = null; + if (kk_ != null) { + subBuilder = kk_.toBuilder(); + } + kk_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectA.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(kk_); + kk_ = subBuilder.buildPartial(); + } + + break; + } + case 290: { + if (!((mutable_bitField0_ & 0x00002000) != 0)) { + kkk_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00002000; + } + kkk_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectA.parser(), extensionRegistry)); + break; + } + case 296: { + if (!((mutable_bitField0_ & 0x00004000) != 0)) { + l_ = newIntList(); + mutable_bitField0_ |= 0x00004000; + } + l_.addInt(input.readInt32()); + break; + } + case 298: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00004000) != 0) && input.getBytesUntilLimit() > 0) { + l_ = newIntList(); + mutable_bitField0_ |= 0x00004000; + } + while (input.getBytesUntilLimit() > 0) { + l_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + case 306: { + if (!((mutable_bitField0_ & 0x00008000) != 0)) { + ll_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00008000; + } + ll_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListListInteger.parser(), extensionRegistry)); + break; + } + case 314: { + if (!((mutable_bitField0_ & 0x00010000) != 0)) { + lll_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00010000; + } + lll_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListObjectA.parser(), extensionRegistry)); + break; + } + case 322: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00020000) != 0)) { + llll_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00020000; + } + llll_.add(s); + break; + } + case 330: { + if (!((mutable_bitField0_ & 0x00040000) != 0)) { + lllll_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00040000; + } + lllll_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.parser(), extensionRegistry)); + break; + } + case 410: { + if (!((mutable_bitField0_ & 0x00080000) != 0)) { + m_ = com.google.protobuf.MapField.newMapField( + MDefaultEntryHolder.defaultEntry); + mutable_bitField0_ |= 0x00080000; + } + com.google.protobuf.MapEntry + m__ = input.readMessage( + MDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + m_.getMutableMap().put( + m__.getKey(), m__.getValue()); + break; + } + case 418: { + if (!((mutable_bitField0_ & 0x00100000) != 0)) { + mm_ = com.google.protobuf.MapField.newMapField( + MmDefaultEntryHolder.defaultEntry); + mutable_bitField0_ |= 0x00100000; + } + com.google.protobuf.MapEntry + mm__ = input.readMessage( + MmDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + mm_.getMutableMap().put( + mm__.getKey(), mm__.getValue()); + break; + } + case 426: { + if (!((mutable_bitField0_ & 0x00200000) != 0)) { + mmm_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00200000; + } + mmm_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.MapObjectA.parser(), extensionRegistry)); + break; + } + case 434: { + if (!((mutable_bitField0_ & 0x00400000) != 0)) { + mmmm_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00400000; + } + mmmm_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.parser(), extensionRegistry)); + break; + } + case 442: { + if (!((mutable_bitField0_ & 0x00800000) != 0)) { + mmmmm_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00800000; + } + mmmmm_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.parser(), extensionRegistry)); + break; + } + case 488: { + if (!((mutable_bitField0_ & 0x01000000) != 0)) { + s_ = newIntList(); + mutable_bitField0_ |= 0x01000000; + } + s_.addInt(input.readInt32()); + break; + } + case 490: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x01000000) != 0) && input.getBytesUntilLimit() > 0) { + s_ = newIntList(); + mutable_bitField0_ |= 0x01000000; + } + while (input.getBytesUntilLimit() > 0) { + s_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + case 498: { + if (!((mutable_bitField0_ & 0x02000000) != 0)) { + ss_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x02000000; + } + ss_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListListInteger.parser(), extensionRegistry)); + break; + } + case 506: { + if (!((mutable_bitField0_ & 0x04000000) != 0)) { + sss_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x04000000; + } + sss_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ListObjectA.parser(), extensionRegistry)); + break; + } + case 514: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x08000000) != 0)) { + ssss_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x08000000; + } + ssss_.add(s); + break; + } + case 522: { + if (!((mutable_bitField0_ & 0x10000000) != 0)) { + sssss_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x10000000; + } + sssss_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.parser(), extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + ccc_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000002) != 0)) { + cccc_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000004) != 0)) { + ddd_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000008) != 0)) { + dddd_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000010) != 0)) { + eee_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000020) != 0)) { + eeee_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000040) != 0)) { + fff_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000080) != 0)) { + ffff_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000100) != 0)) { + ggg_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000200) != 0)) { + gggg_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000400) != 0)) { + hhh_ = hhh_.getUnmodifiableView(); + } + if (((mutable_bitField0_ & 0x00000800) != 0)) { + hhhh_ = hhhh_.getUnmodifiableView(); + } + if (((mutable_bitField0_ & 0x00001000) != 0)) { + jjj_ = jjj_.getUnmodifiableView(); + } + if (((mutable_bitField0_ & 0x00002000) != 0)) { + kkk_ = java.util.Collections.unmodifiableList(kkk_); + } + if (((mutable_bitField0_ & 0x00004000) != 0)) { + l_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00008000) != 0)) { + ll_ = java.util.Collections.unmodifiableList(ll_); + } + if (((mutable_bitField0_ & 0x00010000) != 0)) { + lll_ = java.util.Collections.unmodifiableList(lll_); + } + if (((mutable_bitField0_ & 0x00020000) != 0)) { + llll_ = llll_.getUnmodifiableView(); + } + if (((mutable_bitField0_ & 0x00040000) != 0)) { + lllll_ = java.util.Collections.unmodifiableList(lllll_); + } + if (((mutable_bitField0_ & 0x00200000) != 0)) { + mmm_ = java.util.Collections.unmodifiableList(mmm_); + } + if (((mutable_bitField0_ & 0x00400000) != 0)) { + mmmm_ = java.util.Collections.unmodifiableList(mmmm_); + } + if (((mutable_bitField0_ & 0x00800000) != 0)) { + mmmmm_ = java.util.Collections.unmodifiableList(mmmmm_); + } + if (((mutable_bitField0_ & 0x01000000) != 0)) { + s_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x02000000) != 0)) { + ss_ = java.util.Collections.unmodifiableList(ss_); + } + if (((mutable_bitField0_ & 0x04000000) != 0)) { + sss_ = java.util.Collections.unmodifiableList(sss_); + } + if (((mutable_bitField0_ & 0x08000000) != 0)) { + ssss_ = ssss_.getUnmodifiableView(); + } + if (((mutable_bitField0_ & 0x10000000) != 0)) { + sssss_ = java.util.Collections.unmodifiableList(sssss_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + @java.lang.Override + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 51: + return internalGetM(); + case 52: + return internalGetMm(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.class, com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private int a_; + /** + *
+     * protobuf不支持单个byte,用int代替,增加了一点性能开销
+     * 
+ * + * int32 a = 1; + */ + public int getA() { + return a_; + } + + public static final int AA_FIELD_NUMBER = 2; + private int aa_; + /** + * int32 aa = 2; + */ + public int getAa() { + return aa_; + } + + public static final int AAA_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString aaa_; + /** + * bytes aaa = 3; + */ + public com.google.protobuf.ByteString getAaa() { + return aaa_; + } + + public static final int AAAA_FIELD_NUMBER = 4; + private com.google.protobuf.ByteString aaaa_; + /** + * bytes aaaa = 4; + */ + public com.google.protobuf.ByteString getAaaa() { + return aaaa_; + } + + public static final int B_FIELD_NUMBER = 5; + private int b_; + /** + *
+     * protobuf不支持单个short,用int代替,增加了一点性能开销
+     * 
+ * + * int32 b = 5; + */ + public int getB() { + return b_; + } + + public static final int BB_FIELD_NUMBER = 6; + private int bb_; + /** + * int32 bb = 6; + */ + public int getBb() { + return bb_; + } + + public static final int BBB_FIELD_NUMBER = 7; + private com.google.protobuf.ByteString bbb_; + /** + *
+     * protobuf不支持单个short,用bytes代替,减少了一点性能开销
+     * 
+ * + * bytes bbb = 7; + */ + public com.google.protobuf.ByteString getBbb() { + return bbb_; + } + + public static final int BBBB_FIELD_NUMBER = 8; + private com.google.protobuf.ByteString bbbb_; + /** + * bytes bbbb = 8; + */ + public com.google.protobuf.ByteString getBbbb() { + return bbbb_; + } + + public static final int C_FIELD_NUMBER = 9; + private int c_; + /** + * int32 c = 9; + */ + public int getC() { + return c_; + } + + public static final int CC_FIELD_NUMBER = 10; + private int cc_; + /** + * int32 cc = 10; + */ + public int getCc() { + return cc_; + } + + public static final int CCC_FIELD_NUMBER = 11; + private com.google.protobuf.Internal.IntList ccc_; + /** + * repeated int32 ccc = 11; + */ + public java.util.List + getCccList() { + return ccc_; + } + /** + * repeated int32 ccc = 11; + */ + public int getCccCount() { + return ccc_.size(); + } + /** + * repeated int32 ccc = 11; + */ + public int getCcc(int index) { + return ccc_.getInt(index); + } + private int cccMemoizedSerializedSize = -1; + + public static final int CCCC_FIELD_NUMBER = 12; + private com.google.protobuf.Internal.IntList cccc_; + /** + * repeated int32 cccc = 12; + */ + public java.util.List + getCcccList() { + return cccc_; + } + /** + * repeated int32 cccc = 12; + */ + public int getCcccCount() { + return cccc_.size(); + } + /** + * repeated int32 cccc = 12; + */ + public int getCccc(int index) { + return cccc_.getInt(index); + } + private int ccccMemoizedSerializedSize = -1; + + public static final int D_FIELD_NUMBER = 13; + private long d_; + /** + * int64 d = 13; + */ + public long getD() { + return d_; + } + + public static final int DD_FIELD_NUMBER = 14; + private long dd_; + /** + * int64 dd = 14; + */ + public long getDd() { + return dd_; + } + + public static final int DDD_FIELD_NUMBER = 15; + private com.google.protobuf.Internal.LongList ddd_; + /** + * repeated int64 ddd = 15; + */ + public java.util.List + getDddList() { + return ddd_; + } + /** + * repeated int64 ddd = 15; + */ + public int getDddCount() { + return ddd_.size(); + } + /** + * repeated int64 ddd = 15; + */ + public long getDdd(int index) { + return ddd_.getLong(index); + } + private int dddMemoizedSerializedSize = -1; + + public static final int DDDD_FIELD_NUMBER = 16; + private com.google.protobuf.Internal.LongList dddd_; + /** + * repeated int64 dddd = 16; + */ + public java.util.List + getDdddList() { + return dddd_; + } + /** + * repeated int64 dddd = 16; + */ + public int getDdddCount() { + return dddd_.size(); + } + /** + * repeated int64 dddd = 16; + */ + public long getDddd(int index) { + return dddd_.getLong(index); + } + private int ddddMemoizedSerializedSize = -1; + + public static final int E_FIELD_NUMBER = 17; + private float e_; + /** + * float e = 17; + */ + public float getE() { + return e_; + } + + public static final int EE_FIELD_NUMBER = 18; + private float ee_; + /** + * float ee = 18; + */ + public float getEe() { + return ee_; + } + + public static final int EEE_FIELD_NUMBER = 19; + private com.google.protobuf.Internal.FloatList eee_; + /** + * repeated float eee = 19; + */ + public java.util.List + getEeeList() { + return eee_; + } + /** + * repeated float eee = 19; + */ + public int getEeeCount() { + return eee_.size(); + } + /** + * repeated float eee = 19; + */ + public float getEee(int index) { + return eee_.getFloat(index); + } + private int eeeMemoizedSerializedSize = -1; + + public static final int EEEE_FIELD_NUMBER = 20; + private com.google.protobuf.Internal.FloatList eeee_; + /** + * repeated float eeee = 20; + */ + public java.util.List + getEeeeList() { + return eeee_; + } + /** + * repeated float eeee = 20; + */ + public int getEeeeCount() { + return eeee_.size(); + } + /** + * repeated float eeee = 20; + */ + public float getEeee(int index) { + return eeee_.getFloat(index); + } + private int eeeeMemoizedSerializedSize = -1; + + public static final int F_FIELD_NUMBER = 21; + private double f_; + /** + * double f = 21; + */ + public double getF() { + return f_; + } + + public static final int FF_FIELD_NUMBER = 22; + private double ff_; + /** + * double ff = 22; + */ + public double getFf() { + return ff_; + } + + public static final int FFF_FIELD_NUMBER = 23; + private com.google.protobuf.Internal.DoubleList fff_; + /** + * repeated double fff = 23; + */ + public java.util.List + getFffList() { + return fff_; + } + /** + * repeated double fff = 23; + */ + public int getFffCount() { + return fff_.size(); + } + /** + * repeated double fff = 23; + */ + public double getFff(int index) { + return fff_.getDouble(index); + } + private int fffMemoizedSerializedSize = -1; + + public static final int FFFF_FIELD_NUMBER = 24; + private com.google.protobuf.Internal.DoubleList ffff_; + /** + * repeated double ffff = 24; + */ + public java.util.List + getFfffList() { + return ffff_; + } + /** + * repeated double ffff = 24; + */ + public int getFfffCount() { + return ffff_.size(); + } + /** + * repeated double ffff = 24; + */ + public double getFfff(int index) { + return ffff_.getDouble(index); + } + private int ffffMemoizedSerializedSize = -1; + + public static final int G_FIELD_NUMBER = 25; + private boolean g_; + /** + * bool g = 25; + */ + public boolean getG() { + return g_; + } + + public static final int GG_FIELD_NUMBER = 26; + private boolean gg_; + /** + * bool gg = 26; + */ + public boolean getGg() { + return gg_; + } + + public static final int GGG_FIELD_NUMBER = 27; + private com.google.protobuf.Internal.BooleanList ggg_; + /** + * repeated bool ggg = 27; + */ + public java.util.List + getGggList() { + return ggg_; + } + /** + * repeated bool ggg = 27; + */ + public int getGggCount() { + return ggg_.size(); + } + /** + * repeated bool ggg = 27; + */ + public boolean getGgg(int index) { + return ggg_.getBoolean(index); + } + private int gggMemoizedSerializedSize = -1; + + public static final int GGGG_FIELD_NUMBER = 28; + private com.google.protobuf.Internal.BooleanList gggg_; + /** + * repeated bool gggg = 28; + */ + public java.util.List + getGgggList() { + return gggg_; + } + /** + * repeated bool gggg = 28; + */ + public int getGgggCount() { + return gggg_.size(); + } + /** + * repeated bool gggg = 28; + */ + public boolean getGggg(int index) { + return gggg_.getBoolean(index); + } + private int ggggMemoizedSerializedSize = -1; + + public static final int H_FIELD_NUMBER = 29; + private volatile java.lang.Object h_; + /** + *
+     * protobuf不支持char,用string代替,增加了一点性能开销
+     * 
+ * + * string h = 29; + */ + public java.lang.String getH() { + java.lang.Object ref = h_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + h_ = s; + return s; + } + } + /** + *
+     * protobuf不支持char,用string代替,增加了一点性能开销
+     * 
+ * + * string h = 29; + */ + public com.google.protobuf.ByteString + getHBytes() { + java.lang.Object ref = h_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + h_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int HH_FIELD_NUMBER = 30; + private volatile java.lang.Object hh_; + /** + * string hh = 30; + */ + public java.lang.String getHh() { + java.lang.Object ref = hh_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + hh_ = s; + return s; + } + } + /** + * string hh = 30; + */ + public com.google.protobuf.ByteString + getHhBytes() { + java.lang.Object ref = hh_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + hh_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int HHH_FIELD_NUMBER = 31; + private com.google.protobuf.LazyStringList hhh_; + /** + * repeated string hhh = 31; + */ + public com.google.protobuf.ProtocolStringList + getHhhList() { + return hhh_; + } + /** + * repeated string hhh = 31; + */ + public int getHhhCount() { + return hhh_.size(); + } + /** + * repeated string hhh = 31; + */ + public java.lang.String getHhh(int index) { + return hhh_.get(index); + } + /** + * repeated string hhh = 31; + */ + public com.google.protobuf.ByteString + getHhhBytes(int index) { + return hhh_.getByteString(index); + } + + public static final int HHHH_FIELD_NUMBER = 32; + private com.google.protobuf.LazyStringList hhhh_; + /** + * repeated string hhhh = 32; + */ + public com.google.protobuf.ProtocolStringList + getHhhhList() { + return hhhh_; + } + /** + * repeated string hhhh = 32; + */ + public int getHhhhCount() { + return hhhh_.size(); + } + /** + * repeated string hhhh = 32; + */ + public java.lang.String getHhhh(int index) { + return hhhh_.get(index); + } + /** + * repeated string hhhh = 32; + */ + public com.google.protobuf.ByteString + getHhhhBytes(int index) { + return hhhh_.getByteString(index); + } + + public static final int JJ_FIELD_NUMBER = 33; + private volatile java.lang.Object jj_; + /** + * string jj = 33; + */ + public java.lang.String getJj() { + java.lang.Object ref = jj_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + jj_ = s; + return s; + } + } + /** + * string jj = 33; + */ + public com.google.protobuf.ByteString + getJjBytes() { + java.lang.Object ref = jj_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + jj_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int JJJ_FIELD_NUMBER = 34; + private com.google.protobuf.LazyStringList jjj_; + /** + * repeated string jjj = 34; + */ + public com.google.protobuf.ProtocolStringList + getJjjList() { + return jjj_; + } + /** + * repeated string jjj = 34; + */ + public int getJjjCount() { + return jjj_.size(); + } + /** + * repeated string jjj = 34; + */ + public java.lang.String getJjj(int index) { + return jjj_.get(index); + } + /** + * repeated string jjj = 34; + */ + public com.google.protobuf.ByteString + getJjjBytes(int index) { + return jjj_.getByteString(index); + } + + public static final int KK_FIELD_NUMBER = 35; + private com.zfoo.protocol.packet.ProtobufObject.ObjectA kk_; + /** + * .ObjectA kk = 35; + */ + public boolean hasKk() { + return kk_ != null; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKk() { + return kk_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : kk_; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkOrBuilder() { + return getKk(); + } + + public static final int KKK_FIELD_NUMBER = 36; + private java.util.List kkk_; + /** + * repeated .ObjectA kkk = 36; + */ + public java.util.List getKkkList() { + return kkk_; + } + /** + * repeated .ObjectA kkk = 36; + */ + public java.util.List + getKkkOrBuilderList() { + return kkk_; + } + /** + * repeated .ObjectA kkk = 36; + */ + public int getKkkCount() { + return kkk_.size(); + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKkk(int index) { + return kkk_.get(index); + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkkOrBuilder( + int index) { + return kkk_.get(index); + } + + public static final int L_FIELD_NUMBER = 37; + private com.google.protobuf.Internal.IntList l_; + /** + * repeated int32 l = 37; + */ + public java.util.List + getLList() { + return l_; + } + /** + * repeated int32 l = 37; + */ + public int getLCount() { + return l_.size(); + } + /** + * repeated int32 l = 37; + */ + public int getL(int index) { + return l_.getInt(index); + } + private int lMemoizedSerializedSize = -1; + + public static final int LL_FIELD_NUMBER = 38; + private java.util.List ll_; + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + public java.util.List getLlList() { + return ll_; + } + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + public java.util.List + getLlOrBuilderList() { + return ll_; + } + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + public int getLlCount() { + return ll_.size(); + } + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getLl(int index) { + return ll_.get(index); + } + /** + *
+     * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+     * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getLlOrBuilder( + int index) { + return ll_.get(index); + } + + public static final int LLL_FIELD_NUMBER = 39; + private java.util.List lll_; + /** + * repeated .ListObjectA lll = 39; + */ + public java.util.List getLllList() { + return lll_; + } + /** + * repeated .ListObjectA lll = 39; + */ + public java.util.List + getLllOrBuilderList() { + return lll_; + } + /** + * repeated .ListObjectA lll = 39; + */ + public int getLllCount() { + return lll_.size(); + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getLll(int index) { + return lll_.get(index); + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getLllOrBuilder( + int index) { + return lll_.get(index); + } + + public static final int LLLL_FIELD_NUMBER = 40; + private com.google.protobuf.LazyStringList llll_; + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ProtocolStringList + getLlllList() { + return llll_; + } + /** + * repeated string llll = 40; + */ + public int getLlllCount() { + return llll_.size(); + } + /** + * repeated string llll = 40; + */ + public java.lang.String getLlll(int index) { + return llll_.get(index); + } + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ByteString + getLlllBytes(int index) { + return llll_.getByteString(index); + } + + public static final int LLLLL_FIELD_NUMBER = 41; + private java.util.List lllll_; + /** + * repeated .MapIntegerString lllll = 41; + */ + public java.util.List getLllllList() { + return lllll_; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public java.util.List + getLllllOrBuilderList() { + return lllll_; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public int getLllllCount() { + return lllll_.size(); + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getLllll(int index) { + return lllll_.get(index); + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getLllllOrBuilder( + int index) { + return lllll_.get(index); + } + + public static final int M_FIELD_NUMBER = 51; + private static final class MDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.Integer, java.lang.String> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_MEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.INT32, + 0, + com.google.protobuf.WireFormat.FieldType.STRING, + ""); + } + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> m_; + private com.google.protobuf.MapField + internalGetM() { + if (m_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MDefaultEntryHolder.defaultEntry); + } + return m_; + } + + public int getMCount() { + return internalGetM().getMap().size(); + } + /** + * map<int32, string> m = 51; + */ + + public boolean containsM( + int key) { + + return internalGetM().getMap().containsKey(key); + } + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getM() { + return getMMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.util.Map getMMap() { + return internalGetM().getMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetM().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrThrow( + int key) { + + java.util.Map map = + internalGetM().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public static final int MM_FIELD_NUMBER = 52; + private static final class MmDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.Integer, com.zfoo.protocol.packet.ProtobufObject.ObjectA> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_MmEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.INT32, + 0, + com.google.protobuf.WireFormat.FieldType.MESSAGE, + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + private com.google.protobuf.MapField< + java.lang.Integer, com.zfoo.protocol.packet.ProtobufObject.ObjectA> mm_; + private com.google.protobuf.MapField + internalGetMm() { + if (mm_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MmDefaultEntryHolder.defaultEntry); + } + return mm_; + } + + public int getMmCount() { + return internalGetMm().getMap().size(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public boolean containsMm( + int key) { + + return internalGetMm().getMap().containsKey(key); + } + /** + * Use {@link #getMmMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getMm() { + return getMmMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public java.util.Map getMmMap() { + return internalGetMm().getMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrDefault( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA defaultValue) { + + java.util.Map map = + internalGetMm().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrThrow( + int key) { + + java.util.Map map = + internalGetMm().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public static final int MMM_FIELD_NUMBER = 53; + private java.util.List mmm_; + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public java.util.List getMmmList() { + return mmm_; + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public java.util.List + getMmmOrBuilderList() { + return mmm_; + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public int getMmmCount() { + return mmm_.size(); + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA getMmm(int index) { + return mmm_.get(index); + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder getMmmOrBuilder( + int index) { + return mmm_.get(index); + } + + public static final int MMMM_FIELD_NUMBER = 54; + private java.util.List mmmm_; + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public java.util.List getMmmmList() { + return mmmm_; + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public java.util.List + getMmmmOrBuilderList() { + return mmmm_; + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public int getMmmmCount() { + return mmmm_.size(); + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA getMmmm(int index) { + return mmmm_.get(index); + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder getMmmmOrBuilder( + int index) { + return mmmm_.get(index); + } + + public static final int MMMMM_FIELD_NUMBER = 55; + private java.util.List mmmmm_; + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public java.util.List getMmmmmList() { + return mmmmm_; + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public java.util.List + getMmmmmOrBuilderList() { + return mmmmm_; + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public int getMmmmmCount() { + return mmmmm_.size(); + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger getMmmmm(int index) { + return mmmmm_.get(index); + } + /** + *
+     * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+     * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder getMmmmmOrBuilder( + int index) { + return mmmmm_.get(index); + } + + public static final int S_FIELD_NUMBER = 61; + private com.google.protobuf.Internal.IntList s_; + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated int32 s = 61; + */ + public java.util.List + getSList() { + return s_; + } + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated int32 s = 61; + */ + public int getSCount() { + return s_.size(); + } + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated int32 s = 61; + */ + public int getS(int index) { + return s_.getInt(index); + } + private int sMemoizedSerializedSize = -1; + + public static final int SS_FIELD_NUMBER = 62; + private java.util.List ss_; + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + public java.util.List getSsList() { + return ss_; + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + public java.util.List + getSsOrBuilderList() { + return ss_; + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + public int getSsCount() { + return ss_.size(); + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getSs(int index) { + return ss_.get(index); + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getSsOrBuilder( + int index) { + return ss_.get(index); + } + + public static final int SSS_FIELD_NUMBER = 63; + private java.util.List sss_; + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + public java.util.List getSssList() { + return sss_; + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + public java.util.List + getSssOrBuilderList() { + return sss_; + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + public int getSssCount() { + return sss_.size(); + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getSss(int index) { + return sss_.get(index); + } + /** + *
+     * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getSssOrBuilder( + int index) { + return sss_.get(index); + } + + public static final int SSSS_FIELD_NUMBER = 64; + private com.google.protobuf.LazyStringList ssss_; + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + public com.google.protobuf.ProtocolStringList + getSsssList() { + return ssss_; + } + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + public int getSsssCount() { + return ssss_.size(); + } + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + public java.lang.String getSsss(int index) { + return ssss_.get(index); + } + /** + *
+     * protobuf不支持set,用数组代替,减少了很多性能开销
+     * 
+ * + * repeated string ssss = 64; + */ + public com.google.protobuf.ByteString + getSsssBytes(int index) { + return ssss_.getByteString(index); + } + + public static final int SSSSS_FIELD_NUMBER = 65; + private java.util.List sssss_; + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public java.util.List getSssssList() { + return sssss_; + } + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public java.util.List + getSssssOrBuilderList() { + return sssss_; + } + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public int getSssssCount() { + return sssss_.size(); + } + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getSssss(int index) { + return sssss_.get(index); + } + /** + *
+     * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+     * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getSssssOrBuilder( + int index) { + return sssss_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (a_ != 0) { + output.writeInt32(1, a_); + } + if (aa_ != 0) { + output.writeInt32(2, aa_); + } + if (!aaa_.isEmpty()) { + output.writeBytes(3, aaa_); + } + if (!aaaa_.isEmpty()) { + output.writeBytes(4, aaaa_); + } + if (b_ != 0) { + output.writeInt32(5, b_); + } + if (bb_ != 0) { + output.writeInt32(6, bb_); + } + if (!bbb_.isEmpty()) { + output.writeBytes(7, bbb_); + } + if (!bbbb_.isEmpty()) { + output.writeBytes(8, bbbb_); + } + if (c_ != 0) { + output.writeInt32(9, c_); + } + if (cc_ != 0) { + output.writeInt32(10, cc_); + } + if (getCccList().size() > 0) { + output.writeUInt32NoTag(90); + output.writeUInt32NoTag(cccMemoizedSerializedSize); + } + for (int i = 0; i < ccc_.size(); i++) { + output.writeInt32NoTag(ccc_.getInt(i)); + } + if (getCcccList().size() > 0) { + output.writeUInt32NoTag(98); + output.writeUInt32NoTag(ccccMemoizedSerializedSize); + } + for (int i = 0; i < cccc_.size(); i++) { + output.writeInt32NoTag(cccc_.getInt(i)); + } + if (d_ != 0L) { + output.writeInt64(13, d_); + } + if (dd_ != 0L) { + output.writeInt64(14, dd_); + } + if (getDddList().size() > 0) { + output.writeUInt32NoTag(122); + output.writeUInt32NoTag(dddMemoizedSerializedSize); + } + for (int i = 0; i < ddd_.size(); i++) { + output.writeInt64NoTag(ddd_.getLong(i)); + } + if (getDdddList().size() > 0) { + output.writeUInt32NoTag(130); + output.writeUInt32NoTag(ddddMemoizedSerializedSize); + } + for (int i = 0; i < dddd_.size(); i++) { + output.writeInt64NoTag(dddd_.getLong(i)); + } + if (e_ != 0F) { + output.writeFloat(17, e_); + } + if (ee_ != 0F) { + output.writeFloat(18, ee_); + } + if (getEeeList().size() > 0) { + output.writeUInt32NoTag(154); + output.writeUInt32NoTag(eeeMemoizedSerializedSize); + } + for (int i = 0; i < eee_.size(); i++) { + output.writeFloatNoTag(eee_.getFloat(i)); + } + if (getEeeeList().size() > 0) { + output.writeUInt32NoTag(162); + output.writeUInt32NoTag(eeeeMemoizedSerializedSize); + } + for (int i = 0; i < eeee_.size(); i++) { + output.writeFloatNoTag(eeee_.getFloat(i)); + } + if (f_ != 0D) { + output.writeDouble(21, f_); + } + if (ff_ != 0D) { + output.writeDouble(22, ff_); + } + if (getFffList().size() > 0) { + output.writeUInt32NoTag(186); + output.writeUInt32NoTag(fffMemoizedSerializedSize); + } + for (int i = 0; i < fff_.size(); i++) { + output.writeDoubleNoTag(fff_.getDouble(i)); + } + if (getFfffList().size() > 0) { + output.writeUInt32NoTag(194); + output.writeUInt32NoTag(ffffMemoizedSerializedSize); + } + for (int i = 0; i < ffff_.size(); i++) { + output.writeDoubleNoTag(ffff_.getDouble(i)); + } + if (g_ != false) { + output.writeBool(25, g_); + } + if (gg_ != false) { + output.writeBool(26, gg_); + } + if (getGggList().size() > 0) { + output.writeUInt32NoTag(218); + output.writeUInt32NoTag(gggMemoizedSerializedSize); + } + for (int i = 0; i < ggg_.size(); i++) { + output.writeBoolNoTag(ggg_.getBoolean(i)); + } + if (getGgggList().size() > 0) { + output.writeUInt32NoTag(226); + output.writeUInt32NoTag(ggggMemoizedSerializedSize); + } + for (int i = 0; i < gggg_.size(); i++) { + output.writeBoolNoTag(gggg_.getBoolean(i)); + } + if (!getHBytes().isEmpty()) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 29, h_); + } + if (!getHhBytes().isEmpty()) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 30, hh_); + } + for (int i = 0; i < hhh_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 31, hhh_.getRaw(i)); + } + for (int i = 0; i < hhhh_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 32, hhhh_.getRaw(i)); + } + if (!getJjBytes().isEmpty()) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 33, jj_); + } + for (int i = 0; i < jjj_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 34, jjj_.getRaw(i)); + } + if (kk_ != null) { + output.writeMessage(35, getKk()); + } + for (int i = 0; i < kkk_.size(); i++) { + output.writeMessage(36, kkk_.get(i)); + } + if (getLList().size() > 0) { + output.writeUInt32NoTag(298); + output.writeUInt32NoTag(lMemoizedSerializedSize); + } + for (int i = 0; i < l_.size(); i++) { + output.writeInt32NoTag(l_.getInt(i)); + } + for (int i = 0; i < ll_.size(); i++) { + output.writeMessage(38, ll_.get(i)); + } + for (int i = 0; i < lll_.size(); i++) { + output.writeMessage(39, lll_.get(i)); + } + for (int i = 0; i < llll_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 40, llll_.getRaw(i)); + } + for (int i = 0; i < lllll_.size(); i++) { + output.writeMessage(41, lllll_.get(i)); + } + com.google.protobuf.GeneratedMessageV3 + .serializeIntegerMapTo( + output, + internalGetM(), + MDefaultEntryHolder.defaultEntry, + 51); + com.google.protobuf.GeneratedMessageV3 + .serializeIntegerMapTo( + output, + internalGetMm(), + MmDefaultEntryHolder.defaultEntry, + 52); + for (int i = 0; i < mmm_.size(); i++) { + output.writeMessage(53, mmm_.get(i)); + } + for (int i = 0; i < mmmm_.size(); i++) { + output.writeMessage(54, mmmm_.get(i)); + } + for (int i = 0; i < mmmmm_.size(); i++) { + output.writeMessage(55, mmmmm_.get(i)); + } + if (getSList().size() > 0) { + output.writeUInt32NoTag(490); + output.writeUInt32NoTag(sMemoizedSerializedSize); + } + for (int i = 0; i < s_.size(); i++) { + output.writeInt32NoTag(s_.getInt(i)); + } + for (int i = 0; i < ss_.size(); i++) { + output.writeMessage(62, ss_.get(i)); + } + for (int i = 0; i < sss_.size(); i++) { + output.writeMessage(63, sss_.get(i)); + } + for (int i = 0; i < ssss_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 64, ssss_.getRaw(i)); + } + for (int i = 0; i < sssss_.size(); i++) { + output.writeMessage(65, sssss_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (a_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, a_); + } + if (aa_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, aa_); + } + if (!aaa_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, aaa_); + } + if (!aaaa_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, aaaa_); + } + if (b_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(5, b_); + } + if (bb_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(6, bb_); + } + if (!bbb_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(7, bbb_); + } + if (!bbbb_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(8, bbbb_); + } + if (c_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(9, c_); + } + if (cc_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(10, cc_); + } + { + int dataSize = 0; + for (int i = 0; i < ccc_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(ccc_.getInt(i)); + } + size += dataSize; + if (!getCccList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + cccMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + for (int i = 0; i < cccc_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(cccc_.getInt(i)); + } + size += dataSize; + if (!getCcccList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + ccccMemoizedSerializedSize = dataSize; + } + if (d_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(13, d_); + } + if (dd_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(14, dd_); + } + { + int dataSize = 0; + for (int i = 0; i < ddd_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt64SizeNoTag(ddd_.getLong(i)); + } + size += dataSize; + if (!getDddList().isEmpty()) { + size += 1; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + dddMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + for (int i = 0; i < dddd_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt64SizeNoTag(dddd_.getLong(i)); + } + size += dataSize; + if (!getDdddList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + ddddMemoizedSerializedSize = dataSize; + } + if (e_ != 0F) { + size += com.google.protobuf.CodedOutputStream + .computeFloatSize(17, e_); + } + if (ee_ != 0F) { + size += com.google.protobuf.CodedOutputStream + .computeFloatSize(18, ee_); + } + { + int dataSize = 0; + dataSize = 4 * getEeeList().size(); + size += dataSize; + if (!getEeeList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + eeeMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + dataSize = 4 * getEeeeList().size(); + size += dataSize; + if (!getEeeeList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + eeeeMemoizedSerializedSize = dataSize; + } + if (f_ != 0D) { + size += com.google.protobuf.CodedOutputStream + .computeDoubleSize(21, f_); + } + if (ff_ != 0D) { + size += com.google.protobuf.CodedOutputStream + .computeDoubleSize(22, ff_); + } + { + int dataSize = 0; + dataSize = 8 * getFffList().size(); + size += dataSize; + if (!getFffList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + fffMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + dataSize = 8 * getFfffList().size(); + size += dataSize; + if (!getFfffList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + ffffMemoizedSerializedSize = dataSize; + } + if (g_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(25, g_); + } + if (gg_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(26, gg_); + } + { + int dataSize = 0; + dataSize = 1 * getGggList().size(); + size += dataSize; + if (!getGggList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + gggMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + dataSize = 1 * getGgggList().size(); + size += dataSize; + if (!getGgggList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + ggggMemoizedSerializedSize = dataSize; + } + if (!getHBytes().isEmpty()) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(29, h_); + } + if (!getHhBytes().isEmpty()) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(30, hh_); + } + { + int dataSize = 0; + for (int i = 0; i < hhh_.size(); i++) { + dataSize += computeStringSizeNoTag(hhh_.getRaw(i)); + } + size += dataSize; + size += 2 * getHhhList().size(); + } + { + int dataSize = 0; + for (int i = 0; i < hhhh_.size(); i++) { + dataSize += computeStringSizeNoTag(hhhh_.getRaw(i)); + } + size += dataSize; + size += 2 * getHhhhList().size(); + } + if (!getJjBytes().isEmpty()) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(33, jj_); + } + { + int dataSize = 0; + for (int i = 0; i < jjj_.size(); i++) { + dataSize += computeStringSizeNoTag(jjj_.getRaw(i)); + } + size += dataSize; + size += 2 * getJjjList().size(); + } + if (kk_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(35, getKk()); + } + for (int i = 0; i < kkk_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(36, kkk_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < l_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(l_.getInt(i)); + } + size += dataSize; + if (!getLList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + lMemoizedSerializedSize = dataSize; + } + for (int i = 0; i < ll_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(38, ll_.get(i)); + } + for (int i = 0; i < lll_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(39, lll_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < llll_.size(); i++) { + dataSize += computeStringSizeNoTag(llll_.getRaw(i)); + } + size += dataSize; + size += 2 * getLlllList().size(); + } + for (int i = 0; i < lllll_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(41, lllll_.get(i)); + } + for (java.util.Map.Entry entry + : internalGetM().getMap().entrySet()) { + com.google.protobuf.MapEntry + m__ = MDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(51, m__); + } + for (java.util.Map.Entry entry + : internalGetMm().getMap().entrySet()) { + com.google.protobuf.MapEntry + mm__ = MmDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(52, mm__); + } + for (int i = 0; i < mmm_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(53, mmm_.get(i)); + } + for (int i = 0; i < mmmm_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(54, mmmm_.get(i)); + } + for (int i = 0; i < mmmmm_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(55, mmmmm_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < s_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(s_.getInt(i)); + } + size += dataSize; + if (!getSList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + sMemoizedSerializedSize = dataSize; + } + for (int i = 0; i < ss_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(62, ss_.get(i)); + } + for (int i = 0; i < sss_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(63, sss_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < ssss_.size(); i++) { + dataSize += computeStringSizeNoTag(ssss_.getRaw(i)); + } + size += dataSize; + size += 2 * getSsssList().size(); + } + for (int i = 0; i < sssss_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(65, sssss_.get(i)); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject other = (com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject) obj; + + if (getA() + != other.getA()) return false; + if (getAa() + != other.getAa()) return false; + if (!getAaa() + .equals(other.getAaa())) return false; + if (!getAaaa() + .equals(other.getAaaa())) return false; + if (getB() + != other.getB()) return false; + if (getBb() + != other.getBb()) return false; + if (!getBbb() + .equals(other.getBbb())) return false; + if (!getBbbb() + .equals(other.getBbbb())) return false; + if (getC() + != other.getC()) return false; + if (getCc() + != other.getCc()) return false; + if (!getCccList() + .equals(other.getCccList())) return false; + if (!getCcccList() + .equals(other.getCcccList())) return false; + if (getD() + != other.getD()) return false; + if (getDd() + != other.getDd()) return false; + if (!getDddList() + .equals(other.getDddList())) return false; + if (!getDdddList() + .equals(other.getDdddList())) return false; + if (java.lang.Float.floatToIntBits(getE()) + != java.lang.Float.floatToIntBits( + other.getE())) return false; + if (java.lang.Float.floatToIntBits(getEe()) + != java.lang.Float.floatToIntBits( + other.getEe())) return false; + if (!getEeeList() + .equals(other.getEeeList())) return false; + if (!getEeeeList() + .equals(other.getEeeeList())) return false; + if (java.lang.Double.doubleToLongBits(getF()) + != java.lang.Double.doubleToLongBits( + other.getF())) return false; + if (java.lang.Double.doubleToLongBits(getFf()) + != java.lang.Double.doubleToLongBits( + other.getFf())) return false; + if (!getFffList() + .equals(other.getFffList())) return false; + if (!getFfffList() + .equals(other.getFfffList())) return false; + if (getG() + != other.getG()) return false; + if (getGg() + != other.getGg()) return false; + if (!getGggList() + .equals(other.getGggList())) return false; + if (!getGgggList() + .equals(other.getGgggList())) return false; + if (!getH() + .equals(other.getH())) return false; + if (!getHh() + .equals(other.getHh())) return false; + if (!getHhhList() + .equals(other.getHhhList())) return false; + if (!getHhhhList() + .equals(other.getHhhhList())) return false; + if (!getJj() + .equals(other.getJj())) return false; + if (!getJjjList() + .equals(other.getJjjList())) return false; + if (hasKk() != other.hasKk()) return false; + if (hasKk()) { + if (!getKk() + .equals(other.getKk())) return false; + } + if (!getKkkList() + .equals(other.getKkkList())) return false; + if (!getLList() + .equals(other.getLList())) return false; + if (!getLlList() + .equals(other.getLlList())) return false; + if (!getLllList() + .equals(other.getLllList())) return false; + if (!getLlllList() + .equals(other.getLlllList())) return false; + if (!getLllllList() + .equals(other.getLllllList())) return false; + if (!internalGetM().equals( + other.internalGetM())) return false; + if (!internalGetMm().equals( + other.internalGetMm())) return false; + if (!getMmmList() + .equals(other.getMmmList())) return false; + if (!getMmmmList() + .equals(other.getMmmmList())) return false; + if (!getMmmmmList() + .equals(other.getMmmmmList())) return false; + if (!getSList() + .equals(other.getSList())) return false; + if (!getSsList() + .equals(other.getSsList())) return false; + if (!getSssList() + .equals(other.getSssList())) return false; + if (!getSsssList() + .equals(other.getSsssList())) return false; + if (!getSssssList() + .equals(other.getSssssList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getA(); + hash = (37 * hash) + AA_FIELD_NUMBER; + hash = (53 * hash) + getAa(); + hash = (37 * hash) + AAA_FIELD_NUMBER; + hash = (53 * hash) + getAaa().hashCode(); + hash = (37 * hash) + AAAA_FIELD_NUMBER; + hash = (53 * hash) + getAaaa().hashCode(); + hash = (37 * hash) + B_FIELD_NUMBER; + hash = (53 * hash) + getB(); + hash = (37 * hash) + BB_FIELD_NUMBER; + hash = (53 * hash) + getBb(); + hash = (37 * hash) + BBB_FIELD_NUMBER; + hash = (53 * hash) + getBbb().hashCode(); + hash = (37 * hash) + BBBB_FIELD_NUMBER; + hash = (53 * hash) + getBbbb().hashCode(); + hash = (37 * hash) + C_FIELD_NUMBER; + hash = (53 * hash) + getC(); + hash = (37 * hash) + CC_FIELD_NUMBER; + hash = (53 * hash) + getCc(); + if (getCccCount() > 0) { + hash = (37 * hash) + CCC_FIELD_NUMBER; + hash = (53 * hash) + getCccList().hashCode(); + } + if (getCcccCount() > 0) { + hash = (37 * hash) + CCCC_FIELD_NUMBER; + hash = (53 * hash) + getCcccList().hashCode(); + } + hash = (37 * hash) + D_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getD()); + hash = (37 * hash) + DD_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getDd()); + if (getDddCount() > 0) { + hash = (37 * hash) + DDD_FIELD_NUMBER; + hash = (53 * hash) + getDddList().hashCode(); + } + if (getDdddCount() > 0) { + hash = (37 * hash) + DDDD_FIELD_NUMBER; + hash = (53 * hash) + getDdddList().hashCode(); + } + hash = (37 * hash) + E_FIELD_NUMBER; + hash = (53 * hash) + java.lang.Float.floatToIntBits( + getE()); + hash = (37 * hash) + EE_FIELD_NUMBER; + hash = (53 * hash) + java.lang.Float.floatToIntBits( + getEe()); + if (getEeeCount() > 0) { + hash = (37 * hash) + EEE_FIELD_NUMBER; + hash = (53 * hash) + getEeeList().hashCode(); + } + if (getEeeeCount() > 0) { + hash = (37 * hash) + EEEE_FIELD_NUMBER; + hash = (53 * hash) + getEeeeList().hashCode(); + } + hash = (37 * hash) + F_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + java.lang.Double.doubleToLongBits(getF())); + hash = (37 * hash) + FF_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + java.lang.Double.doubleToLongBits(getFf())); + if (getFffCount() > 0) { + hash = (37 * hash) + FFF_FIELD_NUMBER; + hash = (53 * hash) + getFffList().hashCode(); + } + if (getFfffCount() > 0) { + hash = (37 * hash) + FFFF_FIELD_NUMBER; + hash = (53 * hash) + getFfffList().hashCode(); + } + hash = (37 * hash) + G_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getG()); + hash = (37 * hash) + GG_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getGg()); + if (getGggCount() > 0) { + hash = (37 * hash) + GGG_FIELD_NUMBER; + hash = (53 * hash) + getGggList().hashCode(); + } + if (getGgggCount() > 0) { + hash = (37 * hash) + GGGG_FIELD_NUMBER; + hash = (53 * hash) + getGgggList().hashCode(); + } + hash = (37 * hash) + H_FIELD_NUMBER; + hash = (53 * hash) + getH().hashCode(); + hash = (37 * hash) + HH_FIELD_NUMBER; + hash = (53 * hash) + getHh().hashCode(); + if (getHhhCount() > 0) { + hash = (37 * hash) + HHH_FIELD_NUMBER; + hash = (53 * hash) + getHhhList().hashCode(); + } + if (getHhhhCount() > 0) { + hash = (37 * hash) + HHHH_FIELD_NUMBER; + hash = (53 * hash) + getHhhhList().hashCode(); + } + hash = (37 * hash) + JJ_FIELD_NUMBER; + hash = (53 * hash) + getJj().hashCode(); + if (getJjjCount() > 0) { + hash = (37 * hash) + JJJ_FIELD_NUMBER; + hash = (53 * hash) + getJjjList().hashCode(); + } + if (hasKk()) { + hash = (37 * hash) + KK_FIELD_NUMBER; + hash = (53 * hash) + getKk().hashCode(); + } + if (getKkkCount() > 0) { + hash = (37 * hash) + KKK_FIELD_NUMBER; + hash = (53 * hash) + getKkkList().hashCode(); + } + if (getLCount() > 0) { + hash = (37 * hash) + L_FIELD_NUMBER; + hash = (53 * hash) + getLList().hashCode(); + } + if (getLlCount() > 0) { + hash = (37 * hash) + LL_FIELD_NUMBER; + hash = (53 * hash) + getLlList().hashCode(); + } + if (getLllCount() > 0) { + hash = (37 * hash) + LLL_FIELD_NUMBER; + hash = (53 * hash) + getLllList().hashCode(); + } + if (getLlllCount() > 0) { + hash = (37 * hash) + LLLL_FIELD_NUMBER; + hash = (53 * hash) + getLlllList().hashCode(); + } + if (getLllllCount() > 0) { + hash = (37 * hash) + LLLLL_FIELD_NUMBER; + hash = (53 * hash) + getLllllList().hashCode(); + } + if (!internalGetM().getMap().isEmpty()) { + hash = (37 * hash) + M_FIELD_NUMBER; + hash = (53 * hash) + internalGetM().hashCode(); + } + if (!internalGetMm().getMap().isEmpty()) { + hash = (37 * hash) + MM_FIELD_NUMBER; + hash = (53 * hash) + internalGetMm().hashCode(); + } + if (getMmmCount() > 0) { + hash = (37 * hash) + MMM_FIELD_NUMBER; + hash = (53 * hash) + getMmmList().hashCode(); + } + if (getMmmmCount() > 0) { + hash = (37 * hash) + MMMM_FIELD_NUMBER; + hash = (53 * hash) + getMmmmList().hashCode(); + } + if (getMmmmmCount() > 0) { + hash = (37 * hash) + MMMMM_FIELD_NUMBER; + hash = (53 * hash) + getMmmmmList().hashCode(); + } + if (getSCount() > 0) { + hash = (37 * hash) + S_FIELD_NUMBER; + hash = (53 * hash) + getSList().hashCode(); + } + if (getSsCount() > 0) { + hash = (37 * hash) + SS_FIELD_NUMBER; + hash = (53 * hash) + getSsList().hashCode(); + } + if (getSssCount() > 0) { + hash = (37 * hash) + SSS_FIELD_NUMBER; + hash = (53 * hash) + getSssList().hashCode(); + } + if (getSsssCount() > 0) { + hash = (37 * hash) + SSSS_FIELD_NUMBER; + hash = (53 * hash) + getSsssList().hashCode(); + } + if (getSssssCount() > 0) { + hash = (37 * hash) + SSSSS_FIELD_NUMBER; + hash = (53 * hash) + getSssssList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ProtobufComplexObject} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ProtobufComplexObject) + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObjectOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 51: + return internalGetM(); + case 52: + return internalGetMm(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMutableMapField( + int number) { + switch (number) { + case 51: + return internalGetMutableM(); + case 52: + return internalGetMutableMm(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.class, com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getKkkFieldBuilder(); + getLlFieldBuilder(); + getLllFieldBuilder(); + getLllllFieldBuilder(); + getMmmFieldBuilder(); + getMmmmFieldBuilder(); + getMmmmmFieldBuilder(); + getSsFieldBuilder(); + getSssFieldBuilder(); + getSssssFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + a_ = 0; + + aa_ = 0; + + aaa_ = com.google.protobuf.ByteString.EMPTY; + + aaaa_ = com.google.protobuf.ByteString.EMPTY; + + b_ = 0; + + bb_ = 0; + + bbb_ = com.google.protobuf.ByteString.EMPTY; + + bbbb_ = com.google.protobuf.ByteString.EMPTY; + + c_ = 0; + + cc_ = 0; + + ccc_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000001); + cccc_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000002); + d_ = 0L; + + dd_ = 0L; + + ddd_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000004); + dddd_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000008); + e_ = 0F; + + ee_ = 0F; + + eee_ = emptyFloatList(); + bitField0_ = (bitField0_ & ~0x00000010); + eeee_ = emptyFloatList(); + bitField0_ = (bitField0_ & ~0x00000020); + f_ = 0D; + + ff_ = 0D; + + fff_ = emptyDoubleList(); + bitField0_ = (bitField0_ & ~0x00000040); + ffff_ = emptyDoubleList(); + bitField0_ = (bitField0_ & ~0x00000080); + g_ = false; + + gg_ = false; + + ggg_ = emptyBooleanList(); + bitField0_ = (bitField0_ & ~0x00000100); + gggg_ = emptyBooleanList(); + bitField0_ = (bitField0_ & ~0x00000200); + h_ = ""; + + hh_ = ""; + + hhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000400); + hhhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000800); + jj_ = ""; + + jjj_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00001000); + if (kkBuilder_ == null) { + kk_ = null; + } else { + kk_ = null; + kkBuilder_ = null; + } + if (kkkBuilder_ == null) { + kkk_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00002000); + } else { + kkkBuilder_.clear(); + } + l_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00004000); + if (llBuilder_ == null) { + ll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00008000); + } else { + llBuilder_.clear(); + } + if (lllBuilder_ == null) { + lll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00010000); + } else { + lllBuilder_.clear(); + } + llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00020000); + if (lllllBuilder_ == null) { + lllll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00040000); + } else { + lllllBuilder_.clear(); + } + internalGetMutableM().clear(); + internalGetMutableMm().clear(); + if (mmmBuilder_ == null) { + mmm_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00200000); + } else { + mmmBuilder_.clear(); + } + if (mmmmBuilder_ == null) { + mmmm_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00400000); + } else { + mmmmBuilder_.clear(); + } + if (mmmmmBuilder_ == null) { + mmmmm_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00800000); + } else { + mmmmmBuilder_.clear(); + } + s_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x01000000); + if (ssBuilder_ == null) { + ss_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x02000000); + } else { + ssBuilder_.clear(); + } + if (sssBuilder_ == null) { + sss_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x04000000); + } else { + sssBuilder_.clear(); + } + ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x08000000); + if (sssssBuilder_ == null) { + sssss_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x10000000); + } else { + sssssBuilder_.clear(); + } + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufComplexObject_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject build() { + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject result = new com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject(this); + int from_bitField0_ = bitField0_; + result.a_ = a_; + result.aa_ = aa_; + result.aaa_ = aaa_; + result.aaaa_ = aaaa_; + result.b_ = b_; + result.bb_ = bb_; + result.bbb_ = bbb_; + result.bbbb_ = bbbb_; + result.c_ = c_; + result.cc_ = cc_; + if (((bitField0_ & 0x00000001) != 0)) { + ccc_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.ccc_ = ccc_; + if (((bitField0_ & 0x00000002) != 0)) { + cccc_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.cccc_ = cccc_; + result.d_ = d_; + result.dd_ = dd_; + if (((bitField0_ & 0x00000004) != 0)) { + ddd_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.ddd_ = ddd_; + if (((bitField0_ & 0x00000008) != 0)) { + dddd_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.dddd_ = dddd_; + result.e_ = e_; + result.ee_ = ee_; + if (((bitField0_ & 0x00000010) != 0)) { + eee_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000010); + } + result.eee_ = eee_; + if (((bitField0_ & 0x00000020) != 0)) { + eeee_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000020); + } + result.eeee_ = eeee_; + result.f_ = f_; + result.ff_ = ff_; + if (((bitField0_ & 0x00000040) != 0)) { + fff_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000040); + } + result.fff_ = fff_; + if (((bitField0_ & 0x00000080) != 0)) { + ffff_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000080); + } + result.ffff_ = ffff_; + result.g_ = g_; + result.gg_ = gg_; + if (((bitField0_ & 0x00000100) != 0)) { + ggg_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000100); + } + result.ggg_ = ggg_; + if (((bitField0_ & 0x00000200) != 0)) { + gggg_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000200); + } + result.gggg_ = gggg_; + result.h_ = h_; + result.hh_ = hh_; + if (((bitField0_ & 0x00000400) != 0)) { + hhh_ = hhh_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000400); + } + result.hhh_ = hhh_; + if (((bitField0_ & 0x00000800) != 0)) { + hhhh_ = hhhh_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000800); + } + result.hhhh_ = hhhh_; + result.jj_ = jj_; + if (((bitField0_ & 0x00001000) != 0)) { + jjj_ = jjj_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00001000); + } + result.jjj_ = jjj_; + if (kkBuilder_ == null) { + result.kk_ = kk_; + } else { + result.kk_ = kkBuilder_.build(); + } + if (kkkBuilder_ == null) { + if (((bitField0_ & 0x00002000) != 0)) { + kkk_ = java.util.Collections.unmodifiableList(kkk_); + bitField0_ = (bitField0_ & ~0x00002000); + } + result.kkk_ = kkk_; + } else { + result.kkk_ = kkkBuilder_.build(); + } + if (((bitField0_ & 0x00004000) != 0)) { + l_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00004000); + } + result.l_ = l_; + if (llBuilder_ == null) { + if (((bitField0_ & 0x00008000) != 0)) { + ll_ = java.util.Collections.unmodifiableList(ll_); + bitField0_ = (bitField0_ & ~0x00008000); + } + result.ll_ = ll_; + } else { + result.ll_ = llBuilder_.build(); + } + if (lllBuilder_ == null) { + if (((bitField0_ & 0x00010000) != 0)) { + lll_ = java.util.Collections.unmodifiableList(lll_); + bitField0_ = (bitField0_ & ~0x00010000); + } + result.lll_ = lll_; + } else { + result.lll_ = lllBuilder_.build(); + } + if (((bitField0_ & 0x00020000) != 0)) { + llll_ = llll_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00020000); + } + result.llll_ = llll_; + if (lllllBuilder_ == null) { + if (((bitField0_ & 0x00040000) != 0)) { + lllll_ = java.util.Collections.unmodifiableList(lllll_); + bitField0_ = (bitField0_ & ~0x00040000); + } + result.lllll_ = lllll_; + } else { + result.lllll_ = lllllBuilder_.build(); + } + result.m_ = internalGetM(); + result.m_.makeImmutable(); + result.mm_ = internalGetMm(); + result.mm_.makeImmutable(); + if (mmmBuilder_ == null) { + if (((bitField0_ & 0x00200000) != 0)) { + mmm_ = java.util.Collections.unmodifiableList(mmm_); + bitField0_ = (bitField0_ & ~0x00200000); + } + result.mmm_ = mmm_; + } else { + result.mmm_ = mmmBuilder_.build(); + } + if (mmmmBuilder_ == null) { + if (((bitField0_ & 0x00400000) != 0)) { + mmmm_ = java.util.Collections.unmodifiableList(mmmm_); + bitField0_ = (bitField0_ & ~0x00400000); + } + result.mmmm_ = mmmm_; + } else { + result.mmmm_ = mmmmBuilder_.build(); + } + if (mmmmmBuilder_ == null) { + if (((bitField0_ & 0x00800000) != 0)) { + mmmmm_ = java.util.Collections.unmodifiableList(mmmmm_); + bitField0_ = (bitField0_ & ~0x00800000); + } + result.mmmmm_ = mmmmm_; + } else { + result.mmmmm_ = mmmmmBuilder_.build(); + } + if (((bitField0_ & 0x01000000) != 0)) { + s_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x01000000); + } + result.s_ = s_; + if (ssBuilder_ == null) { + if (((bitField0_ & 0x02000000) != 0)) { + ss_ = java.util.Collections.unmodifiableList(ss_); + bitField0_ = (bitField0_ & ~0x02000000); + } + result.ss_ = ss_; + } else { + result.ss_ = ssBuilder_.build(); + } + if (sssBuilder_ == null) { + if (((bitField0_ & 0x04000000) != 0)) { + sss_ = java.util.Collections.unmodifiableList(sss_); + bitField0_ = (bitField0_ & ~0x04000000); + } + result.sss_ = sss_; + } else { + result.sss_ = sssBuilder_.build(); + } + if (((bitField0_ & 0x08000000) != 0)) { + ssss_ = ssss_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x08000000); + } + result.ssss_ = ssss_; + if (sssssBuilder_ == null) { + if (((bitField0_ & 0x10000000) != 0)) { + sssss_ = java.util.Collections.unmodifiableList(sssss_); + bitField0_ = (bitField0_ & ~0x10000000); + } + result.sssss_ = sssss_; + } else { + result.sssss_ = sssssBuilder_.build(); + } + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject.getDefaultInstance()) return this; + if (other.getA() != 0) { + setA(other.getA()); + } + if (other.getAa() != 0) { + setAa(other.getAa()); + } + if (other.getAaa() != com.google.protobuf.ByteString.EMPTY) { + setAaa(other.getAaa()); + } + if (other.getAaaa() != com.google.protobuf.ByteString.EMPTY) { + setAaaa(other.getAaaa()); + } + if (other.getB() != 0) { + setB(other.getB()); + } + if (other.getBb() != 0) { + setBb(other.getBb()); + } + if (other.getBbb() != com.google.protobuf.ByteString.EMPTY) { + setBbb(other.getBbb()); + } + if (other.getBbbb() != com.google.protobuf.ByteString.EMPTY) { + setBbbb(other.getBbbb()); + } + if (other.getC() != 0) { + setC(other.getC()); + } + if (other.getCc() != 0) { + setCc(other.getCc()); + } + if (!other.ccc_.isEmpty()) { + if (ccc_.isEmpty()) { + ccc_ = other.ccc_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureCccIsMutable(); + ccc_.addAll(other.ccc_); + } + onChanged(); + } + if (!other.cccc_.isEmpty()) { + if (cccc_.isEmpty()) { + cccc_ = other.cccc_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureCcccIsMutable(); + cccc_.addAll(other.cccc_); + } + onChanged(); + } + if (other.getD() != 0L) { + setD(other.getD()); + } + if (other.getDd() != 0L) { + setDd(other.getDd()); + } + if (!other.ddd_.isEmpty()) { + if (ddd_.isEmpty()) { + ddd_ = other.ddd_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureDddIsMutable(); + ddd_.addAll(other.ddd_); + } + onChanged(); + } + if (!other.dddd_.isEmpty()) { + if (dddd_.isEmpty()) { + dddd_ = other.dddd_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureDdddIsMutable(); + dddd_.addAll(other.dddd_); + } + onChanged(); + } + if (other.getE() != 0F) { + setE(other.getE()); + } + if (other.getEe() != 0F) { + setEe(other.getEe()); + } + if (!other.eee_.isEmpty()) { + if (eee_.isEmpty()) { + eee_ = other.eee_; + bitField0_ = (bitField0_ & ~0x00000010); + } else { + ensureEeeIsMutable(); + eee_.addAll(other.eee_); + } + onChanged(); + } + if (!other.eeee_.isEmpty()) { + if (eeee_.isEmpty()) { + eeee_ = other.eeee_; + bitField0_ = (bitField0_ & ~0x00000020); + } else { + ensureEeeeIsMutable(); + eeee_.addAll(other.eeee_); + } + onChanged(); + } + if (other.getF() != 0D) { + setF(other.getF()); + } + if (other.getFf() != 0D) { + setFf(other.getFf()); + } + if (!other.fff_.isEmpty()) { + if (fff_.isEmpty()) { + fff_ = other.fff_; + bitField0_ = (bitField0_ & ~0x00000040); + } else { + ensureFffIsMutable(); + fff_.addAll(other.fff_); + } + onChanged(); + } + if (!other.ffff_.isEmpty()) { + if (ffff_.isEmpty()) { + ffff_ = other.ffff_; + bitField0_ = (bitField0_ & ~0x00000080); + } else { + ensureFfffIsMutable(); + ffff_.addAll(other.ffff_); + } + onChanged(); + } + if (other.getG() != false) { + setG(other.getG()); + } + if (other.getGg() != false) { + setGg(other.getGg()); + } + if (!other.ggg_.isEmpty()) { + if (ggg_.isEmpty()) { + ggg_ = other.ggg_; + bitField0_ = (bitField0_ & ~0x00000100); + } else { + ensureGggIsMutable(); + ggg_.addAll(other.ggg_); + } + onChanged(); + } + if (!other.gggg_.isEmpty()) { + if (gggg_.isEmpty()) { + gggg_ = other.gggg_; + bitField0_ = (bitField0_ & ~0x00000200); + } else { + ensureGgggIsMutable(); + gggg_.addAll(other.gggg_); + } + onChanged(); + } + if (!other.getH().isEmpty()) { + h_ = other.h_; + onChanged(); + } + if (!other.getHh().isEmpty()) { + hh_ = other.hh_; + onChanged(); + } + if (!other.hhh_.isEmpty()) { + if (hhh_.isEmpty()) { + hhh_ = other.hhh_; + bitField0_ = (bitField0_ & ~0x00000400); + } else { + ensureHhhIsMutable(); + hhh_.addAll(other.hhh_); + } + onChanged(); + } + if (!other.hhhh_.isEmpty()) { + if (hhhh_.isEmpty()) { + hhhh_ = other.hhhh_; + bitField0_ = (bitField0_ & ~0x00000800); + } else { + ensureHhhhIsMutable(); + hhhh_.addAll(other.hhhh_); + } + onChanged(); + } + if (!other.getJj().isEmpty()) { + jj_ = other.jj_; + onChanged(); + } + if (!other.jjj_.isEmpty()) { + if (jjj_.isEmpty()) { + jjj_ = other.jjj_; + bitField0_ = (bitField0_ & ~0x00001000); + } else { + ensureJjjIsMutable(); + jjj_.addAll(other.jjj_); + } + onChanged(); + } + if (other.hasKk()) { + mergeKk(other.getKk()); + } + if (kkkBuilder_ == null) { + if (!other.kkk_.isEmpty()) { + if (kkk_.isEmpty()) { + kkk_ = other.kkk_; + bitField0_ = (bitField0_ & ~0x00002000); + } else { + ensureKkkIsMutable(); + kkk_.addAll(other.kkk_); + } + onChanged(); + } + } else { + if (!other.kkk_.isEmpty()) { + if (kkkBuilder_.isEmpty()) { + kkkBuilder_.dispose(); + kkkBuilder_ = null; + kkk_ = other.kkk_; + bitField0_ = (bitField0_ & ~0x00002000); + kkkBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getKkkFieldBuilder() : null; + } else { + kkkBuilder_.addAllMessages(other.kkk_); + } + } + } + if (!other.l_.isEmpty()) { + if (l_.isEmpty()) { + l_ = other.l_; + bitField0_ = (bitField0_ & ~0x00004000); + } else { + ensureLIsMutable(); + l_.addAll(other.l_); + } + onChanged(); + } + if (llBuilder_ == null) { + if (!other.ll_.isEmpty()) { + if (ll_.isEmpty()) { + ll_ = other.ll_; + bitField0_ = (bitField0_ & ~0x00008000); + } else { + ensureLlIsMutable(); + ll_.addAll(other.ll_); + } + onChanged(); + } + } else { + if (!other.ll_.isEmpty()) { + if (llBuilder_.isEmpty()) { + llBuilder_.dispose(); + llBuilder_ = null; + ll_ = other.ll_; + bitField0_ = (bitField0_ & ~0x00008000); + llBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getLlFieldBuilder() : null; + } else { + llBuilder_.addAllMessages(other.ll_); + } + } + } + if (lllBuilder_ == null) { + if (!other.lll_.isEmpty()) { + if (lll_.isEmpty()) { + lll_ = other.lll_; + bitField0_ = (bitField0_ & ~0x00010000); + } else { + ensureLllIsMutable(); + lll_.addAll(other.lll_); + } + onChanged(); + } + } else { + if (!other.lll_.isEmpty()) { + if (lllBuilder_.isEmpty()) { + lllBuilder_.dispose(); + lllBuilder_ = null; + lll_ = other.lll_; + bitField0_ = (bitField0_ & ~0x00010000); + lllBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getLllFieldBuilder() : null; + } else { + lllBuilder_.addAllMessages(other.lll_); + } + } + } + if (!other.llll_.isEmpty()) { + if (llll_.isEmpty()) { + llll_ = other.llll_; + bitField0_ = (bitField0_ & ~0x00020000); + } else { + ensureLlllIsMutable(); + llll_.addAll(other.llll_); + } + onChanged(); + } + if (lllllBuilder_ == null) { + if (!other.lllll_.isEmpty()) { + if (lllll_.isEmpty()) { + lllll_ = other.lllll_; + bitField0_ = (bitField0_ & ~0x00040000); + } else { + ensureLllllIsMutable(); + lllll_.addAll(other.lllll_); + } + onChanged(); + } + } else { + if (!other.lllll_.isEmpty()) { + if (lllllBuilder_.isEmpty()) { + lllllBuilder_.dispose(); + lllllBuilder_ = null; + lllll_ = other.lllll_; + bitField0_ = (bitField0_ & ~0x00040000); + lllllBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getLllllFieldBuilder() : null; + } else { + lllllBuilder_.addAllMessages(other.lllll_); + } + } + } + internalGetMutableM().mergeFrom( + other.internalGetM()); + internalGetMutableMm().mergeFrom( + other.internalGetMm()); + if (mmmBuilder_ == null) { + if (!other.mmm_.isEmpty()) { + if (mmm_.isEmpty()) { + mmm_ = other.mmm_; + bitField0_ = (bitField0_ & ~0x00200000); + } else { + ensureMmmIsMutable(); + mmm_.addAll(other.mmm_); + } + onChanged(); + } + } else { + if (!other.mmm_.isEmpty()) { + if (mmmBuilder_.isEmpty()) { + mmmBuilder_.dispose(); + mmmBuilder_ = null; + mmm_ = other.mmm_; + bitField0_ = (bitField0_ & ~0x00200000); + mmmBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getMmmFieldBuilder() : null; + } else { + mmmBuilder_.addAllMessages(other.mmm_); + } + } + } + if (mmmmBuilder_ == null) { + if (!other.mmmm_.isEmpty()) { + if (mmmm_.isEmpty()) { + mmmm_ = other.mmmm_; + bitField0_ = (bitField0_ & ~0x00400000); + } else { + ensureMmmmIsMutable(); + mmmm_.addAll(other.mmmm_); + } + onChanged(); + } + } else { + if (!other.mmmm_.isEmpty()) { + if (mmmmBuilder_.isEmpty()) { + mmmmBuilder_.dispose(); + mmmmBuilder_ = null; + mmmm_ = other.mmmm_; + bitField0_ = (bitField0_ & ~0x00400000); + mmmmBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getMmmmFieldBuilder() : null; + } else { + mmmmBuilder_.addAllMessages(other.mmmm_); + } + } + } + if (mmmmmBuilder_ == null) { + if (!other.mmmmm_.isEmpty()) { + if (mmmmm_.isEmpty()) { + mmmmm_ = other.mmmmm_; + bitField0_ = (bitField0_ & ~0x00800000); + } else { + ensureMmmmmIsMutable(); + mmmmm_.addAll(other.mmmmm_); + } + onChanged(); + } + } else { + if (!other.mmmmm_.isEmpty()) { + if (mmmmmBuilder_.isEmpty()) { + mmmmmBuilder_.dispose(); + mmmmmBuilder_ = null; + mmmmm_ = other.mmmmm_; + bitField0_ = (bitField0_ & ~0x00800000); + mmmmmBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getMmmmmFieldBuilder() : null; + } else { + mmmmmBuilder_.addAllMessages(other.mmmmm_); + } + } + } + if (!other.s_.isEmpty()) { + if (s_.isEmpty()) { + s_ = other.s_; + bitField0_ = (bitField0_ & ~0x01000000); + } else { + ensureSIsMutable(); + s_.addAll(other.s_); + } + onChanged(); + } + if (ssBuilder_ == null) { + if (!other.ss_.isEmpty()) { + if (ss_.isEmpty()) { + ss_ = other.ss_; + bitField0_ = (bitField0_ & ~0x02000000); + } else { + ensureSsIsMutable(); + ss_.addAll(other.ss_); + } + onChanged(); + } + } else { + if (!other.ss_.isEmpty()) { + if (ssBuilder_.isEmpty()) { + ssBuilder_.dispose(); + ssBuilder_ = null; + ss_ = other.ss_; + bitField0_ = (bitField0_ & ~0x02000000); + ssBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getSsFieldBuilder() : null; + } else { + ssBuilder_.addAllMessages(other.ss_); + } + } + } + if (sssBuilder_ == null) { + if (!other.sss_.isEmpty()) { + if (sss_.isEmpty()) { + sss_ = other.sss_; + bitField0_ = (bitField0_ & ~0x04000000); + } else { + ensureSssIsMutable(); + sss_.addAll(other.sss_); + } + onChanged(); + } + } else { + if (!other.sss_.isEmpty()) { + if (sssBuilder_.isEmpty()) { + sssBuilder_.dispose(); + sssBuilder_ = null; + sss_ = other.sss_; + bitField0_ = (bitField0_ & ~0x04000000); + sssBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getSssFieldBuilder() : null; + } else { + sssBuilder_.addAllMessages(other.sss_); + } + } + } + if (!other.ssss_.isEmpty()) { + if (ssss_.isEmpty()) { + ssss_ = other.ssss_; + bitField0_ = (bitField0_ & ~0x08000000); + } else { + ensureSsssIsMutable(); + ssss_.addAll(other.ssss_); + } + onChanged(); + } + if (sssssBuilder_ == null) { + if (!other.sssss_.isEmpty()) { + if (sssss_.isEmpty()) { + sssss_ = other.sssss_; + bitField0_ = (bitField0_ & ~0x10000000); + } else { + ensureSssssIsMutable(); + sssss_.addAll(other.sssss_); + } + onChanged(); + } + } else { + if (!other.sssss_.isEmpty()) { + if (sssssBuilder_.isEmpty()) { + sssssBuilder_.dispose(); + sssssBuilder_ = null; + sssss_ = other.sssss_; + bitField0_ = (bitField0_ & ~0x10000000); + sssssBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getSssssFieldBuilder() : null; + } else { + sssssBuilder_.addAllMessages(other.sssss_); + } + } + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int a_ ; + /** + *
+       * protobuf不支持单个byte,用int代替,增加了一点性能开销
+       * 
+ * + * int32 a = 1; + */ + public int getA() { + return a_; + } + /** + *
+       * protobuf不支持单个byte,用int代替,增加了一点性能开销
+       * 
+ * + * int32 a = 1; + */ + public Builder setA(int value) { + + a_ = value; + onChanged(); + return this; + } + /** + *
+       * protobuf不支持单个byte,用int代替,增加了一点性能开销
+       * 
+ * + * int32 a = 1; + */ + public Builder clearA() { + + a_ = 0; + onChanged(); + return this; + } + + private int aa_ ; + /** + * int32 aa = 2; + */ + public int getAa() { + return aa_; + } + /** + * int32 aa = 2; + */ + public Builder setAa(int value) { + + aa_ = value; + onChanged(); + return this; + } + /** + * int32 aa = 2; + */ + public Builder clearAa() { + + aa_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString aaa_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes aaa = 3; + */ + public com.google.protobuf.ByteString getAaa() { + return aaa_; + } + /** + * bytes aaa = 3; + */ + public Builder setAaa(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + aaa_ = value; + onChanged(); + return this; + } + /** + * bytes aaa = 3; + */ + public Builder clearAaa() { + + aaa_ = getDefaultInstance().getAaa(); + onChanged(); + return this; + } + + private com.google.protobuf.ByteString aaaa_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes aaaa = 4; + */ + public com.google.protobuf.ByteString getAaaa() { + return aaaa_; + } + /** + * bytes aaaa = 4; + */ + public Builder setAaaa(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + aaaa_ = value; + onChanged(); + return this; + } + /** + * bytes aaaa = 4; + */ + public Builder clearAaaa() { + + aaaa_ = getDefaultInstance().getAaaa(); + onChanged(); + return this; + } + + private int b_ ; + /** + *
+       * protobuf不支持单个short,用int代替,增加了一点性能开销
+       * 
+ * + * int32 b = 5; + */ + public int getB() { + return b_; + } + /** + *
+       * protobuf不支持单个short,用int代替,增加了一点性能开销
+       * 
+ * + * int32 b = 5; + */ + public Builder setB(int value) { + + b_ = value; + onChanged(); + return this; + } + /** + *
+       * protobuf不支持单个short,用int代替,增加了一点性能开销
+       * 
+ * + * int32 b = 5; + */ + public Builder clearB() { + + b_ = 0; + onChanged(); + return this; + } + + private int bb_ ; + /** + * int32 bb = 6; + */ + public int getBb() { + return bb_; + } + /** + * int32 bb = 6; + */ + public Builder setBb(int value) { + + bb_ = value; + onChanged(); + return this; + } + /** + * int32 bb = 6; + */ + public Builder clearBb() { + + bb_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString bbb_ = com.google.protobuf.ByteString.EMPTY; + /** + *
+       * protobuf不支持单个short,用bytes代替,减少了一点性能开销
+       * 
+ * + * bytes bbb = 7; + */ + public com.google.protobuf.ByteString getBbb() { + return bbb_; + } + /** + *
+       * protobuf不支持单个short,用bytes代替,减少了一点性能开销
+       * 
+ * + * bytes bbb = 7; + */ + public Builder setBbb(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + bbb_ = value; + onChanged(); + return this; + } + /** + *
+       * protobuf不支持单个short,用bytes代替,减少了一点性能开销
+       * 
+ * + * bytes bbb = 7; + */ + public Builder clearBbb() { + + bbb_ = getDefaultInstance().getBbb(); + onChanged(); + return this; + } + + private com.google.protobuf.ByteString bbbb_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes bbbb = 8; + */ + public com.google.protobuf.ByteString getBbbb() { + return bbbb_; + } + /** + * bytes bbbb = 8; + */ + public Builder setBbbb(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + bbbb_ = value; + onChanged(); + return this; + } + /** + * bytes bbbb = 8; + */ + public Builder clearBbbb() { + + bbbb_ = getDefaultInstance().getBbbb(); + onChanged(); + return this; + } + + private int c_ ; + /** + * int32 c = 9; + */ + public int getC() { + return c_; + } + /** + * int32 c = 9; + */ + public Builder setC(int value) { + + c_ = value; + onChanged(); + return this; + } + /** + * int32 c = 9; + */ + public Builder clearC() { + + c_ = 0; + onChanged(); + return this; + } + + private int cc_ ; + /** + * int32 cc = 10; + */ + public int getCc() { + return cc_; + } + /** + * int32 cc = 10; + */ + public Builder setCc(int value) { + + cc_ = value; + onChanged(); + return this; + } + /** + * int32 cc = 10; + */ + public Builder clearCc() { + + cc_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.Internal.IntList ccc_ = emptyIntList(); + private void ensureCccIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + ccc_ = mutableCopy(ccc_); + bitField0_ |= 0x00000001; + } + } + /** + * repeated int32 ccc = 11; + */ + public java.util.List + getCccList() { + return ((bitField0_ & 0x00000001) != 0) ? + java.util.Collections.unmodifiableList(ccc_) : ccc_; + } + /** + * repeated int32 ccc = 11; + */ + public int getCccCount() { + return ccc_.size(); + } + /** + * repeated int32 ccc = 11; + */ + public int getCcc(int index) { + return ccc_.getInt(index); + } + /** + * repeated int32 ccc = 11; + */ + public Builder setCcc( + int index, int value) { + ensureCccIsMutable(); + ccc_.setInt(index, value); + onChanged(); + return this; + } + /** + * repeated int32 ccc = 11; + */ + public Builder addCcc(int value) { + ensureCccIsMutable(); + ccc_.addInt(value); + onChanged(); + return this; + } + /** + * repeated int32 ccc = 11; + */ + public Builder addAllCcc( + java.lang.Iterable values) { + ensureCccIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ccc_); + onChanged(); + return this; + } + /** + * repeated int32 ccc = 11; + */ + public Builder clearCcc() { + ccc_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.IntList cccc_ = emptyIntList(); + private void ensureCcccIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + cccc_ = mutableCopy(cccc_); + bitField0_ |= 0x00000002; + } + } + /** + * repeated int32 cccc = 12; + */ + public java.util.List + getCcccList() { + return ((bitField0_ & 0x00000002) != 0) ? + java.util.Collections.unmodifiableList(cccc_) : cccc_; + } + /** + * repeated int32 cccc = 12; + */ + public int getCcccCount() { + return cccc_.size(); + } + /** + * repeated int32 cccc = 12; + */ + public int getCccc(int index) { + return cccc_.getInt(index); + } + /** + * repeated int32 cccc = 12; + */ + public Builder setCccc( + int index, int value) { + ensureCcccIsMutable(); + cccc_.setInt(index, value); + onChanged(); + return this; + } + /** + * repeated int32 cccc = 12; + */ + public Builder addCccc(int value) { + ensureCcccIsMutable(); + cccc_.addInt(value); + onChanged(); + return this; + } + /** + * repeated int32 cccc = 12; + */ + public Builder addAllCccc( + java.lang.Iterable values) { + ensureCcccIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, cccc_); + onChanged(); + return this; + } + /** + * repeated int32 cccc = 12; + */ + public Builder clearCccc() { + cccc_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + + private long d_ ; + /** + * int64 d = 13; + */ + public long getD() { + return d_; + } + /** + * int64 d = 13; + */ + public Builder setD(long value) { + + d_ = value; + onChanged(); + return this; + } + /** + * int64 d = 13; + */ + public Builder clearD() { + + d_ = 0L; + onChanged(); + return this; + } + + private long dd_ ; + /** + * int64 dd = 14; + */ + public long getDd() { + return dd_; + } + /** + * int64 dd = 14; + */ + public Builder setDd(long value) { + + dd_ = value; + onChanged(); + return this; + } + /** + * int64 dd = 14; + */ + public Builder clearDd() { + + dd_ = 0L; + onChanged(); + return this; + } + + private com.google.protobuf.Internal.LongList ddd_ = emptyLongList(); + private void ensureDddIsMutable() { + if (!((bitField0_ & 0x00000004) != 0)) { + ddd_ = mutableCopy(ddd_); + bitField0_ |= 0x00000004; + } + } + /** + * repeated int64 ddd = 15; + */ + public java.util.List + getDddList() { + return ((bitField0_ & 0x00000004) != 0) ? + java.util.Collections.unmodifiableList(ddd_) : ddd_; + } + /** + * repeated int64 ddd = 15; + */ + public int getDddCount() { + return ddd_.size(); + } + /** + * repeated int64 ddd = 15; + */ + public long getDdd(int index) { + return ddd_.getLong(index); + } + /** + * repeated int64 ddd = 15; + */ + public Builder setDdd( + int index, long value) { + ensureDddIsMutable(); + ddd_.setLong(index, value); + onChanged(); + return this; + } + /** + * repeated int64 ddd = 15; + */ + public Builder addDdd(long value) { + ensureDddIsMutable(); + ddd_.addLong(value); + onChanged(); + return this; + } + /** + * repeated int64 ddd = 15; + */ + public Builder addAllDdd( + java.lang.Iterable values) { + ensureDddIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ddd_); + onChanged(); + return this; + } + /** + * repeated int64 ddd = 15; + */ + public Builder clearDdd() { + ddd_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.LongList dddd_ = emptyLongList(); + private void ensureDdddIsMutable() { + if (!((bitField0_ & 0x00000008) != 0)) { + dddd_ = mutableCopy(dddd_); + bitField0_ |= 0x00000008; + } + } + /** + * repeated int64 dddd = 16; + */ + public java.util.List + getDdddList() { + return ((bitField0_ & 0x00000008) != 0) ? + java.util.Collections.unmodifiableList(dddd_) : dddd_; + } + /** + * repeated int64 dddd = 16; + */ + public int getDdddCount() { + return dddd_.size(); + } + /** + * repeated int64 dddd = 16; + */ + public long getDddd(int index) { + return dddd_.getLong(index); + } + /** + * repeated int64 dddd = 16; + */ + public Builder setDddd( + int index, long value) { + ensureDdddIsMutable(); + dddd_.setLong(index, value); + onChanged(); + return this; + } + /** + * repeated int64 dddd = 16; + */ + public Builder addDddd(long value) { + ensureDdddIsMutable(); + dddd_.addLong(value); + onChanged(); + return this; + } + /** + * repeated int64 dddd = 16; + */ + public Builder addAllDddd( + java.lang.Iterable values) { + ensureDdddIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, dddd_); + onChanged(); + return this; + } + /** + * repeated int64 dddd = 16; + */ + public Builder clearDddd() { + dddd_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + + private float e_ ; + /** + * float e = 17; + */ + public float getE() { + return e_; + } + /** + * float e = 17; + */ + public Builder setE(float value) { + + e_ = value; + onChanged(); + return this; + } + /** + * float e = 17; + */ + public Builder clearE() { + + e_ = 0F; + onChanged(); + return this; + } + + private float ee_ ; + /** + * float ee = 18; + */ + public float getEe() { + return ee_; + } + /** + * float ee = 18; + */ + public Builder setEe(float value) { + + ee_ = value; + onChanged(); + return this; + } + /** + * float ee = 18; + */ + public Builder clearEe() { + + ee_ = 0F; + onChanged(); + return this; + } + + private com.google.protobuf.Internal.FloatList eee_ = emptyFloatList(); + private void ensureEeeIsMutable() { + if (!((bitField0_ & 0x00000010) != 0)) { + eee_ = mutableCopy(eee_); + bitField0_ |= 0x00000010; + } + } + /** + * repeated float eee = 19; + */ + public java.util.List + getEeeList() { + return ((bitField0_ & 0x00000010) != 0) ? + java.util.Collections.unmodifiableList(eee_) : eee_; + } + /** + * repeated float eee = 19; + */ + public int getEeeCount() { + return eee_.size(); + } + /** + * repeated float eee = 19; + */ + public float getEee(int index) { + return eee_.getFloat(index); + } + /** + * repeated float eee = 19; + */ + public Builder setEee( + int index, float value) { + ensureEeeIsMutable(); + eee_.setFloat(index, value); + onChanged(); + return this; + } + /** + * repeated float eee = 19; + */ + public Builder addEee(float value) { + ensureEeeIsMutable(); + eee_.addFloat(value); + onChanged(); + return this; + } + /** + * repeated float eee = 19; + */ + public Builder addAllEee( + java.lang.Iterable values) { + ensureEeeIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, eee_); + onChanged(); + return this; + } + /** + * repeated float eee = 19; + */ + public Builder clearEee() { + eee_ = emptyFloatList(); + bitField0_ = (bitField0_ & ~0x00000010); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.FloatList eeee_ = emptyFloatList(); + private void ensureEeeeIsMutable() { + if (!((bitField0_ & 0x00000020) != 0)) { + eeee_ = mutableCopy(eeee_); + bitField0_ |= 0x00000020; + } + } + /** + * repeated float eeee = 20; + */ + public java.util.List + getEeeeList() { + return ((bitField0_ & 0x00000020) != 0) ? + java.util.Collections.unmodifiableList(eeee_) : eeee_; + } + /** + * repeated float eeee = 20; + */ + public int getEeeeCount() { + return eeee_.size(); + } + /** + * repeated float eeee = 20; + */ + public float getEeee(int index) { + return eeee_.getFloat(index); + } + /** + * repeated float eeee = 20; + */ + public Builder setEeee( + int index, float value) { + ensureEeeeIsMutable(); + eeee_.setFloat(index, value); + onChanged(); + return this; + } + /** + * repeated float eeee = 20; + */ + public Builder addEeee(float value) { + ensureEeeeIsMutable(); + eeee_.addFloat(value); + onChanged(); + return this; + } + /** + * repeated float eeee = 20; + */ + public Builder addAllEeee( + java.lang.Iterable values) { + ensureEeeeIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, eeee_); + onChanged(); + return this; + } + /** + * repeated float eeee = 20; + */ + public Builder clearEeee() { + eeee_ = emptyFloatList(); + bitField0_ = (bitField0_ & ~0x00000020); + onChanged(); + return this; + } + + private double f_ ; + /** + * double f = 21; + */ + public double getF() { + return f_; + } + /** + * double f = 21; + */ + public Builder setF(double value) { + + f_ = value; + onChanged(); + return this; + } + /** + * double f = 21; + */ + public Builder clearF() { + + f_ = 0D; + onChanged(); + return this; + } + + private double ff_ ; + /** + * double ff = 22; + */ + public double getFf() { + return ff_; + } + /** + * double ff = 22; + */ + public Builder setFf(double value) { + + ff_ = value; + onChanged(); + return this; + } + /** + * double ff = 22; + */ + public Builder clearFf() { + + ff_ = 0D; + onChanged(); + return this; + } + + private com.google.protobuf.Internal.DoubleList fff_ = emptyDoubleList(); + private void ensureFffIsMutable() { + if (!((bitField0_ & 0x00000040) != 0)) { + fff_ = mutableCopy(fff_); + bitField0_ |= 0x00000040; + } + } + /** + * repeated double fff = 23; + */ + public java.util.List + getFffList() { + return ((bitField0_ & 0x00000040) != 0) ? + java.util.Collections.unmodifiableList(fff_) : fff_; + } + /** + * repeated double fff = 23; + */ + public int getFffCount() { + return fff_.size(); + } + /** + * repeated double fff = 23; + */ + public double getFff(int index) { + return fff_.getDouble(index); + } + /** + * repeated double fff = 23; + */ + public Builder setFff( + int index, double value) { + ensureFffIsMutable(); + fff_.setDouble(index, value); + onChanged(); + return this; + } + /** + * repeated double fff = 23; + */ + public Builder addFff(double value) { + ensureFffIsMutable(); + fff_.addDouble(value); + onChanged(); + return this; + } + /** + * repeated double fff = 23; + */ + public Builder addAllFff( + java.lang.Iterable values) { + ensureFffIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, fff_); + onChanged(); + return this; + } + /** + * repeated double fff = 23; + */ + public Builder clearFff() { + fff_ = emptyDoubleList(); + bitField0_ = (bitField0_ & ~0x00000040); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.DoubleList ffff_ = emptyDoubleList(); + private void ensureFfffIsMutable() { + if (!((bitField0_ & 0x00000080) != 0)) { + ffff_ = mutableCopy(ffff_); + bitField0_ |= 0x00000080; + } + } + /** + * repeated double ffff = 24; + */ + public java.util.List + getFfffList() { + return ((bitField0_ & 0x00000080) != 0) ? + java.util.Collections.unmodifiableList(ffff_) : ffff_; + } + /** + * repeated double ffff = 24; + */ + public int getFfffCount() { + return ffff_.size(); + } + /** + * repeated double ffff = 24; + */ + public double getFfff(int index) { + return ffff_.getDouble(index); + } + /** + * repeated double ffff = 24; + */ + public Builder setFfff( + int index, double value) { + ensureFfffIsMutable(); + ffff_.setDouble(index, value); + onChanged(); + return this; + } + /** + * repeated double ffff = 24; + */ + public Builder addFfff(double value) { + ensureFfffIsMutable(); + ffff_.addDouble(value); + onChanged(); + return this; + } + /** + * repeated double ffff = 24; + */ + public Builder addAllFfff( + java.lang.Iterable values) { + ensureFfffIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ffff_); + onChanged(); + return this; + } + /** + * repeated double ffff = 24; + */ + public Builder clearFfff() { + ffff_ = emptyDoubleList(); + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + return this; + } + + private boolean g_ ; + /** + * bool g = 25; + */ + public boolean getG() { + return g_; + } + /** + * bool g = 25; + */ + public Builder setG(boolean value) { + + g_ = value; + onChanged(); + return this; + } + /** + * bool g = 25; + */ + public Builder clearG() { + + g_ = false; + onChanged(); + return this; + } + + private boolean gg_ ; + /** + * bool gg = 26; + */ + public boolean getGg() { + return gg_; + } + /** + * bool gg = 26; + */ + public Builder setGg(boolean value) { + + gg_ = value; + onChanged(); + return this; + } + /** + * bool gg = 26; + */ + public Builder clearGg() { + + gg_ = false; + onChanged(); + return this; + } + + private com.google.protobuf.Internal.BooleanList ggg_ = emptyBooleanList(); + private void ensureGggIsMutable() { + if (!((bitField0_ & 0x00000100) != 0)) { + ggg_ = mutableCopy(ggg_); + bitField0_ |= 0x00000100; + } + } + /** + * repeated bool ggg = 27; + */ + public java.util.List + getGggList() { + return ((bitField0_ & 0x00000100) != 0) ? + java.util.Collections.unmodifiableList(ggg_) : ggg_; + } + /** + * repeated bool ggg = 27; + */ + public int getGggCount() { + return ggg_.size(); + } + /** + * repeated bool ggg = 27; + */ + public boolean getGgg(int index) { + return ggg_.getBoolean(index); + } + /** + * repeated bool ggg = 27; + */ + public Builder setGgg( + int index, boolean value) { + ensureGggIsMutable(); + ggg_.setBoolean(index, value); + onChanged(); + return this; + } + /** + * repeated bool ggg = 27; + */ + public Builder addGgg(boolean value) { + ensureGggIsMutable(); + ggg_.addBoolean(value); + onChanged(); + return this; + } + /** + * repeated bool ggg = 27; + */ + public Builder addAllGgg( + java.lang.Iterable values) { + ensureGggIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ggg_); + onChanged(); + return this; + } + /** + * repeated bool ggg = 27; + */ + public Builder clearGgg() { + ggg_ = emptyBooleanList(); + bitField0_ = (bitField0_ & ~0x00000100); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.BooleanList gggg_ = emptyBooleanList(); + private void ensureGgggIsMutable() { + if (!((bitField0_ & 0x00000200) != 0)) { + gggg_ = mutableCopy(gggg_); + bitField0_ |= 0x00000200; + } + } + /** + * repeated bool gggg = 28; + */ + public java.util.List + getGgggList() { + return ((bitField0_ & 0x00000200) != 0) ? + java.util.Collections.unmodifiableList(gggg_) : gggg_; + } + /** + * repeated bool gggg = 28; + */ + public int getGgggCount() { + return gggg_.size(); + } + /** + * repeated bool gggg = 28; + */ + public boolean getGggg(int index) { + return gggg_.getBoolean(index); + } + /** + * repeated bool gggg = 28; + */ + public Builder setGggg( + int index, boolean value) { + ensureGgggIsMutable(); + gggg_.setBoolean(index, value); + onChanged(); + return this; + } + /** + * repeated bool gggg = 28; + */ + public Builder addGggg(boolean value) { + ensureGgggIsMutable(); + gggg_.addBoolean(value); + onChanged(); + return this; + } + /** + * repeated bool gggg = 28; + */ + public Builder addAllGggg( + java.lang.Iterable values) { + ensureGgggIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, gggg_); + onChanged(); + return this; + } + /** + * repeated bool gggg = 28; + */ + public Builder clearGggg() { + gggg_ = emptyBooleanList(); + bitField0_ = (bitField0_ & ~0x00000200); + onChanged(); + return this; + } + + private java.lang.Object h_ = ""; + /** + *
+       * protobuf不支持char,用string代替,增加了一点性能开销
+       * 
+ * + * string h = 29; + */ + public java.lang.String getH() { + java.lang.Object ref = h_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + h_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
+       * protobuf不支持char,用string代替,增加了一点性能开销
+       * 
+ * + * string h = 29; + */ + public com.google.protobuf.ByteString + getHBytes() { + java.lang.Object ref = h_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + h_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
+       * protobuf不支持char,用string代替,增加了一点性能开销
+       * 
+ * + * string h = 29; + */ + public Builder setH( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + h_ = value; + onChanged(); + return this; + } + /** + *
+       * protobuf不支持char,用string代替,增加了一点性能开销
+       * 
+ * + * string h = 29; + */ + public Builder clearH() { + + h_ = getDefaultInstance().getH(); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持char,用string代替,增加了一点性能开销
+       * 
+ * + * string h = 29; + */ + public Builder setHBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + h_ = value; + onChanged(); + return this; + } + + private java.lang.Object hh_ = ""; + /** + * string hh = 30; + */ + public java.lang.String getHh() { + java.lang.Object ref = hh_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + hh_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string hh = 30; + */ + public com.google.protobuf.ByteString + getHhBytes() { + java.lang.Object ref = hh_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + hh_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string hh = 30; + */ + public Builder setHh( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + hh_ = value; + onChanged(); + return this; + } + /** + * string hh = 30; + */ + public Builder clearHh() { + + hh_ = getDefaultInstance().getHh(); + onChanged(); + return this; + } + /** + * string hh = 30; + */ + public Builder setHhBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + hh_ = value; + onChanged(); + return this; + } + + private com.google.protobuf.LazyStringList hhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureHhhIsMutable() { + if (!((bitField0_ & 0x00000400) != 0)) { + hhh_ = new com.google.protobuf.LazyStringArrayList(hhh_); + bitField0_ |= 0x00000400; + } + } + /** + * repeated string hhh = 31; + */ + public com.google.protobuf.ProtocolStringList + getHhhList() { + return hhh_.getUnmodifiableView(); + } + /** + * repeated string hhh = 31; + */ + public int getHhhCount() { + return hhh_.size(); + } + /** + * repeated string hhh = 31; + */ + public java.lang.String getHhh(int index) { + return hhh_.get(index); + } + /** + * repeated string hhh = 31; + */ + public com.google.protobuf.ByteString + getHhhBytes(int index) { + return hhh_.getByteString(index); + } + /** + * repeated string hhh = 31; + */ + public Builder setHhh( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureHhhIsMutable(); + hhh_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string hhh = 31; + */ + public Builder addHhh( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureHhhIsMutable(); + hhh_.add(value); + onChanged(); + return this; + } + /** + * repeated string hhh = 31; + */ + public Builder addAllHhh( + java.lang.Iterable values) { + ensureHhhIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, hhh_); + onChanged(); + return this; + } + /** + * repeated string hhh = 31; + */ + public Builder clearHhh() { + hhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000400); + onChanged(); + return this; + } + /** + * repeated string hhh = 31; + */ + public Builder addHhhBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureHhhIsMutable(); + hhh_.add(value); + onChanged(); + return this; + } + + private com.google.protobuf.LazyStringList hhhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureHhhhIsMutable() { + if (!((bitField0_ & 0x00000800) != 0)) { + hhhh_ = new com.google.protobuf.LazyStringArrayList(hhhh_); + bitField0_ |= 0x00000800; + } + } + /** + * repeated string hhhh = 32; + */ + public com.google.protobuf.ProtocolStringList + getHhhhList() { + return hhhh_.getUnmodifiableView(); + } + /** + * repeated string hhhh = 32; + */ + public int getHhhhCount() { + return hhhh_.size(); + } + /** + * repeated string hhhh = 32; + */ + public java.lang.String getHhhh(int index) { + return hhhh_.get(index); + } + /** + * repeated string hhhh = 32; + */ + public com.google.protobuf.ByteString + getHhhhBytes(int index) { + return hhhh_.getByteString(index); + } + /** + * repeated string hhhh = 32; + */ + public Builder setHhhh( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureHhhhIsMutable(); + hhhh_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string hhhh = 32; + */ + public Builder addHhhh( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureHhhhIsMutable(); + hhhh_.add(value); + onChanged(); + return this; + } + /** + * repeated string hhhh = 32; + */ + public Builder addAllHhhh( + java.lang.Iterable values) { + ensureHhhhIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, hhhh_); + onChanged(); + return this; + } + /** + * repeated string hhhh = 32; + */ + public Builder clearHhhh() { + hhhh_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000800); + onChanged(); + return this; + } + /** + * repeated string hhhh = 32; + */ + public Builder addHhhhBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureHhhhIsMutable(); + hhhh_.add(value); + onChanged(); + return this; + } + + private java.lang.Object jj_ = ""; + /** + * string jj = 33; + */ + public java.lang.String getJj() { + java.lang.Object ref = jj_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + jj_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string jj = 33; + */ + public com.google.protobuf.ByteString + getJjBytes() { + java.lang.Object ref = jj_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + jj_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string jj = 33; + */ + public Builder setJj( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + jj_ = value; + onChanged(); + return this; + } + /** + * string jj = 33; + */ + public Builder clearJj() { + + jj_ = getDefaultInstance().getJj(); + onChanged(); + return this; + } + /** + * string jj = 33; + */ + public Builder setJjBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + jj_ = value; + onChanged(); + return this; + } + + private com.google.protobuf.LazyStringList jjj_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureJjjIsMutable() { + if (!((bitField0_ & 0x00001000) != 0)) { + jjj_ = new com.google.protobuf.LazyStringArrayList(jjj_); + bitField0_ |= 0x00001000; + } + } + /** + * repeated string jjj = 34; + */ + public com.google.protobuf.ProtocolStringList + getJjjList() { + return jjj_.getUnmodifiableView(); + } + /** + * repeated string jjj = 34; + */ + public int getJjjCount() { + return jjj_.size(); + } + /** + * repeated string jjj = 34; + */ + public java.lang.String getJjj(int index) { + return jjj_.get(index); + } + /** + * repeated string jjj = 34; + */ + public com.google.protobuf.ByteString + getJjjBytes(int index) { + return jjj_.getByteString(index); + } + /** + * repeated string jjj = 34; + */ + public Builder setJjj( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureJjjIsMutable(); + jjj_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string jjj = 34; + */ + public Builder addJjj( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureJjjIsMutable(); + jjj_.add(value); + onChanged(); + return this; + } + /** + * repeated string jjj = 34; + */ + public Builder addAllJjj( + java.lang.Iterable values) { + ensureJjjIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, jjj_); + onChanged(); + return this; + } + /** + * repeated string jjj = 34; + */ + public Builder clearJjj() { + jjj_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00001000); + onChanged(); + return this; + } + /** + * repeated string jjj = 34; + */ + public Builder addJjjBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureJjjIsMutable(); + jjj_.add(value); + onChanged(); + return this; + } + + private com.zfoo.protocol.packet.ProtobufObject.ObjectA kk_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> kkBuilder_; + /** + * .ObjectA kk = 35; + */ + public boolean hasKk() { + return kkBuilder_ != null || kk_ != null; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKk() { + if (kkBuilder_ == null) { + return kk_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : kk_; + } else { + return kkBuilder_.getMessage(); + } + } + /** + * .ObjectA kk = 35; + */ + public Builder setKk(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + kk_ = value; + onChanged(); + } else { + kkBuilder_.setMessage(value); + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public Builder setKk( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (kkBuilder_ == null) { + kk_ = builderForValue.build(); + onChanged(); + } else { + kkBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public Builder mergeKk(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkBuilder_ == null) { + if (kk_ != null) { + kk_ = + com.zfoo.protocol.packet.ProtobufObject.ObjectA.newBuilder(kk_).mergeFrom(value).buildPartial(); + } else { + kk_ = value; + } + onChanged(); + } else { + kkBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public Builder clearKk() { + if (kkBuilder_ == null) { + kk_ = null; + onChanged(); + } else { + kk_ = null; + kkBuilder_ = null; + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder getKkBuilder() { + + onChanged(); + return getKkFieldBuilder().getBuilder(); + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkOrBuilder() { + if (kkBuilder_ != null) { + return kkBuilder_.getMessageOrBuilder(); + } else { + return kk_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : kk_; + } + } + /** + * .ObjectA kk = 35; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> + getKkFieldBuilder() { + if (kkBuilder_ == null) { + kkBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder>( + getKk(), + getParentForChildren(), + isClean()); + kk_ = null; + } + return kkBuilder_; + } + + private java.util.List kkk_ = + java.util.Collections.emptyList(); + private void ensureKkkIsMutable() { + if (!((bitField0_ & 0x00002000) != 0)) { + kkk_ = new java.util.ArrayList(kkk_); + bitField0_ |= 0x00002000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> kkkBuilder_; + + /** + * repeated .ObjectA kkk = 36; + */ + public java.util.List getKkkList() { + if (kkkBuilder_ == null) { + return java.util.Collections.unmodifiableList(kkk_); + } else { + return kkkBuilder_.getMessageList(); + } + } + /** + * repeated .ObjectA kkk = 36; + */ + public int getKkkCount() { + if (kkkBuilder_ == null) { + return kkk_.size(); + } else { + return kkkBuilder_.getCount(); + } + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKkk(int index) { + if (kkkBuilder_ == null) { + return kkk_.get(index); + } else { + return kkkBuilder_.getMessage(index); + } + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder setKkk( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkkBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureKkkIsMutable(); + kkk_.set(index, value); + onChanged(); + } else { + kkkBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder setKkk( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (kkkBuilder_ == null) { + ensureKkkIsMutable(); + kkk_.set(index, builderForValue.build()); + onChanged(); + } else { + kkkBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder addKkk(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkkBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureKkkIsMutable(); + kkk_.add(value); + onChanged(); + } else { + kkkBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder addKkk( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkkBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureKkkIsMutable(); + kkk_.add(index, value); + onChanged(); + } else { + kkkBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder addKkk( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (kkkBuilder_ == null) { + ensureKkkIsMutable(); + kkk_.add(builderForValue.build()); + onChanged(); + } else { + kkkBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder addKkk( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (kkkBuilder_ == null) { + ensureKkkIsMutable(); + kkk_.add(index, builderForValue.build()); + onChanged(); + } else { + kkkBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder addAllKkk( + java.lang.Iterable values) { + if (kkkBuilder_ == null) { + ensureKkkIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, kkk_); + onChanged(); + } else { + kkkBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder clearKkk() { + if (kkkBuilder_ == null) { + kkk_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00002000); + onChanged(); + } else { + kkkBuilder_.clear(); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public Builder removeKkk(int index) { + if (kkkBuilder_ == null) { + ensureKkkIsMutable(); + kkk_.remove(index); + onChanged(); + } else { + kkkBuilder_.remove(index); + } + return this; + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder getKkkBuilder( + int index) { + return getKkkFieldBuilder().getBuilder(index); + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkkOrBuilder( + int index) { + if (kkkBuilder_ == null) { + return kkk_.get(index); } else { + return kkkBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ObjectA kkk = 36; + */ + public java.util.List + getKkkOrBuilderList() { + if (kkkBuilder_ != null) { + return kkkBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(kkk_); + } + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder addKkkBuilder() { + return getKkkFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + /** + * repeated .ObjectA kkk = 36; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder addKkkBuilder( + int index) { + return getKkkFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + /** + * repeated .ObjectA kkk = 36; + */ + public java.util.List + getKkkBuilderList() { + return getKkkFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> + getKkkFieldBuilder() { + if (kkkBuilder_ == null) { + kkkBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder>( + kkk_, + ((bitField0_ & 0x00002000) != 0), + getParentForChildren(), + isClean()); + kkk_ = null; + } + return kkkBuilder_; + } + + private com.google.protobuf.Internal.IntList l_ = emptyIntList(); + private void ensureLIsMutable() { + if (!((bitField0_ & 0x00004000) != 0)) { + l_ = mutableCopy(l_); + bitField0_ |= 0x00004000; + } + } + /** + * repeated int32 l = 37; + */ + public java.util.List + getLList() { + return ((bitField0_ & 0x00004000) != 0) ? + java.util.Collections.unmodifiableList(l_) : l_; + } + /** + * repeated int32 l = 37; + */ + public int getLCount() { + return l_.size(); + } + /** + * repeated int32 l = 37; + */ + public int getL(int index) { + return l_.getInt(index); + } + /** + * repeated int32 l = 37; + */ + public Builder setL( + int index, int value) { + ensureLIsMutable(); + l_.setInt(index, value); + onChanged(); + return this; + } + /** + * repeated int32 l = 37; + */ + public Builder addL(int value) { + ensureLIsMutable(); + l_.addInt(value); + onChanged(); + return this; + } + /** + * repeated int32 l = 37; + */ + public Builder addAllL( + java.lang.Iterable values) { + ensureLIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, l_); + onChanged(); + return this; + } + /** + * repeated int32 l = 37; + */ + public Builder clearL() { + l_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00004000); + onChanged(); + return this; + } + + private java.util.List ll_ = + java.util.Collections.emptyList(); + private void ensureLlIsMutable() { + if (!((bitField0_ & 0x00008000) != 0)) { + ll_ = new java.util.ArrayList(ll_); + bitField0_ |= 0x00008000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder> llBuilder_; + + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public java.util.List getLlList() { + if (llBuilder_ == null) { + return java.util.Collections.unmodifiableList(ll_); + } else { + return llBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public int getLlCount() { + if (llBuilder_ == null) { + return ll_.size(); + } else { + return llBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getLl(int index) { + if (llBuilder_ == null) { + return ll_.get(index); + } else { + return llBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder setLl( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (llBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlIsMutable(); + ll_.set(index, value); + onChanged(); + } else { + llBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder setLl( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (llBuilder_ == null) { + ensureLlIsMutable(); + ll_.set(index, builderForValue.build()); + onChanged(); + } else { + llBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder addLl(com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (llBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlIsMutable(); + ll_.add(value); + onChanged(); + } else { + llBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder addLl( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (llBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlIsMutable(); + ll_.add(index, value); + onChanged(); + } else { + llBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder addLl( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (llBuilder_ == null) { + ensureLlIsMutable(); + ll_.add(builderForValue.build()); + onChanged(); + } else { + llBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder addLl( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (llBuilder_ == null) { + ensureLlIsMutable(); + ll_.add(index, builderForValue.build()); + onChanged(); + } else { + llBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder addAllLl( + java.lang.Iterable values) { + if (llBuilder_ == null) { + ensureLlIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ll_); + onChanged(); + } else { + llBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder clearLl() { + if (llBuilder_ == null) { + ll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00008000); + onChanged(); + } else { + llBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public Builder removeLl(int index) { + if (llBuilder_ == null) { + ensureLlIsMutable(); + ll_.remove(index); + onChanged(); + } else { + llBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder getLlBuilder( + int index) { + return getLlFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getLlOrBuilder( + int index) { + if (llBuilder_ == null) { + return ll_.get(index); } else { + return llBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public java.util.List + getLlOrBuilderList() { + if (llBuilder_ != null) { + return llBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(ll_); + } + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder addLlBuilder() { + return getLlFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()); + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder addLlBuilder( + int index) { + return getLlFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()); + } + /** + *
+       * protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销
+       * 
+ * + * repeated .ListListInteger ll = 38; + */ + public java.util.List + getLlBuilderList() { + return getLlFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder> + getLlFieldBuilder() { + if (llBuilder_ == null) { + llBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder>( + ll_, + ((bitField0_ & 0x00008000) != 0), + getParentForChildren(), + isClean()); + ll_ = null; + } + return llBuilder_; + } + + private java.util.List lll_ = + java.util.Collections.emptyList(); + private void ensureLllIsMutable() { + if (!((bitField0_ & 0x00010000) != 0)) { + lll_ = new java.util.ArrayList(lll_); + bitField0_ |= 0x00010000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder> lllBuilder_; + + /** + * repeated .ListObjectA lll = 39; + */ + public java.util.List getLllList() { + if (lllBuilder_ == null) { + return java.util.Collections.unmodifiableList(lll_); + } else { + return lllBuilder_.getMessageList(); + } + } + /** + * repeated .ListObjectA lll = 39; + */ + public int getLllCount() { + if (lllBuilder_ == null) { + return lll_.size(); + } else { + return lllBuilder_.getCount(); + } + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getLll(int index) { + if (lllBuilder_ == null) { + return lll_.get(index); + } else { + return lllBuilder_.getMessage(index); + } + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder setLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (lllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllIsMutable(); + lll_.set(index, value); + onChanged(); + } else { + lllBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder setLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.set(index, builderForValue.build()); + onChanged(); + } else { + lllBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder addLll(com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (lllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllIsMutable(); + lll_.add(value); + onChanged(); + } else { + lllBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder addLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (lllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllIsMutable(); + lll_.add(index, value); + onChanged(); + } else { + lllBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder addLll( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.add(builderForValue.build()); + onChanged(); + } else { + lllBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder addLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.add(index, builderForValue.build()); + onChanged(); + } else { + lllBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder addAllLll( + java.lang.Iterable values) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, lll_); + onChanged(); + } else { + lllBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder clearLll() { + if (lllBuilder_ == null) { + lll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00010000); + onChanged(); + } else { + lllBuilder_.clear(); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public Builder removeLll(int index) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.remove(index); + onChanged(); + } else { + lllBuilder_.remove(index); + } + return this; + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder getLllBuilder( + int index) { + return getLllFieldBuilder().getBuilder(index); + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getLllOrBuilder( + int index) { + if (lllBuilder_ == null) { + return lll_.get(index); } else { + return lllBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ListObjectA lll = 39; + */ + public java.util.List + getLllOrBuilderList() { + if (lllBuilder_ != null) { + return lllBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(lll_); + } + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder addLllBuilder() { + return getLllFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()); + } + /** + * repeated .ListObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder addLllBuilder( + int index) { + return getLllFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()); + } + /** + * repeated .ListObjectA lll = 39; + */ + public java.util.List + getLllBuilderList() { + return getLllFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder> + getLllFieldBuilder() { + if (lllBuilder_ == null) { + lllBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder>( + lll_, + ((bitField0_ & 0x00010000) != 0), + getParentForChildren(), + isClean()); + lll_ = null; + } + return lllBuilder_; + } + + private com.google.protobuf.LazyStringList llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureLlllIsMutable() { + if (!((bitField0_ & 0x00020000) != 0)) { + llll_ = new com.google.protobuf.LazyStringArrayList(llll_); + bitField0_ |= 0x00020000; + } + } + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ProtocolStringList + getLlllList() { + return llll_.getUnmodifiableView(); + } + /** + * repeated string llll = 40; + */ + public int getLlllCount() { + return llll_.size(); + } + /** + * repeated string llll = 40; + */ + public java.lang.String getLlll(int index) { + return llll_.get(index); + } + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ByteString + getLlllBytes(int index) { + return llll_.getByteString(index); + } + /** + * repeated string llll = 40; + */ + public Builder setLlll( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlllIsMutable(); + llll_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder addLlll( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlllIsMutable(); + llll_.add(value); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder addAllLlll( + java.lang.Iterable values) { + ensureLlllIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, llll_); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder clearLlll() { + llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00020000); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder addLlllBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureLlllIsMutable(); + llll_.add(value); + onChanged(); + return this; + } + + private java.util.List lllll_ = + java.util.Collections.emptyList(); + private void ensureLllllIsMutable() { + if (!((bitField0_ & 0x00040000) != 0)) { + lllll_ = new java.util.ArrayList(lllll_); + bitField0_ |= 0x00040000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder> lllllBuilder_; + + /** + * repeated .MapIntegerString lllll = 41; + */ + public java.util.List getLllllList() { + if (lllllBuilder_ == null) { + return java.util.Collections.unmodifiableList(lllll_); + } else { + return lllllBuilder_.getMessageList(); + } + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public int getLllllCount() { + if (lllllBuilder_ == null) { + return lllll_.size(); + } else { + return lllllBuilder_.getCount(); + } + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getLllll(int index) { + if (lllllBuilder_ == null) { + return lllll_.get(index); + } else { + return lllllBuilder_.getMessage(index); + } + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder setLllll( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (lllllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllllIsMutable(); + lllll_.set(index, value); + onChanged(); + } else { + lllllBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder setLllll( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (lllllBuilder_ == null) { + ensureLllllIsMutable(); + lllll_.set(index, builderForValue.build()); + onChanged(); + } else { + lllllBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder addLllll(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (lllllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllllIsMutable(); + lllll_.add(value); + onChanged(); + } else { + lllllBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder addLllll( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (lllllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllllIsMutable(); + lllll_.add(index, value); + onChanged(); + } else { + lllllBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder addLllll( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (lllllBuilder_ == null) { + ensureLllllIsMutable(); + lllll_.add(builderForValue.build()); + onChanged(); + } else { + lllllBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder addLllll( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (lllllBuilder_ == null) { + ensureLllllIsMutable(); + lllll_.add(index, builderForValue.build()); + onChanged(); + } else { + lllllBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder addAllLllll( + java.lang.Iterable values) { + if (lllllBuilder_ == null) { + ensureLllllIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, lllll_); + onChanged(); + } else { + lllllBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder clearLllll() { + if (lllllBuilder_ == null) { + lllll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00040000); + onChanged(); + } else { + lllllBuilder_.clear(); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public Builder removeLllll(int index) { + if (lllllBuilder_ == null) { + ensureLllllIsMutable(); + lllll_.remove(index); + onChanged(); + } else { + lllllBuilder_.remove(index); + } + return this; + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder getLllllBuilder( + int index) { + return getLllllFieldBuilder().getBuilder(index); + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getLllllOrBuilder( + int index) { + if (lllllBuilder_ == null) { + return lllll_.get(index); } else { + return lllllBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public java.util.List + getLllllOrBuilderList() { + if (lllllBuilder_ != null) { + return lllllBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(lllll_); + } + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder addLllllBuilder() { + return getLllllFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()); + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder addLllllBuilder( + int index) { + return getLllllFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()); + } + /** + * repeated .MapIntegerString lllll = 41; + */ + public java.util.List + getLllllBuilderList() { + return getLllllFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder> + getLllllFieldBuilder() { + if (lllllBuilder_ == null) { + lllllBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder>( + lllll_, + ((bitField0_ & 0x00040000) != 0), + getParentForChildren(), + isClean()); + lllll_ = null; + } + return lllllBuilder_; + } + + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> m_; + private com.google.protobuf.MapField + internalGetM() { + if (m_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MDefaultEntryHolder.defaultEntry); + } + return m_; + } + private com.google.protobuf.MapField + internalGetMutableM() { + onChanged();; + if (m_ == null) { + m_ = com.google.protobuf.MapField.newMapField( + MDefaultEntryHolder.defaultEntry); + } + if (!m_.isMutable()) { + m_ = m_.copy(); + } + return m_; + } + + public int getMCount() { + return internalGetM().getMap().size(); + } + /** + * map<int32, string> m = 51; + */ + + public boolean containsM( + int key) { + + return internalGetM().getMap().containsKey(key); + } + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getM() { + return getMMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.util.Map getMMap() { + return internalGetM().getMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetM().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrThrow( + int key) { + + java.util.Map map = + internalGetM().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public Builder clearM() { + internalGetMutableM().getMutableMap() + .clear(); + return this; + } + /** + * map<int32, string> m = 51; + */ + + public Builder removeM( + int key) { + + internalGetMutableM().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableM() { + return internalGetMutableM().getMutableMap(); + } + /** + * map<int32, string> m = 51; + */ + public Builder putM( + int key, + java.lang.String value) { + + if (value == null) { throw new java.lang.NullPointerException(); } + internalGetMutableM().getMutableMap() + .put(key, value); + return this; + } + /** + * map<int32, string> m = 51; + */ + + public Builder putAllM( + java.util.Map values) { + internalGetMutableM().getMutableMap() + .putAll(values); + return this; + } + + private com.google.protobuf.MapField< + java.lang.Integer, com.zfoo.protocol.packet.ProtobufObject.ObjectA> mm_; + private com.google.protobuf.MapField + internalGetMm() { + if (mm_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MmDefaultEntryHolder.defaultEntry); + } + return mm_; + } + private com.google.protobuf.MapField + internalGetMutableMm() { + onChanged();; + if (mm_ == null) { + mm_ = com.google.protobuf.MapField.newMapField( + MmDefaultEntryHolder.defaultEntry); + } + if (!mm_.isMutable()) { + mm_ = mm_.copy(); + } + return mm_; + } + + public int getMmCount() { + return internalGetMm().getMap().size(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public boolean containsMm( + int key) { + + return internalGetMm().getMap().containsKey(key); + } + /** + * Use {@link #getMmMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getMm() { + return getMmMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public java.util.Map getMmMap() { + return internalGetMm().getMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrDefault( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA defaultValue) { + + java.util.Map map = + internalGetMm().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrThrow( + int key) { + + java.util.Map map = + internalGetMm().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public Builder clearMm() { + internalGetMutableMm().getMutableMap() + .clear(); + return this; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public Builder removeMm( + int key) { + + internalGetMutableMm().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableMm() { + return internalGetMutableMm().getMutableMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + public Builder putMm( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + + if (value == null) { throw new java.lang.NullPointerException(); } + internalGetMutableMm().getMutableMap() + .put(key, value); + return this; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public Builder putAllMm( + java.util.Map values) { + internalGetMutableMm().getMutableMap() + .putAll(values); + return this; + } + + private java.util.List mmm_ = + java.util.Collections.emptyList(); + private void ensureMmmIsMutable() { + if (!((bitField0_ & 0x00200000) != 0)) { + mmm_ = new java.util.ArrayList(mmm_); + bitField0_ |= 0x00200000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapObjectA, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder> mmmBuilder_; + + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public java.util.List getMmmList() { + if (mmmBuilder_ == null) { + return java.util.Collections.unmodifiableList(mmm_); + } else { + return mmmBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public int getMmmCount() { + if (mmmBuilder_ == null) { + return mmm_.size(); + } else { + return mmmBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA getMmm(int index) { + if (mmmBuilder_ == null) { + return mmm_.get(index); + } else { + return mmmBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder setMmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapObjectA value) { + if (mmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmIsMutable(); + mmm_.set(index, value); + onChanged(); + } else { + mmmBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder setMmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder builderForValue) { + if (mmmBuilder_ == null) { + ensureMmmIsMutable(); + mmm_.set(index, builderForValue.build()); + onChanged(); + } else { + mmmBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder addMmm(com.zfoo.protocol.packet.ProtobufObject.MapObjectA value) { + if (mmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmIsMutable(); + mmm_.add(value); + onChanged(); + } else { + mmmBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder addMmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapObjectA value) { + if (mmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmIsMutable(); + mmm_.add(index, value); + onChanged(); + } else { + mmmBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder addMmm( + com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder builderForValue) { + if (mmmBuilder_ == null) { + ensureMmmIsMutable(); + mmm_.add(builderForValue.build()); + onChanged(); + } else { + mmmBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder addMmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder builderForValue) { + if (mmmBuilder_ == null) { + ensureMmmIsMutable(); + mmm_.add(index, builderForValue.build()); + onChanged(); + } else { + mmmBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder addAllMmm( + java.lang.Iterable values) { + if (mmmBuilder_ == null) { + ensureMmmIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, mmm_); + onChanged(); + } else { + mmmBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder clearMmm() { + if (mmmBuilder_ == null) { + mmm_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00200000); + onChanged(); + } else { + mmmBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public Builder removeMmm(int index) { + if (mmmBuilder_ == null) { + ensureMmmIsMutable(); + mmm_.remove(index); + onChanged(); + } else { + mmmBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder getMmmBuilder( + int index) { + return getMmmFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder getMmmOrBuilder( + int index) { + if (mmmBuilder_ == null) { + return mmm_.get(index); } else { + return mmmBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public java.util.List + getMmmOrBuilderList() { + if (mmmBuilder_ != null) { + return mmmBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(mmm_); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder addMmmBuilder() { + return getMmmFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.MapObjectA.getDefaultInstance()); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder addMmmBuilder( + int index) { + return getMmmFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.getDefaultInstance()); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapObjectA mmm = 53; + */ + public java.util.List + getMmmBuilderList() { + return getMmmFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapObjectA, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder> + getMmmFieldBuilder() { + if (mmmBuilder_ == null) { + mmmBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapObjectA, com.zfoo.protocol.packet.ProtobufObject.MapObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.MapObjectAOrBuilder>( + mmm_, + ((bitField0_ & 0x00200000) != 0), + getParentForChildren(), + isClean()); + mmm_ = null; + } + return mmmBuilder_; + } + + private java.util.List mmmm_ = + java.util.Collections.emptyList(); + private void ensureMmmmIsMutable() { + if (!((bitField0_ & 0x00400000) != 0)) { + mmmm_ = new java.util.ArrayList(mmmm_); + bitField0_ |= 0x00400000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder> mmmmBuilder_; + + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public java.util.List getMmmmList() { + if (mmmmBuilder_ == null) { + return java.util.Collections.unmodifiableList(mmmm_); + } else { + return mmmmBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public int getMmmmCount() { + if (mmmmBuilder_ == null) { + return mmmm_.size(); + } else { + return mmmmBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA getMmmm(int index) { + if (mmmmBuilder_ == null) { + return mmmm_.get(index); + } else { + return mmmmBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder setMmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA value) { + if (mmmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmmIsMutable(); + mmmm_.set(index, value); + onChanged(); + } else { + mmmmBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder setMmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder builderForValue) { + if (mmmmBuilder_ == null) { + ensureMmmmIsMutable(); + mmmm_.set(index, builderForValue.build()); + onChanged(); + } else { + mmmmBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder addMmmm(com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA value) { + if (mmmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmmIsMutable(); + mmmm_.add(value); + onChanged(); + } else { + mmmmBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder addMmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA value) { + if (mmmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmmIsMutable(); + mmmm_.add(index, value); + onChanged(); + } else { + mmmmBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder addMmmm( + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder builderForValue) { + if (mmmmBuilder_ == null) { + ensureMmmmIsMutable(); + mmmm_.add(builderForValue.build()); + onChanged(); + } else { + mmmmBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder addMmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder builderForValue) { + if (mmmmBuilder_ == null) { + ensureMmmmIsMutable(); + mmmm_.add(index, builderForValue.build()); + onChanged(); + } else { + mmmmBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder addAllMmmm( + java.lang.Iterable values) { + if (mmmmBuilder_ == null) { + ensureMmmmIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, mmmm_); + onChanged(); + } else { + mmmmBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder clearMmmm() { + if (mmmmBuilder_ == null) { + mmmm_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00400000); + onChanged(); + } else { + mmmmBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public Builder removeMmmm(int index) { + if (mmmmBuilder_ == null) { + ensureMmmmIsMutable(); + mmmm_.remove(index); + onChanged(); + } else { + mmmmBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder getMmmmBuilder( + int index) { + return getMmmmFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder getMmmmOrBuilder( + int index) { + if (mmmmBuilder_ == null) { + return mmmm_.get(index); } else { + return mmmmBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public java.util.List + getMmmmOrBuilderList() { + if (mmmmBuilder_ != null) { + return mmmmBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(mmmm_); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder addMmmmBuilder() { + return getMmmmFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.getDefaultInstance()); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder addMmmmBuilder( + int index) { + return getMmmmFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.getDefaultInstance()); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListListObjectA mmmm = 54; + */ + public java.util.List + getMmmmBuilderList() { + return getMmmmFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder> + getMmmmFieldBuilder() { + if (mmmmBuilder_ == null) { + mmmmBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.MapListListObjectAOrBuilder>( + mmmm_, + ((bitField0_ & 0x00400000) != 0), + getParentForChildren(), + isClean()); + mmmm_ = null; + } + return mmmmBuilder_; + } + + private java.util.List mmmmm_ = + java.util.Collections.emptyList(); + private void ensureMmmmmIsMutable() { + if (!((bitField0_ & 0x00800000) != 0)) { + mmmmm_ = new java.util.ArrayList(mmmmm_); + bitField0_ |= 0x00800000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder> mmmmmBuilder_; + + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public java.util.List getMmmmmList() { + if (mmmmmBuilder_ == null) { + return java.util.Collections.unmodifiableList(mmmmm_); + } else { + return mmmmmBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public int getMmmmmCount() { + if (mmmmmBuilder_ == null) { + return mmmmm_.size(); + } else { + return mmmmmBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger getMmmmm(int index) { + if (mmmmmBuilder_ == null) { + return mmmmm_.get(index); + } else { + return mmmmmBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder setMmmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger value) { + if (mmmmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmmmIsMutable(); + mmmmm_.set(index, value); + onChanged(); + } else { + mmmmmBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder setMmmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder builderForValue) { + if (mmmmmBuilder_ == null) { + ensureMmmmmIsMutable(); + mmmmm_.set(index, builderForValue.build()); + onChanged(); + } else { + mmmmmBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder addMmmmm(com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger value) { + if (mmmmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmmmIsMutable(); + mmmmm_.add(value); + onChanged(); + } else { + mmmmmBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder addMmmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger value) { + if (mmmmmBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureMmmmmIsMutable(); + mmmmm_.add(index, value); + onChanged(); + } else { + mmmmmBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder addMmmmm( + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder builderForValue) { + if (mmmmmBuilder_ == null) { + ensureMmmmmIsMutable(); + mmmmm_.add(builderForValue.build()); + onChanged(); + } else { + mmmmmBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder addMmmmm( + int index, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder builderForValue) { + if (mmmmmBuilder_ == null) { + ensureMmmmmIsMutable(); + mmmmm_.add(index, builderForValue.build()); + onChanged(); + } else { + mmmmmBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder addAllMmmmm( + java.lang.Iterable values) { + if (mmmmmBuilder_ == null) { + ensureMmmmmIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, mmmmm_); + onChanged(); + } else { + mmmmmBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder clearMmmmm() { + if (mmmmmBuilder_ == null) { + mmmmm_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00800000); + onChanged(); + } else { + mmmmmBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public Builder removeMmmmm(int index) { + if (mmmmmBuilder_ == null) { + ensureMmmmmIsMutable(); + mmmmm_.remove(index); + onChanged(); + } else { + mmmmmBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder getMmmmmBuilder( + int index) { + return getMmmmmFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder getMmmmmOrBuilder( + int index) { + if (mmmmmBuilder_ == null) { + return mmmmm_.get(index); } else { + return mmmmmBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public java.util.List + getMmmmmOrBuilderList() { + if (mmmmmBuilder_ != null) { + return mmmmmBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(mmmmm_); + } + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder addMmmmmBuilder() { + return getMmmmmFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.getDefaultInstance()); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder addMmmmmBuilder( + int index) { + return getMmmmmFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.getDefaultInstance()); + } + /** + *
+       * protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销
+       * 
+ * + * repeated .MapListMapInteger mmmmm = 55; + */ + public java.util.List + getMmmmmBuilderList() { + return getMmmmmFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder> + getMmmmmFieldBuilder() { + if (mmmmmBuilder_ == null) { + mmmmmBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger, com.zfoo.protocol.packet.ProtobufObject.MapListMapInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.MapListMapIntegerOrBuilder>( + mmmmm_, + ((bitField0_ & 0x00800000) != 0), + getParentForChildren(), + isClean()); + mmmmm_ = null; + } + return mmmmmBuilder_; + } + + private com.google.protobuf.Internal.IntList s_ = emptyIntList(); + private void ensureSIsMutable() { + if (!((bitField0_ & 0x01000000) != 0)) { + s_ = mutableCopy(s_); + bitField0_ |= 0x01000000; + } + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public java.util.List + getSList() { + return ((bitField0_ & 0x01000000) != 0) ? + java.util.Collections.unmodifiableList(s_) : s_; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public int getSCount() { + return s_.size(); + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public int getS(int index) { + return s_.getInt(index); + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public Builder setS( + int index, int value) { + ensureSIsMutable(); + s_.setInt(index, value); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public Builder addS(int value) { + ensureSIsMutable(); + s_.addInt(value); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public Builder addAllS( + java.lang.Iterable values) { + ensureSIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, s_); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated int32 s = 61; + */ + public Builder clearS() { + s_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x01000000); + onChanged(); + return this; + } + + private java.util.List ss_ = + java.util.Collections.emptyList(); + private void ensureSsIsMutable() { + if (!((bitField0_ & 0x02000000) != 0)) { + ss_ = new java.util.ArrayList(ss_); + bitField0_ |= 0x02000000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder> ssBuilder_; + + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public java.util.List getSsList() { + if (ssBuilder_ == null) { + return java.util.Collections.unmodifiableList(ss_); + } else { + return ssBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public int getSsCount() { + if (ssBuilder_ == null) { + return ss_.size(); + } else { + return ssBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger getSs(int index) { + if (ssBuilder_ == null) { + return ss_.get(index); + } else { + return ssBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder setSs( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (ssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsIsMutable(); + ss_.set(index, value); + onChanged(); + } else { + ssBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder setSs( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (ssBuilder_ == null) { + ensureSsIsMutable(); + ss_.set(index, builderForValue.build()); + onChanged(); + } else { + ssBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder addSs(com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (ssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsIsMutable(); + ss_.add(value); + onChanged(); + } else { + ssBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder addSs( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger value) { + if (ssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsIsMutable(); + ss_.add(index, value); + onChanged(); + } else { + ssBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder addSs( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (ssBuilder_ == null) { + ensureSsIsMutable(); + ss_.add(builderForValue.build()); + onChanged(); + } else { + ssBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder addSs( + int index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder builderForValue) { + if (ssBuilder_ == null) { + ensureSsIsMutable(); + ss_.add(index, builderForValue.build()); + onChanged(); + } else { + ssBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder addAllSs( + java.lang.Iterable values) { + if (ssBuilder_ == null) { + ensureSsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ss_); + onChanged(); + } else { + ssBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder clearSs() { + if (ssBuilder_ == null) { + ss_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x02000000); + onChanged(); + } else { + ssBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public Builder removeSs(int index) { + if (ssBuilder_ == null) { + ensureSsIsMutable(); + ss_.remove(index); + onChanged(); + } else { + ssBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder getSsBuilder( + int index) { + return getSsFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder getSsOrBuilder( + int index) { + if (ssBuilder_ == null) { + return ss_.get(index); } else { + return ssBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public java.util.List + getSsOrBuilderList() { + if (ssBuilder_ != null) { + return ssBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(ss_); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder addSsBuilder() { + return getSsFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()); + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder addSsBuilder( + int index) { + return getSsFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.getDefaultInstance()); + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListListInteger ss = 62; + */ + public java.util.List + getSsBuilderList() { + return getSsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder> + getSsFieldBuilder() { + if (ssBuilder_ == null) { + ssBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListListInteger, com.zfoo.protocol.packet.ProtobufObject.ListListInteger.Builder, com.zfoo.protocol.packet.ProtobufObject.ListListIntegerOrBuilder>( + ss_, + ((bitField0_ & 0x02000000) != 0), + getParentForChildren(), + isClean()); + ss_ = null; + } + return ssBuilder_; + } + + private java.util.List sss_ = + java.util.Collections.emptyList(); + private void ensureSssIsMutable() { + if (!((bitField0_ & 0x04000000) != 0)) { + sss_ = new java.util.ArrayList(sss_); + bitField0_ |= 0x04000000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder> sssBuilder_; + + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public java.util.List getSssList() { + if (sssBuilder_ == null) { + return java.util.Collections.unmodifiableList(sss_); + } else { + return sssBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public int getSssCount() { + if (sssBuilder_ == null) { + return sss_.size(); + } else { + return sssBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA getSss(int index) { + if (sssBuilder_ == null) { + return sss_.get(index); + } else { + return sssBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder setSss( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (sssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSssIsMutable(); + sss_.set(index, value); + onChanged(); + } else { + sssBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder setSss( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (sssBuilder_ == null) { + ensureSssIsMutable(); + sss_.set(index, builderForValue.build()); + onChanged(); + } else { + sssBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder addSss(com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (sssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSssIsMutable(); + sss_.add(value); + onChanged(); + } else { + sssBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder addSss( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA value) { + if (sssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSssIsMutable(); + sss_.add(index, value); + onChanged(); + } else { + sssBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder addSss( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (sssBuilder_ == null) { + ensureSssIsMutable(); + sss_.add(builderForValue.build()); + onChanged(); + } else { + sssBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder addSss( + int index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder builderForValue) { + if (sssBuilder_ == null) { + ensureSssIsMutable(); + sss_.add(index, builderForValue.build()); + onChanged(); + } else { + sssBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder addAllSss( + java.lang.Iterable values) { + if (sssBuilder_ == null) { + ensureSssIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, sss_); + onChanged(); + } else { + sssBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder clearSss() { + if (sssBuilder_ == null) { + sss_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x04000000); + onChanged(); + } else { + sssBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public Builder removeSss(int index) { + if (sssBuilder_ == null) { + ensureSssIsMutable(); + sss_.remove(index); + onChanged(); + } else { + sssBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder getSssBuilder( + int index) { + return getSssFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder getSssOrBuilder( + int index) { + if (sssBuilder_ == null) { + return sss_.get(index); } else { + return sssBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public java.util.List + getSssOrBuilderList() { + if (sssBuilder_ != null) { + return sssBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(sss_); + } + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder addSssBuilder() { + return getSssFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()); + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder addSssBuilder( + int index) { + return getSssFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.getDefaultInstance()); + } + /** + *
+       * protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .ListObjectA sss = 63; + */ + public java.util.List + getSssBuilderList() { + return getSssFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder> + getSssFieldBuilder() { + if (sssBuilder_ == null) { + sssBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ListObjectA, com.zfoo.protocol.packet.ProtobufObject.ListObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ListObjectAOrBuilder>( + sss_, + ((bitField0_ & 0x04000000) != 0), + getParentForChildren(), + isClean()); + sss_ = null; + } + return sssBuilder_; + } + + private com.google.protobuf.LazyStringList ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureSsssIsMutable() { + if (!((bitField0_ & 0x08000000) != 0)) { + ssss_ = new com.google.protobuf.LazyStringArrayList(ssss_); + bitField0_ |= 0x08000000; + } + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public com.google.protobuf.ProtocolStringList + getSsssList() { + return ssss_.getUnmodifiableView(); + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public int getSsssCount() { + return ssss_.size(); + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public java.lang.String getSsss(int index) { + return ssss_.get(index); + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public com.google.protobuf.ByteString + getSsssBytes(int index) { + return ssss_.getByteString(index); + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public Builder setSsss( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsssIsMutable(); + ssss_.set(index, value); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public Builder addSsss( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsssIsMutable(); + ssss_.add(value); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public Builder addAllSsss( + java.lang.Iterable values) { + ensureSsssIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ssss_); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public Builder clearSsss() { + ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x08000000); + onChanged(); + return this; + } + /** + *
+       * protobuf不支持set,用数组代替,减少了很多性能开销
+       * 
+ * + * repeated string ssss = 64; + */ + public Builder addSsssBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureSsssIsMutable(); + ssss_.add(value); + onChanged(); + return this; + } + + private java.util.List sssss_ = + java.util.Collections.emptyList(); + private void ensureSssssIsMutable() { + if (!((bitField0_ & 0x10000000) != 0)) { + sssss_ = new java.util.ArrayList(sssss_); + bitField0_ |= 0x10000000; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder> sssssBuilder_; + + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public java.util.List getSssssList() { + if (sssssBuilder_ == null) { + return java.util.Collections.unmodifiableList(sssss_); + } else { + return sssssBuilder_.getMessageList(); + } + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public int getSssssCount() { + if (sssssBuilder_ == null) { + return sssss_.size(); + } else { + return sssssBuilder_.getCount(); + } + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString getSssss(int index) { + if (sssssBuilder_ == null) { + return sssss_.get(index); + } else { + return sssssBuilder_.getMessage(index); + } + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder setSssss( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (sssssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSssssIsMutable(); + sssss_.set(index, value); + onChanged(); + } else { + sssssBuilder_.setMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder setSssss( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (sssssBuilder_ == null) { + ensureSssssIsMutable(); + sssss_.set(index, builderForValue.build()); + onChanged(); + } else { + sssssBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder addSssss(com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (sssssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSssssIsMutable(); + sssss_.add(value); + onChanged(); + } else { + sssssBuilder_.addMessage(value); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder addSssss( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString value) { + if (sssssBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureSssssIsMutable(); + sssss_.add(index, value); + onChanged(); + } else { + sssssBuilder_.addMessage(index, value); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder addSssss( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (sssssBuilder_ == null) { + ensureSssssIsMutable(); + sssss_.add(builderForValue.build()); + onChanged(); + } else { + sssssBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder addSssss( + int index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder builderForValue) { + if (sssssBuilder_ == null) { + ensureSssssIsMutable(); + sssss_.add(index, builderForValue.build()); + onChanged(); + } else { + sssssBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder addAllSssss( + java.lang.Iterable values) { + if (sssssBuilder_ == null) { + ensureSssssIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, sssss_); + onChanged(); + } else { + sssssBuilder_.addAllMessages(values); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder clearSssss() { + if (sssssBuilder_ == null) { + sssss_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x10000000); + onChanged(); + } else { + sssssBuilder_.clear(); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public Builder removeSssss(int index) { + if (sssssBuilder_ == null) { + ensureSssssIsMutable(); + sssss_.remove(index); + onChanged(); + } else { + sssssBuilder_.remove(index); + } + return this; + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder getSssssBuilder( + int index) { + return getSssssFieldBuilder().getBuilder(index); + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder getSssssOrBuilder( + int index) { + if (sssssBuilder_ == null) { + return sssss_.get(index); } else { + return sssssBuilder_.getMessageOrBuilder(index); + } + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public java.util.List + getSssssOrBuilderList() { + if (sssssBuilder_ != null) { + return sssssBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(sssss_); + } + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder addSssssBuilder() { + return getSssssFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()); + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder addSssssBuilder( + int index) { + return getSssssFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.getDefaultInstance()); + } + /** + *
+       * protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销
+       * 
+ * + * repeated .MapIntegerString sssss = 65; + */ + public java.util.List + getSssssBuilderList() { + return getSssssFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder> + getSssssFieldBuilder() { + if (sssssBuilder_ == null) { + sssssBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.MapIntegerString, com.zfoo.protocol.packet.ProtobufObject.MapIntegerString.Builder, com.zfoo.protocol.packet.ProtobufObject.MapIntegerStringOrBuilder>( + sssss_, + ((bitField0_ & 0x10000000) != 0), + getParentForChildren(), + isClean()); + sssss_ = null; + } + return sssssBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ProtobufComplexObject) + } + + // @@protoc_insertion_point(class_scope:ProtobufComplexObject) + private static final com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ProtobufComplexObject parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ProtobufComplexObject(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufComplexObject getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ProtobufNormalObjectOrBuilder extends + // @@protoc_insertion_point(interface_extends:ProtobufNormalObject) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 a = 1; + */ + int getA(); + + /** + * bytes aaa = 3; + */ + com.google.protobuf.ByteString getAaa(); + + /** + * int32 b = 5; + */ + int getB(); + + /** + * int32 c = 9; + */ + int getC(); + + /** + * int64 d = 13; + */ + long getD(); + + /** + * float e = 17; + */ + float getE(); + + /** + * double f = 21; + */ + double getF(); + + /** + * bool g = 25; + */ + boolean getG(); + + /** + * string jj = 33; + */ + java.lang.String getJj(); + /** + * string jj = 33; + */ + com.google.protobuf.ByteString + getJjBytes(); + + /** + * .ObjectA kk = 35; + */ + boolean hasKk(); + /** + * .ObjectA kk = 35; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectA getKk(); + /** + * .ObjectA kk = 35; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkOrBuilder(); + + /** + * repeated int32 l = 37; + */ + java.util.List getLList(); + /** + * repeated int32 l = 37; + */ + int getLCount(); + /** + * repeated int32 l = 37; + */ + int getL(int index); + + /** + * repeated int64 ll = 38; + */ + java.util.List getLlList(); + /** + * repeated int64 ll = 38; + */ + int getLlCount(); + /** + * repeated int64 ll = 38; + */ + long getLl(int index); + + /** + * repeated .ObjectA lll = 39; + */ + java.util.List + getLllList(); + /** + * repeated .ObjectA lll = 39; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectA getLll(int index); + /** + * repeated .ObjectA lll = 39; + */ + int getLllCount(); + /** + * repeated .ObjectA lll = 39; + */ + java.util.List + getLllOrBuilderList(); + /** + * repeated .ObjectA lll = 39; + */ + com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getLllOrBuilder( + int index); + + /** + * repeated string llll = 40; + */ + java.util.List + getLlllList(); + /** + * repeated string llll = 40; + */ + int getLlllCount(); + /** + * repeated string llll = 40; + */ + java.lang.String getLlll(int index); + /** + * repeated string llll = 40; + */ + com.google.protobuf.ByteString + getLlllBytes(int index); + + /** + * map<int32, string> m = 51; + */ + int getMCount(); + /** + * map<int32, string> m = 51; + */ + boolean containsM( + int key); + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getM(); + /** + * map<int32, string> m = 51; + */ + java.util.Map + getMMap(); + /** + * map<int32, string> m = 51; + */ + + java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue); + /** + * map<int32, string> m = 51; + */ + + java.lang.String getMOrThrow( + int key); + + /** + * map<int32, .ObjectA> mm = 52; + */ + int getMmCount(); + /** + * map<int32, .ObjectA> mm = 52; + */ + boolean containsMm( + int key); + /** + * Use {@link #getMmMap()} instead. + */ + @java.lang.Deprecated + java.util.Map + getMm(); + /** + * map<int32, .ObjectA> mm = 52; + */ + java.util.Map + getMmMap(); + /** + * map<int32, .ObjectA> mm = 52; + */ + + com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrDefault( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA defaultValue); + /** + * map<int32, .ObjectA> mm = 52; + */ + + com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrThrow( + int key); + + /** + * repeated int32 s = 61; + */ + java.util.List getSList(); + /** + * repeated int32 s = 61; + */ + int getSCount(); + /** + * repeated int32 s = 61; + */ + int getS(int index); + + /** + * repeated string ssss = 64; + */ + java.util.List + getSsssList(); + /** + * repeated string ssss = 64; + */ + int getSsssCount(); + /** + * repeated string ssss = 64; + */ + java.lang.String getSsss(int index); + /** + * repeated string ssss = 64; + */ + com.google.protobuf.ByteString + getSsssBytes(int index); + } + /** + * Protobuf type {@code ProtobufNormalObject} + */ + public static final class ProtobufNormalObject extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ProtobufNormalObject) + ProtobufNormalObjectOrBuilder { + private static final long serialVersionUID = 0L; + // Use ProtobufNormalObject.newBuilder() to construct. + private ProtobufNormalObject(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ProtobufNormalObject() { + aaa_ = com.google.protobuf.ByteString.EMPTY; + jj_ = ""; + l_ = emptyIntList(); + ll_ = emptyLongList(); + lll_ = java.util.Collections.emptyList(); + llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + s_ = emptyIntList(); + ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ProtobufNormalObject(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ProtobufNormalObject( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + + a_ = input.readInt32(); + break; + } + case 26: { + + aaa_ = input.readBytes(); + break; + } + case 40: { + + b_ = input.readInt32(); + break; + } + case 72: { + + c_ = input.readInt32(); + break; + } + case 104: { + + d_ = input.readInt64(); + break; + } + case 141: { + + e_ = input.readFloat(); + break; + } + case 169: { + + f_ = input.readDouble(); + break; + } + case 200: { + + g_ = input.readBool(); + break; + } + case 266: { + java.lang.String s = input.readStringRequireUtf8(); + + jj_ = s; + break; + } + case 282: { + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder subBuilder = null; + if (kk_ != null) { + subBuilder = kk_.toBuilder(); + } + kk_ = input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectA.parser(), extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(kk_); + kk_ = subBuilder.buildPartial(); + } + + break; + } + case 296: { + if (!((mutable_bitField0_ & 0x00000001) != 0)) { + l_ = newIntList(); + mutable_bitField0_ |= 0x00000001; + } + l_.addInt(input.readInt32()); + break; + } + case 298: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000001) != 0) && input.getBytesUntilLimit() > 0) { + l_ = newIntList(); + mutable_bitField0_ |= 0x00000001; + } + while (input.getBytesUntilLimit() > 0) { + l_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + case 304: { + if (!((mutable_bitField0_ & 0x00000002) != 0)) { + ll_ = newLongList(); + mutable_bitField0_ |= 0x00000002; + } + ll_.addLong(input.readInt64()); + break; + } + case 306: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000002) != 0) && input.getBytesUntilLimit() > 0) { + ll_ = newLongList(); + mutable_bitField0_ |= 0x00000002; + } + while (input.getBytesUntilLimit() > 0) { + ll_.addLong(input.readInt64()); + } + input.popLimit(limit); + break; + } + case 314: { + if (!((mutable_bitField0_ & 0x00000004) != 0)) { + lll_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + lll_.add( + input.readMessage(com.zfoo.protocol.packet.ProtobufObject.ObjectA.parser(), extensionRegistry)); + break; + } + case 322: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00000008) != 0)) { + llll_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000008; + } + llll_.add(s); + break; + } + case 410: { + if (!((mutable_bitField0_ & 0x00000010) != 0)) { + m_ = com.google.protobuf.MapField.newMapField( + MDefaultEntryHolder.defaultEntry); + mutable_bitField0_ |= 0x00000010; + } + com.google.protobuf.MapEntry + m__ = input.readMessage( + MDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + m_.getMutableMap().put( + m__.getKey(), m__.getValue()); + break; + } + case 418: { + if (!((mutable_bitField0_ & 0x00000020) != 0)) { + mm_ = com.google.protobuf.MapField.newMapField( + MmDefaultEntryHolder.defaultEntry); + mutable_bitField0_ |= 0x00000020; + } + com.google.protobuf.MapEntry + mm__ = input.readMessage( + MmDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry); + mm_.getMutableMap().put( + mm__.getKey(), mm__.getValue()); + break; + } + case 488: { + if (!((mutable_bitField0_ & 0x00000040) != 0)) { + s_ = newIntList(); + mutable_bitField0_ |= 0x00000040; + } + s_.addInt(input.readInt32()); + break; + } + case 490: { + int length = input.readRawVarint32(); + int limit = input.pushLimit(length); + if (!((mutable_bitField0_ & 0x00000040) != 0) && input.getBytesUntilLimit() > 0) { + s_ = newIntList(); + mutable_bitField0_ |= 0x00000040; + } + while (input.getBytesUntilLimit() > 0) { + s_.addInt(input.readInt32()); + } + input.popLimit(limit); + break; + } + case 514: { + java.lang.String s = input.readStringRequireUtf8(); + if (!((mutable_bitField0_ & 0x00000080) != 0)) { + ssss_ = new com.google.protobuf.LazyStringArrayList(); + mutable_bitField0_ |= 0x00000080; + } + ssss_.add(s); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) != 0)) { + l_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000002) != 0)) { + ll_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000004) != 0)) { + lll_ = java.util.Collections.unmodifiableList(lll_); + } + if (((mutable_bitField0_ & 0x00000008) != 0)) { + llll_ = llll_.getUnmodifiableView(); + } + if (((mutable_bitField0_ & 0x00000040) != 0)) { + s_.makeImmutable(); // C + } + if (((mutable_bitField0_ & 0x00000080) != 0)) { + ssss_ = ssss_.getUnmodifiableView(); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + @java.lang.Override + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 51: + return internalGetM(); + case 52: + return internalGetMm(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.class, com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.Builder.class); + } + + public static final int A_FIELD_NUMBER = 1; + private int a_; + /** + * int32 a = 1; + */ + public int getA() { + return a_; + } + + public static final int AAA_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString aaa_; + /** + * bytes aaa = 3; + */ + public com.google.protobuf.ByteString getAaa() { + return aaa_; + } + + public static final int B_FIELD_NUMBER = 5; + private int b_; + /** + * int32 b = 5; + */ + public int getB() { + return b_; + } + + public static final int C_FIELD_NUMBER = 9; + private int c_; + /** + * int32 c = 9; + */ + public int getC() { + return c_; + } + + public static final int D_FIELD_NUMBER = 13; + private long d_; + /** + * int64 d = 13; + */ + public long getD() { + return d_; + } + + public static final int E_FIELD_NUMBER = 17; + private float e_; + /** + * float e = 17; + */ + public float getE() { + return e_; + } + + public static final int F_FIELD_NUMBER = 21; + private double f_; + /** + * double f = 21; + */ + public double getF() { + return f_; + } + + public static final int G_FIELD_NUMBER = 25; + private boolean g_; + /** + * bool g = 25; + */ + public boolean getG() { + return g_; + } + + public static final int JJ_FIELD_NUMBER = 33; + private volatile java.lang.Object jj_; + /** + * string jj = 33; + */ + public java.lang.String getJj() { + java.lang.Object ref = jj_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + jj_ = s; + return s; + } + } + /** + * string jj = 33; + */ + public com.google.protobuf.ByteString + getJjBytes() { + java.lang.Object ref = jj_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + jj_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int KK_FIELD_NUMBER = 35; + private com.zfoo.protocol.packet.ProtobufObject.ObjectA kk_; + /** + * .ObjectA kk = 35; + */ + public boolean hasKk() { + return kk_ != null; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKk() { + return kk_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : kk_; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkOrBuilder() { + return getKk(); + } + + public static final int L_FIELD_NUMBER = 37; + private com.google.protobuf.Internal.IntList l_; + /** + * repeated int32 l = 37; + */ + public java.util.List + getLList() { + return l_; + } + /** + * repeated int32 l = 37; + */ + public int getLCount() { + return l_.size(); + } + /** + * repeated int32 l = 37; + */ + public int getL(int index) { + return l_.getInt(index); + } + private int lMemoizedSerializedSize = -1; + + public static final int LL_FIELD_NUMBER = 38; + private com.google.protobuf.Internal.LongList ll_; + /** + * repeated int64 ll = 38; + */ + public java.util.List + getLlList() { + return ll_; + } + /** + * repeated int64 ll = 38; + */ + public int getLlCount() { + return ll_.size(); + } + /** + * repeated int64 ll = 38; + */ + public long getLl(int index) { + return ll_.getLong(index); + } + private int llMemoizedSerializedSize = -1; + + public static final int LLL_FIELD_NUMBER = 39; + private java.util.List lll_; + /** + * repeated .ObjectA lll = 39; + */ + public java.util.List getLllList() { + return lll_; + } + /** + * repeated .ObjectA lll = 39; + */ + public java.util.List + getLllOrBuilderList() { + return lll_; + } + /** + * repeated .ObjectA lll = 39; + */ + public int getLllCount() { + return lll_.size(); + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getLll(int index) { + return lll_.get(index); + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getLllOrBuilder( + int index) { + return lll_.get(index); + } + + public static final int LLLL_FIELD_NUMBER = 40; + private com.google.protobuf.LazyStringList llll_; + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ProtocolStringList + getLlllList() { + return llll_; + } + /** + * repeated string llll = 40; + */ + public int getLlllCount() { + return llll_.size(); + } + /** + * repeated string llll = 40; + */ + public java.lang.String getLlll(int index) { + return llll_.get(index); + } + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ByteString + getLlllBytes(int index) { + return llll_.getByteString(index); + } + + public static final int M_FIELD_NUMBER = 51; + private static final class MDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.Integer, java.lang.String> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_MEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.INT32, + 0, + com.google.protobuf.WireFormat.FieldType.STRING, + ""); + } + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> m_; + private com.google.protobuf.MapField + internalGetM() { + if (m_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MDefaultEntryHolder.defaultEntry); + } + return m_; + } + + public int getMCount() { + return internalGetM().getMap().size(); + } + /** + * map<int32, string> m = 51; + */ + + public boolean containsM( + int key) { + + return internalGetM().getMap().containsKey(key); + } + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getM() { + return getMMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.util.Map getMMap() { + return internalGetM().getMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetM().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrThrow( + int key) { + + java.util.Map map = + internalGetM().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public static final int MM_FIELD_NUMBER = 52; + private static final class MmDefaultEntryHolder { + static final com.google.protobuf.MapEntry< + java.lang.Integer, com.zfoo.protocol.packet.ProtobufObject.ObjectA> defaultEntry = + com.google.protobuf.MapEntry + .newDefaultInstance( + com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_MmEntry_descriptor, + com.google.protobuf.WireFormat.FieldType.INT32, + 0, + com.google.protobuf.WireFormat.FieldType.MESSAGE, + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + private com.google.protobuf.MapField< + java.lang.Integer, com.zfoo.protocol.packet.ProtobufObject.ObjectA> mm_; + private com.google.protobuf.MapField + internalGetMm() { + if (mm_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MmDefaultEntryHolder.defaultEntry); + } + return mm_; + } + + public int getMmCount() { + return internalGetMm().getMap().size(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public boolean containsMm( + int key) { + + return internalGetMm().getMap().containsKey(key); + } + /** + * Use {@link #getMmMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getMm() { + return getMmMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public java.util.Map getMmMap() { + return internalGetMm().getMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrDefault( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA defaultValue) { + + java.util.Map map = + internalGetMm().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrThrow( + int key) { + + java.util.Map map = + internalGetMm().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public static final int S_FIELD_NUMBER = 61; + private com.google.protobuf.Internal.IntList s_; + /** + * repeated int32 s = 61; + */ + public java.util.List + getSList() { + return s_; + } + /** + * repeated int32 s = 61; + */ + public int getSCount() { + return s_.size(); + } + /** + * repeated int32 s = 61; + */ + public int getS(int index) { + return s_.getInt(index); + } + private int sMemoizedSerializedSize = -1; + + public static final int SSSS_FIELD_NUMBER = 64; + private com.google.protobuf.LazyStringList ssss_; + /** + * repeated string ssss = 64; + */ + public com.google.protobuf.ProtocolStringList + getSsssList() { + return ssss_; + } + /** + * repeated string ssss = 64; + */ + public int getSsssCount() { + return ssss_.size(); + } + /** + * repeated string ssss = 64; + */ + public java.lang.String getSsss(int index) { + return ssss_.get(index); + } + /** + * repeated string ssss = 64; + */ + public com.google.protobuf.ByteString + getSsssBytes(int index) { + return ssss_.getByteString(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (a_ != 0) { + output.writeInt32(1, a_); + } + if (!aaa_.isEmpty()) { + output.writeBytes(3, aaa_); + } + if (b_ != 0) { + output.writeInt32(5, b_); + } + if (c_ != 0) { + output.writeInt32(9, c_); + } + if (d_ != 0L) { + output.writeInt64(13, d_); + } + if (e_ != 0F) { + output.writeFloat(17, e_); + } + if (f_ != 0D) { + output.writeDouble(21, f_); + } + if (g_ != false) { + output.writeBool(25, g_); + } + if (!getJjBytes().isEmpty()) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 33, jj_); + } + if (kk_ != null) { + output.writeMessage(35, getKk()); + } + if (getLList().size() > 0) { + output.writeUInt32NoTag(298); + output.writeUInt32NoTag(lMemoizedSerializedSize); + } + for (int i = 0; i < l_.size(); i++) { + output.writeInt32NoTag(l_.getInt(i)); + } + if (getLlList().size() > 0) { + output.writeUInt32NoTag(306); + output.writeUInt32NoTag(llMemoizedSerializedSize); + } + for (int i = 0; i < ll_.size(); i++) { + output.writeInt64NoTag(ll_.getLong(i)); + } + for (int i = 0; i < lll_.size(); i++) { + output.writeMessage(39, lll_.get(i)); + } + for (int i = 0; i < llll_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 40, llll_.getRaw(i)); + } + com.google.protobuf.GeneratedMessageV3 + .serializeIntegerMapTo( + output, + internalGetM(), + MDefaultEntryHolder.defaultEntry, + 51); + com.google.protobuf.GeneratedMessageV3 + .serializeIntegerMapTo( + output, + internalGetMm(), + MmDefaultEntryHolder.defaultEntry, + 52); + if (getSList().size() > 0) { + output.writeUInt32NoTag(490); + output.writeUInt32NoTag(sMemoizedSerializedSize); + } + for (int i = 0; i < s_.size(); i++) { + output.writeInt32NoTag(s_.getInt(i)); + } + for (int i = 0; i < ssss_.size(); i++) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 64, ssss_.getRaw(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (a_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, a_); + } + if (!aaa_.isEmpty()) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, aaa_); + } + if (b_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(5, b_); + } + if (c_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(9, c_); + } + if (d_ != 0L) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(13, d_); + } + if (e_ != 0F) { + size += com.google.protobuf.CodedOutputStream + .computeFloatSize(17, e_); + } + if (f_ != 0D) { + size += com.google.protobuf.CodedOutputStream + .computeDoubleSize(21, f_); + } + if (g_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(25, g_); + } + if (!getJjBytes().isEmpty()) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(33, jj_); + } + if (kk_ != null) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(35, getKk()); + } + { + int dataSize = 0; + for (int i = 0; i < l_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(l_.getInt(i)); + } + size += dataSize; + if (!getLList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + lMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + for (int i = 0; i < ll_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt64SizeNoTag(ll_.getLong(i)); + } + size += dataSize; + if (!getLlList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + llMemoizedSerializedSize = dataSize; + } + for (int i = 0; i < lll_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(39, lll_.get(i)); + } + { + int dataSize = 0; + for (int i = 0; i < llll_.size(); i++) { + dataSize += computeStringSizeNoTag(llll_.getRaw(i)); + } + size += dataSize; + size += 2 * getLlllList().size(); + } + for (java.util.Map.Entry entry + : internalGetM().getMap().entrySet()) { + com.google.protobuf.MapEntry + m__ = MDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(51, m__); + } + for (java.util.Map.Entry entry + : internalGetMm().getMap().entrySet()) { + com.google.protobuf.MapEntry + mm__ = MmDefaultEntryHolder.defaultEntry.newBuilderForType() + .setKey(entry.getKey()) + .setValue(entry.getValue()) + .build(); + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(52, mm__); + } + { + int dataSize = 0; + for (int i = 0; i < s_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(s_.getInt(i)); + } + size += dataSize; + if (!getSList().isEmpty()) { + size += 2; + size += com.google.protobuf.CodedOutputStream + .computeInt32SizeNoTag(dataSize); + } + sMemoizedSerializedSize = dataSize; + } + { + int dataSize = 0; + for (int i = 0; i < ssss_.size(); i++) { + dataSize += computeStringSizeNoTag(ssss_.getRaw(i)); + } + size += dataSize; + size += 2 * getSsssList().size(); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject other = (com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject) obj; + + if (getA() + != other.getA()) return false; + if (!getAaa() + .equals(other.getAaa())) return false; + if (getB() + != other.getB()) return false; + if (getC() + != other.getC()) return false; + if (getD() + != other.getD()) return false; + if (java.lang.Float.floatToIntBits(getE()) + != java.lang.Float.floatToIntBits( + other.getE())) return false; + if (java.lang.Double.doubleToLongBits(getF()) + != java.lang.Double.doubleToLongBits( + other.getF())) return false; + if (getG() + != other.getG()) return false; + if (!getJj() + .equals(other.getJj())) return false; + if (hasKk() != other.hasKk()) return false; + if (hasKk()) { + if (!getKk() + .equals(other.getKk())) return false; + } + if (!getLList() + .equals(other.getLList())) return false; + if (!getLlList() + .equals(other.getLlList())) return false; + if (!getLllList() + .equals(other.getLllList())) return false; + if (!getLlllList() + .equals(other.getLlllList())) return false; + if (!internalGetM().equals( + other.internalGetM())) return false; + if (!internalGetMm().equals( + other.internalGetMm())) return false; + if (!getSList() + .equals(other.getSList())) return false; + if (!getSsssList() + .equals(other.getSsssList())) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + A_FIELD_NUMBER; + hash = (53 * hash) + getA(); + hash = (37 * hash) + AAA_FIELD_NUMBER; + hash = (53 * hash) + getAaa().hashCode(); + hash = (37 * hash) + B_FIELD_NUMBER; + hash = (53 * hash) + getB(); + hash = (37 * hash) + C_FIELD_NUMBER; + hash = (53 * hash) + getC(); + hash = (37 * hash) + D_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getD()); + hash = (37 * hash) + E_FIELD_NUMBER; + hash = (53 * hash) + java.lang.Float.floatToIntBits( + getE()); + hash = (37 * hash) + F_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + java.lang.Double.doubleToLongBits(getF())); + hash = (37 * hash) + G_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getG()); + hash = (37 * hash) + JJ_FIELD_NUMBER; + hash = (53 * hash) + getJj().hashCode(); + if (hasKk()) { + hash = (37 * hash) + KK_FIELD_NUMBER; + hash = (53 * hash) + getKk().hashCode(); + } + if (getLCount() > 0) { + hash = (37 * hash) + L_FIELD_NUMBER; + hash = (53 * hash) + getLList().hashCode(); + } + if (getLlCount() > 0) { + hash = (37 * hash) + LL_FIELD_NUMBER; + hash = (53 * hash) + getLlList().hashCode(); + } + if (getLllCount() > 0) { + hash = (37 * hash) + LLL_FIELD_NUMBER; + hash = (53 * hash) + getLllList().hashCode(); + } + if (getLlllCount() > 0) { + hash = (37 * hash) + LLLL_FIELD_NUMBER; + hash = (53 * hash) + getLlllList().hashCode(); + } + if (!internalGetM().getMap().isEmpty()) { + hash = (37 * hash) + M_FIELD_NUMBER; + hash = (53 * hash) + internalGetM().hashCode(); + } + if (!internalGetMm().getMap().isEmpty()) { + hash = (37 * hash) + MM_FIELD_NUMBER; + hash = (53 * hash) + internalGetMm().hashCode(); + } + if (getSCount() > 0) { + hash = (37 * hash) + S_FIELD_NUMBER; + hash = (53 * hash) + getSList().hashCode(); + } + if (getSsssCount() > 0) { + hash = (37 * hash) + SSSS_FIELD_NUMBER; + hash = (53 * hash) + getSsssList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ProtobufNormalObject} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ProtobufNormalObject) + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObjectOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_descriptor; + } + + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMapField( + int number) { + switch (number) { + case 51: + return internalGetM(); + case 52: + return internalGetMm(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @SuppressWarnings({"rawtypes"}) + protected com.google.protobuf.MapField internalGetMutableMapField( + int number) { + switch (number) { + case 51: + return internalGetMutableM(); + case 52: + return internalGetMutableMm(); + default: + throw new RuntimeException( + "Invalid map field number: " + number); + } + } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.class, com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + getLllFieldBuilder(); + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + a_ = 0; + + aaa_ = com.google.protobuf.ByteString.EMPTY; + + b_ = 0; + + c_ = 0; + + d_ = 0L; + + e_ = 0F; + + f_ = 0D; + + g_ = false; + + jj_ = ""; + + if (kkBuilder_ == null) { + kk_ = null; + } else { + kk_ = null; + kkBuilder_ = null; + } + l_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000001); + ll_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000002); + if (lllBuilder_ == null) { + lll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + } else { + lllBuilder_.clear(); + } + llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + internalGetMutableM().clear(); + internalGetMutableMm().clear(); + s_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000040); + ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000080); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufNormalObject_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject build() { + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject result = new com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject(this); + int from_bitField0_ = bitField0_; + result.a_ = a_; + result.aaa_ = aaa_; + result.b_ = b_; + result.c_ = c_; + result.d_ = d_; + result.e_ = e_; + result.f_ = f_; + result.g_ = g_; + result.jj_ = jj_; + if (kkBuilder_ == null) { + result.kk_ = kk_; + } else { + result.kk_ = kkBuilder_.build(); + } + if (((bitField0_ & 0x00000001) != 0)) { + l_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.l_ = l_; + if (((bitField0_ & 0x00000002) != 0)) { + ll_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.ll_ = ll_; + if (lllBuilder_ == null) { + if (((bitField0_ & 0x00000004) != 0)) { + lll_ = java.util.Collections.unmodifiableList(lll_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.lll_ = lll_; + } else { + result.lll_ = lllBuilder_.build(); + } + if (((bitField0_ & 0x00000008) != 0)) { + llll_ = llll_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.llll_ = llll_; + result.m_ = internalGetM(); + result.m_.makeImmutable(); + result.mm_ = internalGetMm(); + result.mm_.makeImmutable(); + if (((bitField0_ & 0x00000040) != 0)) { + s_.makeImmutable(); + bitField0_ = (bitField0_ & ~0x00000040); + } + result.s_ = s_; + if (((bitField0_ & 0x00000080) != 0)) { + ssss_ = ssss_.getUnmodifiableView(); + bitField0_ = (bitField0_ & ~0x00000080); + } + result.ssss_ = ssss_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject.getDefaultInstance()) return this; + if (other.getA() != 0) { + setA(other.getA()); + } + if (other.getAaa() != com.google.protobuf.ByteString.EMPTY) { + setAaa(other.getAaa()); + } + if (other.getB() != 0) { + setB(other.getB()); + } + if (other.getC() != 0) { + setC(other.getC()); + } + if (other.getD() != 0L) { + setD(other.getD()); + } + if (other.getE() != 0F) { + setE(other.getE()); + } + if (other.getF() != 0D) { + setF(other.getF()); + } + if (other.getG() != false) { + setG(other.getG()); + } + if (!other.getJj().isEmpty()) { + jj_ = other.jj_; + onChanged(); + } + if (other.hasKk()) { + mergeKk(other.getKk()); + } + if (!other.l_.isEmpty()) { + if (l_.isEmpty()) { + l_ = other.l_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureLIsMutable(); + l_.addAll(other.l_); + } + onChanged(); + } + if (!other.ll_.isEmpty()) { + if (ll_.isEmpty()) { + ll_ = other.ll_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureLlIsMutable(); + ll_.addAll(other.ll_); + } + onChanged(); + } + if (lllBuilder_ == null) { + if (!other.lll_.isEmpty()) { + if (lll_.isEmpty()) { + lll_ = other.lll_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureLllIsMutable(); + lll_.addAll(other.lll_); + } + onChanged(); + } + } else { + if (!other.lll_.isEmpty()) { + if (lllBuilder_.isEmpty()) { + lllBuilder_.dispose(); + lllBuilder_ = null; + lll_ = other.lll_; + bitField0_ = (bitField0_ & ~0x00000004); + lllBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getLllFieldBuilder() : null; + } else { + lllBuilder_.addAllMessages(other.lll_); + } + } + } + if (!other.llll_.isEmpty()) { + if (llll_.isEmpty()) { + llll_ = other.llll_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensureLlllIsMutable(); + llll_.addAll(other.llll_); + } + onChanged(); + } + internalGetMutableM().mergeFrom( + other.internalGetM()); + internalGetMutableMm().mergeFrom( + other.internalGetMm()); + if (!other.s_.isEmpty()) { + if (s_.isEmpty()) { + s_ = other.s_; + bitField0_ = (bitField0_ & ~0x00000040); + } else { + ensureSIsMutable(); + s_.addAll(other.s_); + } + onChanged(); + } + if (!other.ssss_.isEmpty()) { + if (ssss_.isEmpty()) { + ssss_ = other.ssss_; + bitField0_ = (bitField0_ & ~0x00000080); + } else { + ensureSsssIsMutable(); + ssss_.addAll(other.ssss_); + } + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int a_ ; + /** + * int32 a = 1; + */ + public int getA() { + return a_; + } + /** + * int32 a = 1; + */ + public Builder setA(int value) { + + a_ = value; + onChanged(); + return this; + } + /** + * int32 a = 1; + */ + public Builder clearA() { + + a_ = 0; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString aaa_ = com.google.protobuf.ByteString.EMPTY; + /** + * bytes aaa = 3; + */ + public com.google.protobuf.ByteString getAaa() { + return aaa_; + } + /** + * bytes aaa = 3; + */ + public Builder setAaa(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + + aaa_ = value; + onChanged(); + return this; + } + /** + * bytes aaa = 3; + */ + public Builder clearAaa() { + + aaa_ = getDefaultInstance().getAaa(); + onChanged(); + return this; + } + + private int b_ ; + /** + * int32 b = 5; + */ + public int getB() { + return b_; + } + /** + * int32 b = 5; + */ + public Builder setB(int value) { + + b_ = value; + onChanged(); + return this; + } + /** + * int32 b = 5; + */ + public Builder clearB() { + + b_ = 0; + onChanged(); + return this; + } + + private int c_ ; + /** + * int32 c = 9; + */ + public int getC() { + return c_; + } + /** + * int32 c = 9; + */ + public Builder setC(int value) { + + c_ = value; + onChanged(); + return this; + } + /** + * int32 c = 9; + */ + public Builder clearC() { + + c_ = 0; + onChanged(); + return this; + } + + private long d_ ; + /** + * int64 d = 13; + */ + public long getD() { + return d_; + } + /** + * int64 d = 13; + */ + public Builder setD(long value) { + + d_ = value; + onChanged(); + return this; + } + /** + * int64 d = 13; + */ + public Builder clearD() { + + d_ = 0L; + onChanged(); + return this; + } + + private float e_ ; + /** + * float e = 17; + */ + public float getE() { + return e_; + } + /** + * float e = 17; + */ + public Builder setE(float value) { + + e_ = value; + onChanged(); + return this; + } + /** + * float e = 17; + */ + public Builder clearE() { + + e_ = 0F; + onChanged(); + return this; + } + + private double f_ ; + /** + * double f = 21; + */ + public double getF() { + return f_; + } + /** + * double f = 21; + */ + public Builder setF(double value) { + + f_ = value; + onChanged(); + return this; + } + /** + * double f = 21; + */ + public Builder clearF() { + + f_ = 0D; + onChanged(); + return this; + } + + private boolean g_ ; + /** + * bool g = 25; + */ + public boolean getG() { + return g_; + } + /** + * bool g = 25; + */ + public Builder setG(boolean value) { + + g_ = value; + onChanged(); + return this; + } + /** + * bool g = 25; + */ + public Builder clearG() { + + g_ = false; + onChanged(); + return this; + } + + private java.lang.Object jj_ = ""; + /** + * string jj = 33; + */ + public java.lang.String getJj() { + java.lang.Object ref = jj_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + jj_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * string jj = 33; + */ + public com.google.protobuf.ByteString + getJjBytes() { + java.lang.Object ref = jj_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + jj_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * string jj = 33; + */ + public Builder setJj( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + + jj_ = value; + onChanged(); + return this; + } + /** + * string jj = 33; + */ + public Builder clearJj() { + + jj_ = getDefaultInstance().getJj(); + onChanged(); + return this; + } + /** + * string jj = 33; + */ + public Builder setJjBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + + jj_ = value; + onChanged(); + return this; + } + + private com.zfoo.protocol.packet.ProtobufObject.ObjectA kk_; + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> kkBuilder_; + /** + * .ObjectA kk = 35; + */ + public boolean hasKk() { + return kkBuilder_ != null || kk_ != null; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getKk() { + if (kkBuilder_ == null) { + return kk_ == null ? com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : kk_; + } else { + return kkBuilder_.getMessage(); + } + } + /** + * .ObjectA kk = 35; + */ + public Builder setKk(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + kk_ = value; + onChanged(); + } else { + kkBuilder_.setMessage(value); + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public Builder setKk( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (kkBuilder_ == null) { + kk_ = builderForValue.build(); + onChanged(); + } else { + kkBuilder_.setMessage(builderForValue.build()); + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public Builder mergeKk(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (kkBuilder_ == null) { + if (kk_ != null) { + kk_ = + com.zfoo.protocol.packet.ProtobufObject.ObjectA.newBuilder(kk_).mergeFrom(value).buildPartial(); + } else { + kk_ = value; + } + onChanged(); + } else { + kkBuilder_.mergeFrom(value); + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public Builder clearKk() { + if (kkBuilder_ == null) { + kk_ = null; + onChanged(); + } else { + kk_ = null; + kkBuilder_ = null; + } + + return this; + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder getKkBuilder() { + + onChanged(); + return getKkFieldBuilder().getBuilder(); + } + /** + * .ObjectA kk = 35; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getKkOrBuilder() { + if (kkBuilder_ != null) { + return kkBuilder_.getMessageOrBuilder(); + } else { + return kk_ == null ? + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance() : kk_; + } + } + /** + * .ObjectA kk = 35; + */ + private com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> + getKkFieldBuilder() { + if (kkBuilder_ == null) { + kkBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder>( + getKk(), + getParentForChildren(), + isClean()); + kk_ = null; + } + return kkBuilder_; + } + + private com.google.protobuf.Internal.IntList l_ = emptyIntList(); + private void ensureLIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + l_ = mutableCopy(l_); + bitField0_ |= 0x00000001; + } + } + /** + * repeated int32 l = 37; + */ + public java.util.List + getLList() { + return ((bitField0_ & 0x00000001) != 0) ? + java.util.Collections.unmodifiableList(l_) : l_; + } + /** + * repeated int32 l = 37; + */ + public int getLCount() { + return l_.size(); + } + /** + * repeated int32 l = 37; + */ + public int getL(int index) { + return l_.getInt(index); + } + /** + * repeated int32 l = 37; + */ + public Builder setL( + int index, int value) { + ensureLIsMutable(); + l_.setInt(index, value); + onChanged(); + return this; + } + /** + * repeated int32 l = 37; + */ + public Builder addL(int value) { + ensureLIsMutable(); + l_.addInt(value); + onChanged(); + return this; + } + /** + * repeated int32 l = 37; + */ + public Builder addAllL( + java.lang.Iterable values) { + ensureLIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, l_); + onChanged(); + return this; + } + /** + * repeated int32 l = 37; + */ + public Builder clearL() { + l_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + + private com.google.protobuf.Internal.LongList ll_ = emptyLongList(); + private void ensureLlIsMutable() { + if (!((bitField0_ & 0x00000002) != 0)) { + ll_ = mutableCopy(ll_); + bitField0_ |= 0x00000002; + } + } + /** + * repeated int64 ll = 38; + */ + public java.util.List + getLlList() { + return ((bitField0_ & 0x00000002) != 0) ? + java.util.Collections.unmodifiableList(ll_) : ll_; + } + /** + * repeated int64 ll = 38; + */ + public int getLlCount() { + return ll_.size(); + } + /** + * repeated int64 ll = 38; + */ + public long getLl(int index) { + return ll_.getLong(index); + } + /** + * repeated int64 ll = 38; + */ + public Builder setLl( + int index, long value) { + ensureLlIsMutable(); + ll_.setLong(index, value); + onChanged(); + return this; + } + /** + * repeated int64 ll = 38; + */ + public Builder addLl(long value) { + ensureLlIsMutable(); + ll_.addLong(value); + onChanged(); + return this; + } + /** + * repeated int64 ll = 38; + */ + public Builder addAllLl( + java.lang.Iterable values) { + ensureLlIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ll_); + onChanged(); + return this; + } + /** + * repeated int64 ll = 38; + */ + public Builder clearLl() { + ll_ = emptyLongList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + + private java.util.List lll_ = + java.util.Collections.emptyList(); + private void ensureLllIsMutable() { + if (!((bitField0_ & 0x00000004) != 0)) { + lll_ = new java.util.ArrayList(lll_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> lllBuilder_; + + /** + * repeated .ObjectA lll = 39; + */ + public java.util.List getLllList() { + if (lllBuilder_ == null) { + return java.util.Collections.unmodifiableList(lll_); + } else { + return lllBuilder_.getMessageList(); + } + } + /** + * repeated .ObjectA lll = 39; + */ + public int getLllCount() { + if (lllBuilder_ == null) { + return lll_.size(); + } else { + return lllBuilder_.getCount(); + } + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getLll(int index) { + if (lllBuilder_ == null) { + return lll_.get(index); + } else { + return lllBuilder_.getMessage(index); + } + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder setLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (lllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllIsMutable(); + lll_.set(index, value); + onChanged(); + } else { + lllBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder setLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.set(index, builderForValue.build()); + onChanged(); + } else { + lllBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder addLll(com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (lllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllIsMutable(); + lll_.add(value); + onChanged(); + } else { + lllBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder addLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + if (lllBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureLllIsMutable(); + lll_.add(index, value); + onChanged(); + } else { + lllBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder addLll( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.add(builderForValue.build()); + onChanged(); + } else { + lllBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder addLll( + int index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder builderForValue) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.add(index, builderForValue.build()); + onChanged(); + } else { + lllBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder addAllLll( + java.lang.Iterable values) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, lll_); + onChanged(); + } else { + lllBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder clearLll() { + if (lllBuilder_ == null) { + lll_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + lllBuilder_.clear(); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public Builder removeLll(int index) { + if (lllBuilder_ == null) { + ensureLllIsMutable(); + lll_.remove(index); + onChanged(); + } else { + lllBuilder_.remove(index); + } + return this; + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder getLllBuilder( + int index) { + return getLllFieldBuilder().getBuilder(index); + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder getLllOrBuilder( + int index) { + if (lllBuilder_ == null) { + return lll_.get(index); } else { + return lllBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .ObjectA lll = 39; + */ + public java.util.List + getLllOrBuilderList() { + if (lllBuilder_ != null) { + return lllBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(lll_); + } + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder addLllBuilder() { + return getLllFieldBuilder().addBuilder( + com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + /** + * repeated .ObjectA lll = 39; + */ + public com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder addLllBuilder( + int index) { + return getLllFieldBuilder().addBuilder( + index, com.zfoo.protocol.packet.ProtobufObject.ObjectA.getDefaultInstance()); + } + /** + * repeated .ObjectA lll = 39; + */ + public java.util.List + getLllBuilderList() { + return getLllFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder> + getLllFieldBuilder() { + if (lllBuilder_ == null) { + lllBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.zfoo.protocol.packet.ProtobufObject.ObjectA, com.zfoo.protocol.packet.ProtobufObject.ObjectA.Builder, com.zfoo.protocol.packet.ProtobufObject.ObjectAOrBuilder>( + lll_, + ((bitField0_ & 0x00000004) != 0), + getParentForChildren(), + isClean()); + lll_ = null; + } + return lllBuilder_; + } + + private com.google.protobuf.LazyStringList llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureLlllIsMutable() { + if (!((bitField0_ & 0x00000008) != 0)) { + llll_ = new com.google.protobuf.LazyStringArrayList(llll_); + bitField0_ |= 0x00000008; + } + } + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ProtocolStringList + getLlllList() { + return llll_.getUnmodifiableView(); + } + /** + * repeated string llll = 40; + */ + public int getLlllCount() { + return llll_.size(); + } + /** + * repeated string llll = 40; + */ + public java.lang.String getLlll(int index) { + return llll_.get(index); + } + /** + * repeated string llll = 40; + */ + public com.google.protobuf.ByteString + getLlllBytes(int index) { + return llll_.getByteString(index); + } + /** + * repeated string llll = 40; + */ + public Builder setLlll( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlllIsMutable(); + llll_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder addLlll( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureLlllIsMutable(); + llll_.add(value); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder addAllLlll( + java.lang.Iterable values) { + ensureLlllIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, llll_); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder clearLlll() { + llll_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + return this; + } + /** + * repeated string llll = 40; + */ + public Builder addLlllBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureLlllIsMutable(); + llll_.add(value); + onChanged(); + return this; + } + + private com.google.protobuf.MapField< + java.lang.Integer, java.lang.String> m_; + private com.google.protobuf.MapField + internalGetM() { + if (m_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MDefaultEntryHolder.defaultEntry); + } + return m_; + } + private com.google.protobuf.MapField + internalGetMutableM() { + onChanged();; + if (m_ == null) { + m_ = com.google.protobuf.MapField.newMapField( + MDefaultEntryHolder.defaultEntry); + } + if (!m_.isMutable()) { + m_ = m_.copy(); + } + return m_; + } + + public int getMCount() { + return internalGetM().getMap().size(); + } + /** + * map<int32, string> m = 51; + */ + + public boolean containsM( + int key) { + + return internalGetM().getMap().containsKey(key); + } + /** + * Use {@link #getMMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getM() { + return getMMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.util.Map getMMap() { + return internalGetM().getMap(); + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrDefault( + int key, + java.lang.String defaultValue) { + + java.util.Map map = + internalGetM().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, string> m = 51; + */ + + public java.lang.String getMOrThrow( + int key) { + + java.util.Map map = + internalGetM().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public Builder clearM() { + internalGetMutableM().getMutableMap() + .clear(); + return this; + } + /** + * map<int32, string> m = 51; + */ + + public Builder removeM( + int key) { + + internalGetMutableM().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableM() { + return internalGetMutableM().getMutableMap(); + } + /** + * map<int32, string> m = 51; + */ + public Builder putM( + int key, + java.lang.String value) { + + if (value == null) { throw new java.lang.NullPointerException(); } + internalGetMutableM().getMutableMap() + .put(key, value); + return this; + } + /** + * map<int32, string> m = 51; + */ + + public Builder putAllM( + java.util.Map values) { + internalGetMutableM().getMutableMap() + .putAll(values); + return this; + } + + private com.google.protobuf.MapField< + java.lang.Integer, com.zfoo.protocol.packet.ProtobufObject.ObjectA> mm_; + private com.google.protobuf.MapField + internalGetMm() { + if (mm_ == null) { + return com.google.protobuf.MapField.emptyMapField( + MmDefaultEntryHolder.defaultEntry); + } + return mm_; + } + private com.google.protobuf.MapField + internalGetMutableMm() { + onChanged();; + if (mm_ == null) { + mm_ = com.google.protobuf.MapField.newMapField( + MmDefaultEntryHolder.defaultEntry); + } + if (!mm_.isMutable()) { + mm_ = mm_.copy(); + } + return mm_; + } + + public int getMmCount() { + return internalGetMm().getMap().size(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public boolean containsMm( + int key) { + + return internalGetMm().getMap().containsKey(key); + } + /** + * Use {@link #getMmMap()} instead. + */ + @java.lang.Deprecated + public java.util.Map getMm() { + return getMmMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public java.util.Map getMmMap() { + return internalGetMm().getMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrDefault( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA defaultValue) { + + java.util.Map map = + internalGetMm().getMap(); + return map.containsKey(key) ? map.get(key) : defaultValue; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public com.zfoo.protocol.packet.ProtobufObject.ObjectA getMmOrThrow( + int key) { + + java.util.Map map = + internalGetMm().getMap(); + if (!map.containsKey(key)) { + throw new java.lang.IllegalArgumentException(); + } + return map.get(key); + } + + public Builder clearMm() { + internalGetMutableMm().getMutableMap() + .clear(); + return this; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public Builder removeMm( + int key) { + + internalGetMutableMm().getMutableMap() + .remove(key); + return this; + } + /** + * Use alternate mutation accessors instead. + */ + @java.lang.Deprecated + public java.util.Map + getMutableMm() { + return internalGetMutableMm().getMutableMap(); + } + /** + * map<int32, .ObjectA> mm = 52; + */ + public Builder putMm( + int key, + com.zfoo.protocol.packet.ProtobufObject.ObjectA value) { + + if (value == null) { throw new java.lang.NullPointerException(); } + internalGetMutableMm().getMutableMap() + .put(key, value); + return this; + } + /** + * map<int32, .ObjectA> mm = 52; + */ + + public Builder putAllMm( + java.util.Map values) { + internalGetMutableMm().getMutableMap() + .putAll(values); + return this; + } + + private com.google.protobuf.Internal.IntList s_ = emptyIntList(); + private void ensureSIsMutable() { + if (!((bitField0_ & 0x00000040) != 0)) { + s_ = mutableCopy(s_); + bitField0_ |= 0x00000040; + } + } + /** + * repeated int32 s = 61; + */ + public java.util.List + getSList() { + return ((bitField0_ & 0x00000040) != 0) ? + java.util.Collections.unmodifiableList(s_) : s_; + } + /** + * repeated int32 s = 61; + */ + public int getSCount() { + return s_.size(); + } + /** + * repeated int32 s = 61; + */ + public int getS(int index) { + return s_.getInt(index); + } + /** + * repeated int32 s = 61; + */ + public Builder setS( + int index, int value) { + ensureSIsMutable(); + s_.setInt(index, value); + onChanged(); + return this; + } + /** + * repeated int32 s = 61; + */ + public Builder addS(int value) { + ensureSIsMutable(); + s_.addInt(value); + onChanged(); + return this; + } + /** + * repeated int32 s = 61; + */ + public Builder addAllS( + java.lang.Iterable values) { + ensureSIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, s_); + onChanged(); + return this; + } + /** + * repeated int32 s = 61; + */ + public Builder clearS() { + s_ = emptyIntList(); + bitField0_ = (bitField0_ & ~0x00000040); + onChanged(); + return this; + } + + private com.google.protobuf.LazyStringList ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + private void ensureSsssIsMutable() { + if (!((bitField0_ & 0x00000080) != 0)) { + ssss_ = new com.google.protobuf.LazyStringArrayList(ssss_); + bitField0_ |= 0x00000080; + } + } + /** + * repeated string ssss = 64; + */ + public com.google.protobuf.ProtocolStringList + getSsssList() { + return ssss_.getUnmodifiableView(); + } + /** + * repeated string ssss = 64; + */ + public int getSsssCount() { + return ssss_.size(); + } + /** + * repeated string ssss = 64; + */ + public java.lang.String getSsss(int index) { + return ssss_.get(index); + } + /** + * repeated string ssss = 64; + */ + public com.google.protobuf.ByteString + getSsssBytes(int index) { + return ssss_.getByteString(index); + } + /** + * repeated string ssss = 64; + */ + public Builder setSsss( + int index, java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsssIsMutable(); + ssss_.set(index, value); + onChanged(); + return this; + } + /** + * repeated string ssss = 64; + */ + public Builder addSsss( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + ensureSsssIsMutable(); + ssss_.add(value); + onChanged(); + return this; + } + /** + * repeated string ssss = 64; + */ + public Builder addAllSsss( + java.lang.Iterable values) { + ensureSsssIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, ssss_); + onChanged(); + return this; + } + /** + * repeated string ssss = 64; + */ + public Builder clearSsss() { + ssss_ = com.google.protobuf.LazyStringArrayList.EMPTY; + bitField0_ = (bitField0_ & ~0x00000080); + onChanged(); + return this; + } + /** + * repeated string ssss = 64; + */ + public Builder addSsssBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + checkByteStringIsUtf8(value); + ensureSsssIsMutable(); + ssss_.add(value); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ProtobufNormalObject) + } + + // @@protoc_insertion_point(class_scope:ProtobufNormalObject) + private static final com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ProtobufNormalObject parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ProtobufNormalObject(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufNormalObject getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ProtobufSimpleObjectOrBuilder extends + // @@protoc_insertion_point(interface_extends:ProtobufSimpleObject) + com.google.protobuf.MessageOrBuilder { + + /** + * int32 c = 9; + */ + int getC(); + + /** + * bool g = 25; + */ + boolean getG(); + } + /** + * Protobuf type {@code ProtobufSimpleObject} + */ + public static final class ProtobufSimpleObject extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:ProtobufSimpleObject) + ProtobufSimpleObjectOrBuilder { + private static final long serialVersionUID = 0L; + // Use ProtobufSimpleObject.newBuilder() to construct. + private ProtobufSimpleObject(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ProtobufSimpleObject() { + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new ProtobufSimpleObject(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ProtobufSimpleObject( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 72: { + + c_ = input.readInt32(); + break; + } + case 200: { + + g_ = input.readBool(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufSimpleObject_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufSimpleObject_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.class, com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.Builder.class); + } + + public static final int C_FIELD_NUMBER = 9; + private int c_; + /** + * int32 c = 9; + */ + public int getC() { + return c_; + } + + public static final int G_FIELD_NUMBER = 25; + private boolean g_; + /** + * bool g = 25; + */ + public boolean getG() { + return g_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (c_ != 0) { + output.writeInt32(9, c_); + } + if (g_ != false) { + output.writeBool(25, g_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (c_ != 0) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(9, c_); + } + if (g_ != false) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(25, g_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject)) { + return super.equals(obj); + } + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject other = (com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject) obj; + + if (getC() + != other.getC()) return false; + if (getG() + != other.getG()) return false; + if (!unknownFields.equals(other.unknownFields)) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + hash = (37 * hash) + C_FIELD_NUMBER; + hash = (53 * hash) + getC(); + hash = (37 * hash) + G_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getG()); + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code ProtobufSimpleObject} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:ProtobufSimpleObject) + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObjectOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufSimpleObject_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufSimpleObject_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.class, com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.Builder.class); + } + + // Construct using com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + c_ = 0; + + g_ = false; + + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.zfoo.protocol.packet.ProtobufObject.internal_static_ProtobufSimpleObject_descriptor; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject getDefaultInstanceForType() { + return com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.getDefaultInstance(); + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject build() { + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject buildPartial() { + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject result = new com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject(this); + result.c_ = c_; + result.g_ = g_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject) { + return mergeFrom((com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject other) { + if (other == com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject.getDefaultInstance()) return this; + if (other.getC() != 0) { + setC(other.getC()); + } + if (other.getG() != false) { + setG(other.getG()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + private int c_ ; + /** + * int32 c = 9; + */ + public int getC() { + return c_; + } + /** + * int32 c = 9; + */ + public Builder setC(int value) { + + c_ = value; + onChanged(); + return this; + } + /** + * int32 c = 9; + */ + public Builder clearC() { + + c_ = 0; + onChanged(); + return this; + } + + private boolean g_ ; + /** + * bool g = 25; + */ + public boolean getG() { + return g_; + } + /** + * bool g = 25; + */ + public Builder setG(boolean value) { + + g_ = value; + onChanged(); + return this; + } + /** + * bool g = 25; + */ + public Builder clearG() { + + g_ = false; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:ProtobufSimpleObject) + } + + // @@protoc_insertion_point(class_scope:ProtobufSimpleObject) + private static final com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject(); + } + + public static com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ProtobufSimpleObject parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ProtobufSimpleObject(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.zfoo.protocol.packet.ProtobufObject.ProtobufSimpleObject getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ObjectB_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ObjectB_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ObjectA_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ObjectA_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ObjectA_MEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ObjectA_MEntry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ListInteger_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ListInteger_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ListListInteger_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ListListInteger_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ListListListInteger_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ListListListInteger_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ListObjectA_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ListObjectA_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ListListObjectA_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ListListObjectA_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_MapObjectA_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_MapObjectA_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_MapListListObjectA_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_MapListListObjectA_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_MapIntegerString_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_MapIntegerString_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_MapIntegerString_AEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_MapIntegerString_AEntry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ListMapIntegerString_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ListMapIntegerString_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_MapListMapInteger_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_MapListMapInteger_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufComplexObject_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufComplexObject_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufComplexObject_MEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufComplexObject_MEntry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufComplexObject_MmEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufComplexObject_MmEntry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufNormalObject_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufNormalObject_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufNormalObject_MEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufNormalObject_MEntry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufNormalObject_MmEntry_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufNormalObject_MmEntry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_ProtobufSimpleObject_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_ProtobufSimpleObject_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\013speed.proto\"\027\n\007ObjectB\022\014\n\004flag\030\001 \001(\010\"u" + + "\n\007ObjectA\022\t\n\001a\030\001 \001(\005\022\032\n\001m\030\002 \003(\0132\017.Object" + + "A.MEntry\022\031\n\007objectB\030\003 \001(\0132\010.ObjectB\032(\n\006M" + + "Entry\022\013\n\003key\030\001 \001(\005\022\r\n\005value\030\002 \001(\t:\0028\001\"\030\n" + + "\013ListInteger\022\t\n\001a\030\001 \003(\005\"*\n\017ListListInteg" + + "er\022\027\n\001a\030\001 \003(\0132\014.ListInteger\"2\n\023ListListL" + + "istInteger\022\033\n\001a\030\001 \003(\0132\020.ListListInteger\"" + + "\"\n\013ListObjectA\022\023\n\001a\030\001 \003(\0132\010.ObjectA\"*\n\017L" + + "istListObjectA\022\027\n\001a\030\001 \003(\0132\014.ListObjectA\"" + + "@\n\nMapObjectA\022\025\n\003key\030\001 \001(\0132\010.ObjectA\022\033\n\005" + + "value\030\002 \001(\0132\014.ListInteger\"X\n\022MapListList" + + "ObjectA\022\035\n\003key\030\001 \001(\0132\020.ListListObjectA\022#" + + "\n\005value\030\002 \001(\0132\024.ListListListInteger\"a\n\020M" + + "apIntegerString\022#\n\001a\030\001 \003(\0132\030.MapIntegerS" + + "tring.AEntry\032(\n\006AEntry\022\013\n\003key\030\001 \001(\005\022\r\n\005v" + + "alue\030\002 \001(\t:\0028\001\"4\n\024ListMapIntegerString\022\034" + + "\n\001a\030\001 \003(\0132\021.MapIntegerString\"]\n\021MapListM" + + "apInteger\022\"\n\003key\030\001 \001(\0132\025.ListMapIntegerS" + + "tring\022$\n\005value\030\002 \001(\0132\025.ListMapIntegerStr" + + "ing\"\352\007\n\025ProtobufComplexObject\022\t\n\001a\030\001 \001(\005" + + "\022\n\n\002aa\030\002 \001(\005\022\013\n\003aaa\030\003 \001(\014\022\014\n\004aaaa\030\004 \001(\014\022" + + "\t\n\001b\030\005 \001(\005\022\n\n\002bb\030\006 \001(\005\022\013\n\003bbb\030\007 \001(\014\022\014\n\004b" + + "bbb\030\010 \001(\014\022\t\n\001c\030\t \001(\005\022\n\n\002cc\030\n \001(\005\022\013\n\003ccc\030" + + "\013 \003(\005\022\014\n\004cccc\030\014 \003(\005\022\t\n\001d\030\r \001(\003\022\n\n\002dd\030\016 \001" + + "(\003\022\013\n\003ddd\030\017 \003(\003\022\014\n\004dddd\030\020 \003(\003\022\t\n\001e\030\021 \001(\002" + + "\022\n\n\002ee\030\022 \001(\002\022\013\n\003eee\030\023 \003(\002\022\014\n\004eeee\030\024 \003(\002\022" + + "\t\n\001f\030\025 \001(\001\022\n\n\002ff\030\026 \001(\001\022\013\n\003fff\030\027 \003(\001\022\014\n\004f" + + "fff\030\030 \003(\001\022\t\n\001g\030\031 \001(\010\022\n\n\002gg\030\032 \001(\010\022\013\n\003ggg\030" + + "\033 \003(\010\022\014\n\004gggg\030\034 \003(\010\022\t\n\001h\030\035 \001(\t\022\n\n\002hh\030\036 \001" + + "(\t\022\013\n\003hhh\030\037 \003(\t\022\014\n\004hhhh\030 \003(\t\022\n\n\002jj\030! \001(" + + "\t\022\013\n\003jjj\030\" \003(\t\022\024\n\002kk\030# \001(\0132\010.ObjectA\022\025\n\003" + + "kkk\030$ \003(\0132\010.ObjectA\022\t\n\001l\030% \003(\005\022\034\n\002ll\030& \003" + + "(\0132\020.ListListInteger\022\031\n\003lll\030\' \003(\0132\014.List" + + "ObjectA\022\014\n\004llll\030( \003(\t\022 \n\005lllll\030) \003(\0132\021.M" + + "apIntegerString\022(\n\001m\0303 \003(\0132\035.ProtobufCom" + + "plexObject.MEntry\022*\n\002mm\0304 \003(\0132\036.Protobuf" + + "ComplexObject.MmEntry\022\030\n\003mmm\0305 \003(\0132\013.Map" + + "ObjectA\022!\n\004mmmm\0306 \003(\0132\023.MapListListObjec" + + "tA\022!\n\005mmmmm\0307 \003(\0132\022.MapListMapInteger\022\t\n" + + "\001s\030= \003(\005\022\034\n\002ss\030> \003(\0132\020.ListListInteger\022\031" + + "\n\003sss\030? \003(\0132\014.ListObjectA\022\014\n\004ssss\030@ \003(\t\022" + + " \n\005sssss\030A \003(\0132\021.MapIntegerString\032(\n\006MEn" + + "try\022\013\n\003key\030\001 \001(\005\022\r\n\005value\030\002 \001(\t:\0028\001\0323\n\007M" + + "mEntry\022\013\n\003key\030\001 \001(\005\022\027\n\005value\030\002 \001(\0132\010.Obj" + + "ectA:\0028\001\"\232\003\n\024ProtobufNormalObject\022\t\n\001a\030\001" + + " \001(\005\022\013\n\003aaa\030\003 \001(\014\022\t\n\001b\030\005 \001(\005\022\t\n\001c\030\t \001(\005\022" + + "\t\n\001d\030\r \001(\003\022\t\n\001e\030\021 \001(\002\022\t\n\001f\030\025 \001(\001\022\t\n\001g\030\031 " + + "\001(\010\022\n\n\002jj\030! \001(\t\022\024\n\002kk\030# \001(\0132\010.ObjectA\022\t\n" + + "\001l\030% \003(\005\022\n\n\002ll\030& \003(\003\022\025\n\003lll\030\' \003(\0132\010.Obje" + + "ctA\022\014\n\004llll\030( \003(\t\022\'\n\001m\0303 \003(\0132\034.ProtobufN" + + "ormalObject.MEntry\022)\n\002mm\0304 \003(\0132\035.Protobu" + + "fNormalObject.MmEntry\022\t\n\001s\030= \003(\005\022\014\n\004ssss" + + "\030@ \003(\t\032(\n\006MEntry\022\013\n\003key\030\001 \001(\005\022\r\n\005value\030\002" + + " \001(\t:\0028\001\0323\n\007MmEntry\022\013\n\003key\030\001 \001(\005\022\027\n\005valu" + + "e\030\002 \001(\0132\010.ObjectA:\0028\001\",\n\024ProtobufSimpleO" + + "bject\022\t\n\001c\030\t \001(\005\022\t\n\001g\030\031 \001(\010B*\n\030com.zfoo." + + "protocol.packetB\016ProtobufObjectb\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_ObjectB_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_ObjectB_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ObjectB_descriptor, + new java.lang.String[] { "Flag", }); + internal_static_ObjectA_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_ObjectA_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ObjectA_descriptor, + new java.lang.String[] { "A", "M", "ObjectB", }); + internal_static_ObjectA_MEntry_descriptor = + internal_static_ObjectA_descriptor.getNestedTypes().get(0); + internal_static_ObjectA_MEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ObjectA_MEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ListInteger_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_ListInteger_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ListInteger_descriptor, + new java.lang.String[] { "A", }); + internal_static_ListListInteger_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_ListListInteger_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ListListInteger_descriptor, + new java.lang.String[] { "A", }); + internal_static_ListListListInteger_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_ListListListInteger_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ListListListInteger_descriptor, + new java.lang.String[] { "A", }); + internal_static_ListObjectA_descriptor = + getDescriptor().getMessageTypes().get(5); + internal_static_ListObjectA_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ListObjectA_descriptor, + new java.lang.String[] { "A", }); + internal_static_ListListObjectA_descriptor = + getDescriptor().getMessageTypes().get(6); + internal_static_ListListObjectA_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ListListObjectA_descriptor, + new java.lang.String[] { "A", }); + internal_static_MapObjectA_descriptor = + getDescriptor().getMessageTypes().get(7); + internal_static_MapObjectA_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_MapObjectA_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_MapListListObjectA_descriptor = + getDescriptor().getMessageTypes().get(8); + internal_static_MapListListObjectA_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_MapListListObjectA_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_MapIntegerString_descriptor = + getDescriptor().getMessageTypes().get(9); + internal_static_MapIntegerString_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_MapIntegerString_descriptor, + new java.lang.String[] { "A", }); + internal_static_MapIntegerString_AEntry_descriptor = + internal_static_MapIntegerString_descriptor.getNestedTypes().get(0); + internal_static_MapIntegerString_AEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_MapIntegerString_AEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ListMapIntegerString_descriptor = + getDescriptor().getMessageTypes().get(10); + internal_static_ListMapIntegerString_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ListMapIntegerString_descriptor, + new java.lang.String[] { "A", }); + internal_static_MapListMapInteger_descriptor = + getDescriptor().getMessageTypes().get(11); + internal_static_MapListMapInteger_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_MapListMapInteger_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ProtobufComplexObject_descriptor = + getDescriptor().getMessageTypes().get(12); + internal_static_ProtobufComplexObject_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufComplexObject_descriptor, + new java.lang.String[] { "A", "Aa", "Aaa", "Aaaa", "B", "Bb", "Bbb", "Bbbb", "C", "Cc", "Ccc", "Cccc", "D", "Dd", "Ddd", "Dddd", "E", "Ee", "Eee", "Eeee", "F", "Ff", "Fff", "Ffff", "G", "Gg", "Ggg", "Gggg", "H", "Hh", "Hhh", "Hhhh", "Jj", "Jjj", "Kk", "Kkk", "L", "Ll", "Lll", "Llll", "Lllll", "M", "Mm", "Mmm", "Mmmm", "Mmmmm", "S", "Ss", "Sss", "Ssss", "Sssss", }); + internal_static_ProtobufComplexObject_MEntry_descriptor = + internal_static_ProtobufComplexObject_descriptor.getNestedTypes().get(0); + internal_static_ProtobufComplexObject_MEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufComplexObject_MEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ProtobufComplexObject_MmEntry_descriptor = + internal_static_ProtobufComplexObject_descriptor.getNestedTypes().get(1); + internal_static_ProtobufComplexObject_MmEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufComplexObject_MmEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ProtobufNormalObject_descriptor = + getDescriptor().getMessageTypes().get(13); + internal_static_ProtobufNormalObject_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufNormalObject_descriptor, + new java.lang.String[] { "A", "Aaa", "B", "C", "D", "E", "F", "G", "Jj", "Kk", "L", "Ll", "Lll", "Llll", "M", "Mm", "S", "Ssss", }); + internal_static_ProtobufNormalObject_MEntry_descriptor = + internal_static_ProtobufNormalObject_descriptor.getNestedTypes().get(0); + internal_static_ProtobufNormalObject_MEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufNormalObject_MEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ProtobufNormalObject_MmEntry_descriptor = + internal_static_ProtobufNormalObject_descriptor.getNestedTypes().get(1); + internal_static_ProtobufNormalObject_MmEntry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufNormalObject_MmEntry_descriptor, + new java.lang.String[] { "Key", "Value", }); + internal_static_ProtobufSimpleObject_descriptor = + getDescriptor().getMessageTypes().get(14); + internal_static_ProtobufSimpleObject_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_ProtobufSimpleObject_descriptor, + new java.lang.String[] { "C", "G", }); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/protocol/src/test/java/com/zfoo/protocol/packet/SimpleObject.java b/protocol/src/test/java/com/zfoo/protocol/packet/SimpleObject.java new file mode 100644 index 00000000..f3c0fe38 --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/packet/SimpleObject.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.packet; + +import com.zfoo.protocol.IPacket; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SimpleObject implements IPacket { + + public static final transient short PROTOCOL_ID = 1163; + + private int c; + + private boolean g; + + @Override + public short protocolId() { + return PROTOCOL_ID; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public boolean isG() { + return g; + } + + public void setG(boolean g) { + this.g = g; + } +} diff --git a/protocol/src/test/java/com/zfoo/protocol/util/ByteBufUtilsTest.java b/protocol/src/test/java/com/zfoo/protocol/util/ByteBufUtilsTest.java new file mode 100644 index 00000000..3b2218fa --- /dev/null +++ b/protocol/src/test/java/com/zfoo/protocol/util/ByteBufUtilsTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.protocol.util; + +import com.zfoo.protocol.buffer.ByteBufUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.buffer.UnpooledHeapByteBuf; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ByteBufUtilsTest { + + @Test + public void byteTest() { + byte[] values = new byte[]{Byte.MIN_VALUE, -100, -2, -1, 0, 1, 2, 100, Byte.MAX_VALUE}; + ByteBuf byteBuf = Unpooled.buffer(); + for (byte value : values) { + ByteBufUtils.writeByte(byteBuf, value); + byte result = ByteBufUtils.readByte(byteBuf); + Assert.assertEquals(result, value); + } + for (byte value : values) { + ByteBufUtils.writeByteBox(byteBuf, value); + byte result = ByteBufUtils.readByteBox(byteBuf); + Assert.assertEquals(result, value); + } + } + + @Test + public void shortTest() { + short[] values = new short[]{Short.MIN_VALUE, -100, -2, -1, 0, 1, 2, 100, Short.MAX_VALUE}; + ByteBuf byteBuf = Unpooled.buffer(); + for (short value : values) { + ByteBufUtils.writeShort(byteBuf, value); + short result = ByteBufUtils.readShort(byteBuf); + Assert.assertEquals(result, value); + } + + for (short value : values) { + ByteBufUtils.writeShortBox(byteBuf, value); + short result = ByteBufUtils.readShortBox(byteBuf); + Assert.assertEquals(result, value); + } + } + + @Test + public void intTest() { + int[] values = new int[]{Integer.MIN_VALUE, -99999999, -9999, -100, -2, -1, 0, 1, 2, 100, 9999, 99999999, Integer.MAX_VALUE}; + ByteBuf byteBuf = Unpooled.buffer(); + for (int value : values) { + ByteBufUtils.writeInt(byteBuf, value); + int result = ByteBufUtils.readInt(byteBuf); + Assert.assertEquals(result, value); + } + + for (int value : values) { + ByteBufUtils.writeIntBox(byteBuf, value); + int result = ByteBufUtils.readIntBox(byteBuf); + Assert.assertEquals(result, value); + } + } + + @Test + public void longTest() { + long[] values = new long[]{Long.MIN_VALUE, -9999999999999999L, -9999999999999999L, -99999999, -9999, -100, -2, -1 + , 0, 1, 2, 100, 9999, 99999999, 9999999999999999L, Long.MAX_VALUE}; + ByteBuf byteBuf = Unpooled.buffer(); + for (long value : values) { + ByteBufUtils.writeLong(byteBuf, value); + long result = ByteBufUtils.readLong(byteBuf); + Assert.assertEquals(result, value); + } + + for (long value : values) { + ByteBufUtils.writeLongBox(byteBuf, value); + long result = ByteBufUtils.readLongBox(byteBuf); + Assert.assertEquals(result, value); + } + } + + @Test + public void stringTest() { + String str = "hello"; + ByteBuf byteBuf = Unpooled.buffer(); + ByteBufUtils.writeString(byteBuf, str); + String result = ByteBufUtils.readString(byteBuf); + Assert.assertEquals(result, str); + } + + @Test + public void charTest() { + char c = 'a'; + ByteBuf byteBuf = Unpooled.buffer(); + ByteBufUtils.writeChar(byteBuf, c); + char result = ByteBufUtils.readChar(byteBuf); + Assert.assertEquals(result, c); + + Character d = null; + ByteBufUtils.writeCharBox(byteBuf, d); + Assert.assertEquals(ByteBufUtils.readCharBox(byteBuf), Character.valueOf(Character.MIN_VALUE)); + } + + @Ignore + @Test + public void readLongSpeedTest() { + var startTime = System.currentTimeMillis(); + long[] values = new long[]{Long.MIN_VALUE, -9999999999999999L, -9999999999999999L, -99999999, -9999, -100, -2, -1 + , 0, 1, 2, 100, 9999, 99999999, 9999999999999999L, Long.MAX_VALUE}; + ByteBuf buffer = new UnpooledHeapByteBuf(ByteBufAllocator.DEFAULT, 100, 1_0000); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + buffer.clear(); + for (long value : values) { + ByteBufUtils.writeLong(buffer, value); + ByteBufUtils.readLong(buffer); + } + } + System.out.println(System.currentTimeMillis() - startTime); + } + + + /** + * 测试所有int值,运行时间太长,放弃测试 + */ + @Ignore + @Test + public void readIntTest() { + ByteBuf byteBuf = Unpooled.buffer(); + for (int value = Integer.MIN_VALUE; value < Integer.MAX_VALUE; value++) { + byteBuf.clear(); + ByteBufUtils.writeInt(byteBuf, value); + int result = ByteBufUtils.readInt(byteBuf); + Assert.assertEquals(result, value); + } + } + + + @Ignore + @Test + public void readLongTest() { + ByteBuf byteBuf = Unpooled.buffer(); + for (long value = Long.MIN_VALUE; value < Long.MAX_VALUE; value++) { + byteBuf.clear(); + ByteBufUtils.writeLong(byteBuf, value); + Assert.assertEquals(ByteBufUtils.readLong(byteBuf), value); + } + } + +} diff --git a/protocol/src/test/resources/ComplexObject.bytes b/protocol/src/test/resources/ComplexObject.bytes new file mode 100644 index 0000000000000000000000000000000000000000..372f9c8aa88e5eb7d0080029c2323fe24ae49ad8 GIT binary patch literal 2214 zcmZSSU`$TtYM9HATn{1D`PKQk8W{f1Wnch`{YPT{YhE7@!hHXMfd4O$b(E3e7$aC1 zB!Wx)-~aYMXZIXdhS5SG{b2AHDuZSgOgU5)sp@_9?NI<>KA;a6&j1;Rj-NRTWHUGc zX^=#H{r~@XB^{?oyos)pH^ aa5);A#2;|wf|$ literal 0 HcmV?d00001 diff --git a/protocol/src/test/resources/csTest/CsProtocol/Buffer/BigEndianByteBuffer.cs b/protocol/src/test/resources/csTest/CsProtocol/Buffer/BigEndianByteBuffer.cs new file mode 100644 index 00000000..3c836577 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Buffer/BigEndianByteBuffer.cs @@ -0,0 +1,74 @@ +namespace CsProtocol.Buffer +{ + public class BigEndianByteBuffer : ByteBuffer + { + // fast int to byte[] conversion and vice versa + // -> test with 100k conversions: + // BitConverter.GetBytes(ushort): 144ms + // bit shifting: 11ms + // -> 10x speed improvement makes this optimization actually worth it + // -> this way we don't need to allocate BinaryWriter/Reader either + // -> 4 bytes because some people may want to send messages larger than + // 64K bytes + // => big endian is standard for network transmissions, and necessary + // for compatibility with erlang + public static byte[] IntToBytesBigEndian(int value) + { + return new byte[] + { + (byte) (value >> 24), + (byte) (value >> 16), + (byte) (value >> 8), + (byte) value + }; + } + + public static int BytesToIntBigEndian(byte[] bytes) + { + return (bytes[0] << 24) | + (bytes[1] << 16) | + (bytes[2] << 8) | + bytes[3]; + } + + public override void WriteShort(short value) + { + WriteBytes(GetBytes(value)); + } + + public override short ReadShort() + { + return GetInt16(ReadBytes(2)); + } + + public override void WriteRawInt(int value) + { + WriteBytes(IntToBytesBigEndian(value)); + } + + public override int ReadRawInt() + { + return BytesToIntBigEndian(ReadBytes(4)); + } + + public override void WriteFloat(float value) + { + WriteBytes(GetBytes(value)); + } + + public override float ReadFloat() + { + return GetSingle(ReadBytes(4)); + } + + public override void WriteDouble(double value) + { + WriteBytes(GetBytes(value)); + } + + public override double ReadDouble() + { + return GetDouble(ReadBytes(8)); + } + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/csTest/CsProtocol/Buffer/ByteBuffer.cs b/protocol/src/test/resources/csTest/CsProtocol/Buffer/ByteBuffer.cs new file mode 100644 index 00000000..a86da2ef --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Buffer/ByteBuffer.cs @@ -0,0 +1,559 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CsProtocol.Buffer +{ + public abstract class ByteBuffer + { + private static readonly Queue byteBufferQueue = new Queue(); + + private static readonly int INIT_SIZE = 128; + private static readonly int MAX_SIZE = 655537; + + private byte[] buffer = new byte[INIT_SIZE]; + private int writeOffset = 0; + private int readOffset = 0; + + public static ByteBuffer ValueOf() + { + lock (byteBufferQueue) + { + if (byteBufferQueue.Count > 0) + { + return byteBufferQueue.Dequeue(); + } + } + + if (BitConverter.IsLittleEndian) + { + return new LittleEndianByteBuffer(); + } + + return new BigEndianByteBuffer(); + } + + public void Clear() + { + lock (byteBufferQueue) + { + if (byteBufferQueue.Contains(this)) + { + throw new Exception("The reference has been released."); + } + + byteBufferQueue.Enqueue(this); + writeOffset = 0; + readOffset = 0; + } + } + + // -------------------------------------------------get/set------------------------------------------------- + public int WriteOffset() + { + return writeOffset; + } + + public void SetWriteOffset(int writeIndex) + { + if (writeOffset > buffer.Length) + { + throw new Exception("writeIndex[" + writeIndex + "] out of bounds exception: readerIndex: " + readOffset + + ", writerIndex: " + writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + buffer.Length); + } + + writeOffset = writeIndex; + } + + public void SetReadOffset(int readIndex) + { + if (readIndex > writeOffset) + { + throw new Exception("readIndex[" + readIndex + "] out of bounds exception: readerIndex: " + readOffset + + ", writerIndex: " + writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + buffer.Length); + } + + readOffset = readIndex; + } + + public byte[] ToBytes() + { + var bytes = new byte[writeOffset]; + Array.Copy(buffer, 0, bytes, 0, writeOffset); + return bytes; + } + + + // -------------------------------------------------write/read------------------------------------------------- + public void WriteBool(bool value) + { + EnsureCapacity(1); + buffer[writeOffset] = value ? (byte) 1 : (byte) 0; + writeOffset++; + } + + public bool ReadBool() + { + var byteValue = buffer[readOffset]; + readOffset++; + return byteValue == 1; + } + + public void WriteByte(byte value) + { + EnsureCapacity(1); + buffer[writeOffset] = value; + writeOffset++; + } + + public byte ReadByte() + { + var byteValue = buffer[readOffset]; + readOffset++; + return byteValue; + } + + + public int GetCapacity() + { + return buffer.Length - writeOffset; + } + + public void EnsureCapacity(int capacity) + { + while (capacity - GetCapacity() > 0) + { + var newSize = buffer.Length * 2; + if (newSize > MAX_SIZE) + { + throw new Exception("Bytebuf max size is [655537], out of memory error"); + } + + var newBytes = new byte[newSize]; + Array.Copy(buffer, 0, newBytes, 0, buffer.Length); + this.buffer = newBytes; + } + } + + public void WriteBytes(byte[] bytes) + { + EnsureCapacity(bytes.Length); + var length = bytes.Length; + Array.Copy(bytes, 0, buffer, writeOffset, length); + writeOffset += length; + } + + public byte[] ReadBytes(int count) + { + var bytes = new byte[count]; + Array.Copy(buffer, readOffset, bytes, 0, count); + readOffset += count; + return bytes; + } + + public abstract void WriteShort(short value); + public abstract short ReadShort(); + + + // *******************************************int*************************************************** + public void WriteInt(int intValue) + { + // 用Zigzag算法压缩int和long的值 + // 再用Varint紧凑算法表示数字的有效位 + uint value = (uint) ((intValue << 1) ^ (intValue >> 31)); + + if (value >> 7 == 0) + { + WriteByte((byte) value); + return; + } + + if (value >> 14 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) (value >> 7)); + return; + } + + if (value >> 21 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) (value >> 14)); + return; + } + + if (value >> 28 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) (value >> 21)); + return; + } + + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) (value >> 28)); + } + + public int ReadInt() + { + uint b = ReadByte(); + uint value = b & 0x7F; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 28; + } + } + } + } + + return (int) (value >> 1) ^ -((int) (value) & 1); + } + + // 写入没有压缩的int + public abstract void WriteRawInt(int value); + + // 读取没有压缩的int + public abstract int ReadRawInt(); + + // *******************************************long************************************************** + public long ReadLong() + { + ulong b = ReadByte(); + ulong value = b & 0x7F; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 7; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 14; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 21; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 28; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 35; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 42; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= (b & 0x7F) << 49; + if ((b & 0x80) != 0) + { + b = ReadByte(); + value |= b << 56; + } + } + } + } + } + } + } + } + + return (long) (value >> 1) ^ -((long) value & 1); + } + + public void WriteLong(long longValue) + { + ulong value = (ulong) ((longValue << 1) ^ (longValue >> 63)); + + if (value >> 7 == 0) + { + WriteByte((byte) value); + return; + } + + if (value >> 14 == 0) + { + WriteByte((byte) ((value & 0x7F) | 0x80)); + WriteByte((byte) (value >> 7)); + return; + } + + if (value >> 21 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) (value >> 14)); + return; + } + + if ((value >> 28) == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) (value >> 21)); + return; + } + + if (value >> 35 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) (value >> 28)); + return; + } + + if (value >> 42 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) (value >> 35)); + return; + } + + if (value >> 49 == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) ((value >> 35) | 0x80)); + WriteByte((byte) (value >> 42)); + return; + } + + if ((value >> 56) == 0) + { + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) ((value >> 35) | 0x80)); + WriteByte((byte) ((value >> 42) | 0x80)); + WriteByte((byte) (value >> 49)); + return; + } + + WriteByte((byte) (value | 0x80)); + WriteByte((byte) ((value >> 7) | 0x80)); + WriteByte((byte) ((value >> 14) | 0x80)); + WriteByte((byte) ((value >> 21) | 0x80)); + WriteByte((byte) ((value >> 28) | 0x80)); + WriteByte((byte) ((value >> 35) | 0x80)); + WriteByte((byte) ((value >> 42) | 0x80)); + WriteByte((byte) ((value >> 49) | 0x80)); + WriteByte((byte) (value >> 56)); + } + + + // *******************************************float*************************************************** + public abstract void WriteFloat(float value); + public abstract float ReadFloat(); + + // *******************************************double*************************************************** + public abstract void WriteDouble(double value); + public abstract double ReadDouble(); + + // *******************************************char*************************************************** + public char ReadChar() + { + // need check + var str = ReadString(); + return string.IsNullOrEmpty(str) ? char.MinValue : str[0]; + } + + public void WriteChar(char value) + { + // need check + WriteString(new string(value, 1)); + } + + // *******************************************String*************************************************** + + public void WriteString(string value) + { + if (string.IsNullOrEmpty(value)) + { + WriteInt(0); + return; + } + + byte[] strBytes = GetBytes(value); + + if (strBytes == null || strBytes.Length <= 0) + { + WriteInt(0); + return; + } + + WriteInt(strBytes.Length); + WriteBytes(strBytes); + } + + public string ReadString() + { + int length = ReadInt(); + if (length <= 0) + { + return string.Empty; + } + + byte[] bytes = new byte[length]; + for (int i = 0; i < length; i++) + { + bytes[i] = ReadByte(); + } + + string str = GetString(bytes); + return str; + } + + + // -------------------------------------------------Converter------------------------------------------------- + private static readonly byte[] EMPTY_BYTE_ARRAY = new byte[] { }; + + /// + /// 以字节数组的形式返回指定的 16 位有符号整数值。 + /// + /// 要转换的数字。 + /// 长度为 2 的字节数组。 + public byte[] GetBytes(short value) + { + return BitConverter.GetBytes(value); + } + + /// + /// 返回由字节数组中前两个字节转换来的 16 位有符号整数。 + /// + /// 字节数组。 + /// 由两个字节构成的 16 位有符号整数。 + public short GetInt16(byte[] value) + { + return BitConverter.ToInt16(value, 0); + } + + + /// + /// 以字节数组的形式返回指定的 32 位有符号整数值。 + /// + /// 要转换的数字。 + /// 长度为 4 的字节数组。 + public byte[] GetBytes(int value) + { + return BitConverter.GetBytes(value); + } + + + /// + /// 返回由字节数组中前四个字节转换来的 32 位有符号整数。 + /// + /// 字节数组。 + /// 由四个字节构成的 32 位有符号整数。 + public int GetInt32(byte[] value) + { + return BitConverter.ToInt32(value, 0); + } + + + /// + /// 以字节数组的形式返回指定的单精度浮点值。 + /// + /// 要转换的数字。 + /// 长度为 4 的字节数组。 + public byte[] GetBytes(float value) + { + return BitConverter.GetBytes(value); + } + + /// + /// 返回由字节数组中前四个字节转换来的单精度浮点数。 + /// + /// 字节数组。 + /// 由四个字节构成的单精度浮点数。 + public float GetSingle(byte[] value) + { + return BitConverter.ToSingle(value, 0); + } + + /// + /// 以字节数组的形式返回指定的双精度浮点值。 + /// + /// 要转换的数字。 + /// 长度为 8 的字节数组。 + public byte[] GetBytes(double value) + { + return BitConverter.GetBytes(value); + } + + /// + /// 返回由字节数组中前八个字节转换来的双精度浮点数。 + /// + /// 字节数组。 + /// 由八个字节构成的双精度浮点数。 + public double GetDouble(byte[] value) + { + return BitConverter.ToDouble(value, 0); + } + + + /// + /// 以 UTF-8 字节数组的形式返回指定的字符串。 + /// + /// 要转换的字符串。 + /// UTF-8 字节数组。 + public byte[] GetBytes(string value) + { + if (value == null) + { + return EMPTY_BYTE_ARRAY; + } + + return Encoding.UTF8.GetBytes(value); + } + + /// + /// 返回由 UTF-8 字节数组转换来的字符串。 + /// + /// UTF-8 字节数组。 + /// 字符串。 + public string GetString(byte[] value) + { + if (value == null) + { + return string.Empty; + } + + return Encoding.UTF8.GetString(value, 0, value.Length); + } + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/csTest/CsProtocol/Buffer/LittleEndianByteBuffer.cs b/protocol/src/test/resources/csTest/CsProtocol/Buffer/LittleEndianByteBuffer.cs new file mode 100644 index 00000000..38c42738 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Buffer/LittleEndianByteBuffer.cs @@ -0,0 +1,56 @@ +using System; + +namespace CsProtocol.Buffer +{ + public class LittleEndianByteBuffer : ByteBuffer + { + /** + * 翻转字节数组,如果本地字节序列为低字节序列,则进行翻转以转换为高字节序列 + */ + private static byte[] reverse(byte[] bytes) + { + Array.Reverse(bytes); + return bytes; + } + + public override void WriteShort(short value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override short ReadShort() + { + return GetInt16(reverse(ReadBytes(2))); + } + + public override void WriteRawInt(int value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override int ReadRawInt() + { + return GetInt32(reverse(ReadBytes(4))); + } + + public override void WriteFloat(float value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override float ReadFloat() + { + return GetSingle(reverse(ReadBytes(4))); + } + + public override void WriteDouble(double value) + { + WriteBytes(reverse(GetBytes(value))); + } + + public override double ReadDouble() + { + return GetDouble(reverse(ReadBytes(8))); + } + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/csTest/CsProtocol/IPacket.cs b/protocol/src/test/resources/csTest/CsProtocol/IPacket.cs new file mode 100644 index 00000000..0d113ea1 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/IPacket.cs @@ -0,0 +1,7 @@ +namespace CsProtocol.Buffer +{ + public interface IPacket + { + short ProtocolId(); + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/csTest/CsProtocol/IProtocolRegistration.cs b/protocol/src/test/resources/csTest/CsProtocol/IProtocolRegistration.cs new file mode 100644 index 00000000..c3ec2439 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/IProtocolRegistration.cs @@ -0,0 +1,12 @@ +namespace CsProtocol.Buffer +{ + public interface IProtocolRegistration + { + short ProtocolId(); + + void Write(ByteBuffer buffer, IPacket packet); + + IPacket Read(ByteBuffer buffer); + + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/csTest/CsProtocol/Packet/ComplexObject.cs b/protocol/src/test/resources/csTest/CsProtocol/Packet/ComplexObject.cs new file mode 100644 index 00000000..9b33a3f3 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Packet/ComplexObject.cs @@ -0,0 +1,1441 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + // 复杂的对象 + // 包括了各种复杂的结构,数组,List,Set,Map + // + // @author jaysunxiao + // @version 1.0 + // @since 2017 10.14 11:19 + public class ComplexObject : IPacket + { + // byte类型,最简单的整形 + public byte a; + // byte的包装类型 + // 优先使用基础类型,包装类型会有装箱拆箱 + public byte aa; + // 数组类型 + public byte[] aaa; + public byte[] aaaa; + public short b; + public short bb; + public short[] bbb; + public short[] bbbb; + public int c; + public int cc; + public int[] ccc; + public int[] cccc; + public long d; + public long dd; + public long[] ddd; + public long[] dddd; + public float e; + public float ee; + public float[] eee; + public float[] eeee; + public double f; + public double ff; + public double[] fff; + public double[] ffff; + public bool g; + public bool gg; + public bool[] ggg; + public bool[] gggg; + public char h; + public char hh; + public char[] hhh; + public char[] hhhh; + public string jj; + public string[] jjj; + public ObjectA kk; + public ObjectA[] kkk; + public List l; + public List>> ll; + public List> lll; + public List llll; + public List> lllll; + public Dictionary m; + public Dictionary mm; + public Dictionary> mmm; + public Dictionary>, List>>> mmmm; + public Dictionary>, HashSet>> mmmmm; + public HashSet s; + public HashSet>> ss; + public HashSet> sss; + public HashSet ssss; + public HashSet> sssss; + + public static ComplexObject ValueOf(byte a, byte aa, byte[] aaa, byte[] aaaa, short b, short bb, short[] bbb, short[] bbbb, int c, int cc, int[] ccc, int[] cccc, long d, long dd, long[] ddd, long[] dddd, float e, float ee, float[] eee, float[] eeee, double f, double ff, double[] fff, double[] ffff, bool g, bool gg, bool[] ggg, bool[] gggg, char h, char hh, char[] hhh, char[] hhhh, string jj, string[] jjj, ObjectA kk, ObjectA[] kkk, List l, List>> ll, List> lll, List llll, List> lllll, Dictionary m, Dictionary mm, Dictionary> mmm, Dictionary>, List>>> mmmm, Dictionary>, HashSet>> mmmmm, HashSet s, HashSet>> ss, HashSet> sss, HashSet ssss, HashSet> sssss) + { + var packet = new ComplexObject(); + packet.a = a; + packet.aa = aa; + packet.aaa = aaa; + packet.aaaa = aaaa; + packet.b = b; + packet.bb = bb; + packet.bbb = bbb; + packet.bbbb = bbbb; + packet.c = c; + packet.cc = cc; + packet.ccc = ccc; + packet.cccc = cccc; + packet.d = d; + packet.dd = dd; + packet.ddd = ddd; + packet.dddd = dddd; + packet.e = e; + packet.ee = ee; + packet.eee = eee; + packet.eeee = eeee; + packet.f = f; + packet.ff = ff; + packet.fff = fff; + packet.ffff = ffff; + packet.g = g; + packet.gg = gg; + packet.ggg = ggg; + packet.gggg = gggg; + packet.h = h; + packet.hh = hh; + packet.hhh = hhh; + packet.hhhh = hhhh; + packet.jj = jj; + packet.jjj = jjj; + packet.kk = kk; + packet.kkk = kkk; + packet.l = l; + packet.ll = ll; + packet.lll = lll; + packet.llll = llll; + packet.lllll = lllll; + packet.m = m; + packet.mm = mm; + packet.mmm = mmm; + packet.mmmm = mmmm; + packet.mmmmm = mmmmm; + packet.s = s; + packet.ss = ss; + packet.sss = sss; + packet.ssss = ssss; + packet.sssss = sssss; + return packet; + } + + + public short ProtocolId() + { + return 1160; + } + } + + + public class ComplexObjectRegistration : IProtocolRegistration + { + public short ProtocolId() + { + return 1160; + } + + public void Write(ByteBuffer buffer, IPacket packet) + { + if (packet == null) + { + buffer.WriteBool(false); + return; + } + buffer.WriteBool(true); + ComplexObject message = (ComplexObject) packet; + buffer.WriteByte(message.a); + buffer.WriteByte(message.aa); + if ((message.aaa == null) || (message.aaa.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.aaa.Length); + int length0 = message.aaa.Length; + for (int i1 = 0; i1 < length0; i1++) + { + byte element2 = message.aaa[i1]; + buffer.WriteByte(element2); + } + } + if ((message.aaaa == null) || (message.aaaa.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.aaaa.Length); + int length3 = message.aaaa.Length; + for (int i4 = 0; i4 < length3; i4++) + { + byte element5 = message.aaaa[i4]; + buffer.WriteByte(element5); + } + } + buffer.WriteShort(message.b); + buffer.WriteShort(message.bb); + if ((message.bbb == null) || (message.bbb.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.bbb.Length); + int length6 = message.bbb.Length; + for (int i7 = 0; i7 < length6; i7++) + { + short element8 = message.bbb[i7]; + buffer.WriteShort(element8); + } + } + if ((message.bbbb == null) || (message.bbbb.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.bbbb.Length); + int length9 = message.bbbb.Length; + for (int i10 = 0; i10 < length9; i10++) + { + short element11 = message.bbbb[i10]; + buffer.WriteShort(element11); + } + } + buffer.WriteInt(message.c); + buffer.WriteInt(message.cc); + if ((message.ccc == null) || (message.ccc.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ccc.Length); + int length12 = message.ccc.Length; + for (int i13 = 0; i13 < length12; i13++) + { + int element14 = message.ccc[i13]; + buffer.WriteInt(element14); + } + } + if ((message.cccc == null) || (message.cccc.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.cccc.Length); + int length15 = message.cccc.Length; + for (int i16 = 0; i16 < length15; i16++) + { + int element17 = message.cccc[i16]; + buffer.WriteInt(element17); + } + } + buffer.WriteLong(message.d); + buffer.WriteLong(message.dd); + if ((message.ddd == null) || (message.ddd.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ddd.Length); + int length18 = message.ddd.Length; + for (int i19 = 0; i19 < length18; i19++) + { + long element20 = message.ddd[i19]; + buffer.WriteLong(element20); + } + } + if ((message.dddd == null) || (message.dddd.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.dddd.Length); + int length21 = message.dddd.Length; + for (int i22 = 0; i22 < length21; i22++) + { + long element23 = message.dddd[i22]; + buffer.WriteLong(element23); + } + } + buffer.WriteFloat(message.e); + buffer.WriteFloat(message.ee); + if ((message.eee == null) || (message.eee.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.eee.Length); + int length24 = message.eee.Length; + for (int i25 = 0; i25 < length24; i25++) + { + float element26 = message.eee[i25]; + buffer.WriteFloat(element26); + } + } + if ((message.eeee == null) || (message.eeee.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.eeee.Length); + int length27 = message.eeee.Length; + for (int i28 = 0; i28 < length27; i28++) + { + float element29 = message.eeee[i28]; + buffer.WriteFloat(element29); + } + } + buffer.WriteDouble(message.f); + buffer.WriteDouble(message.ff); + if ((message.fff == null) || (message.fff.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.fff.Length); + int length30 = message.fff.Length; + for (int i31 = 0; i31 < length30; i31++) + { + double element32 = message.fff[i31]; + buffer.WriteDouble(element32); + } + } + if ((message.ffff == null) || (message.ffff.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ffff.Length); + int length33 = message.ffff.Length; + for (int i34 = 0; i34 < length33; i34++) + { + double element35 = message.ffff[i34]; + buffer.WriteDouble(element35); + } + } + buffer.WriteBool(message.g); + buffer.WriteBool(message.gg); + if ((message.ggg == null) || (message.ggg.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ggg.Length); + int length36 = message.ggg.Length; + for (int i37 = 0; i37 < length36; i37++) + { + bool element38 = message.ggg[i37]; + buffer.WriteBool(element38); + } + } + if ((message.gggg == null) || (message.gggg.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.gggg.Length); + int length39 = message.gggg.Length; + for (int i40 = 0; i40 < length39; i40++) + { + bool element41 = message.gggg[i40]; + buffer.WriteBool(element41); + } + } + buffer.WriteChar(message.h); + buffer.WriteChar(message.hh); + if ((message.hhh == null) || (message.hhh.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.hhh.Length); + int length42 = message.hhh.Length; + for (int i43 = 0; i43 < length42; i43++) + { + char element44 = message.hhh[i43]; + buffer.WriteChar(element44); + } + } + if ((message.hhhh == null) || (message.hhhh.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.hhhh.Length); + int length45 = message.hhhh.Length; + for (int i46 = 0; i46 < length45; i46++) + { + char element47 = message.hhhh[i46]; + buffer.WriteChar(element47); + } + } + buffer.WriteString(message.jj); + if ((message.jjj == null) || (message.jjj.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.jjj.Length); + int length48 = message.jjj.Length; + for (int i49 = 0; i49 < length48; i49++) + { + string element50 = message.jjj[i49]; + buffer.WriteString(element50); + } + } + ProtocolManager.GetProtocol(1116).Write(buffer, message.kk); + if ((message.kkk == null) || (message.kkk.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.kkk.Length); + int length51 = message.kkk.Length; + for (int i52 = 0; i52 < length51; i52++) + { + ObjectA element53 = message.kkk[i52]; + ProtocolManager.GetProtocol(1116).Write(buffer, element53); + } + } + if (message.l == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.l.Count); + int length54 = message.l.Count; + for (int i55 = 0; i55 < length54; i55++) + { + var element56 = message.l[i55]; + buffer.WriteInt(element56); + } + } + if (message.ll == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ll.Count); + int length57 = message.ll.Count; + for (int i58 = 0; i58 < length57; i58++) + { + var element59 = message.ll[i58]; + if (element59 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element59.Count); + int length60 = element59.Count; + for (int i61 = 0; i61 < length60; i61++) + { + var element62 = element59[i61]; + if (element62 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element62.Count); + int length63 = element62.Count; + for (int i64 = 0; i64 < length63; i64++) + { + var element65 = element62[i64]; + buffer.WriteInt(element65); + } + } + } + } + } + } + if (message.lll == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.lll.Count); + int length66 = message.lll.Count; + for (int i67 = 0; i67 < length66; i67++) + { + var element68 = message.lll[i67]; + if (element68 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element68.Count); + int length69 = element68.Count; + for (int i70 = 0; i70 < length69; i70++) + { + var element71 = element68[i70]; + ProtocolManager.GetProtocol(1116).Write(buffer, element71); + } + } + } + } + if (message.llll == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.llll.Count); + int length72 = message.llll.Count; + for (int i73 = 0; i73 < length72; i73++) + { + var element74 = message.llll[i73]; + buffer.WriteString(element74); + } + } + if (message.lllll == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.lllll.Count); + int length75 = message.lllll.Count; + for (int i76 = 0; i76 < length75; i76++) + { + var element77 = message.lllll[i76]; + if ((element77 == null) || (element77.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element77.Count); + foreach (var i78 in element77) + { + var keyElement79 = i78.Key; + var valueElement80 = i78.Value; + buffer.WriteInt(keyElement79); + buffer.WriteString(valueElement80); + } + } + } + } + if ((message.m == null) || (message.m.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.m.Count); + foreach (var i81 in message.m) + { + var keyElement82 = i81.Key; + var valueElement83 = i81.Value; + buffer.WriteInt(keyElement82); + buffer.WriteString(valueElement83); + } + } + if ((message.mm == null) || (message.mm.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.mm.Count); + foreach (var i84 in message.mm) + { + var keyElement85 = i84.Key; + var valueElement86 = i84.Value; + buffer.WriteInt(keyElement85); + ProtocolManager.GetProtocol(1116).Write(buffer, valueElement86); + } + } + if ((message.mmm == null) || (message.mmm.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.mmm.Count); + foreach (var i87 in message.mmm) + { + var keyElement88 = i87.Key; + var valueElement89 = i87.Value; + ProtocolManager.GetProtocol(1116).Write(buffer, keyElement88); + if (valueElement89 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(valueElement89.Count); + int length90 = valueElement89.Count; + for (int i91 = 0; i91 < length90; i91++) + { + var element92 = valueElement89[i91]; + buffer.WriteInt(element92); + } + } + } + } + if ((message.mmmm == null) || (message.mmmm.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.mmmm.Count); + foreach (var i93 in message.mmmm) + { + var keyElement94 = i93.Key; + var valueElement95 = i93.Value; + if (keyElement94 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(keyElement94.Count); + int length96 = keyElement94.Count; + for (int i97 = 0; i97 < length96; i97++) + { + var element98 = keyElement94[i97]; + if (element98 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element98.Count); + int length99 = element98.Count; + for (int i100 = 0; i100 < length99; i100++) + { + var element101 = element98[i100]; + ProtocolManager.GetProtocol(1116).Write(buffer, element101); + } + } + } + } + if (valueElement95 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(valueElement95.Count); + int length102 = valueElement95.Count; + for (int i103 = 0; i103 < length102; i103++) + { + var element104 = valueElement95[i103]; + if (element104 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element104.Count); + int length105 = element104.Count; + for (int i106 = 0; i106 < length105; i106++) + { + var element107 = element104[i106]; + if (element107 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element107.Count); + int length108 = element107.Count; + for (int i109 = 0; i109 < length108; i109++) + { + var element110 = element107[i109]; + buffer.WriteInt(element110); + } + } + } + } + } + } + } + } + if ((message.mmmmm == null) || (message.mmmmm.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.mmmmm.Count); + foreach (var i111 in message.mmmmm) + { + var keyElement112 = i111.Key; + var valueElement113 = i111.Value; + if (keyElement112 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(keyElement112.Count); + int length114 = keyElement112.Count; + for (int i115 = 0; i115 < length114; i115++) + { + var element116 = keyElement112[i115]; + if ((element116 == null) || (element116.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(element116.Count); + foreach (var i117 in element116) + { + var keyElement118 = i117.Key; + var valueElement119 = i117.Value; + buffer.WriteInt(keyElement118); + buffer.WriteString(valueElement119); + } + } + } + } + if (valueElement113 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(valueElement113.Count); + foreach (var i120 in valueElement113) + { + if ((i120 == null) || (i120.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(i120.Count); + foreach (var i121 in i120) + { + var keyElement122 = i121.Key; + var valueElement123 = i121.Value; + buffer.WriteInt(keyElement122); + buffer.WriteString(valueElement123); + } + } + } + } + } + } + if (message.s == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.s.Count); + foreach (var i124 in message.s) + { + buffer.WriteInt(i124); + } + } + if (message.ss == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ss.Count); + foreach (var i125 in message.ss) + { + if (i125 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(i125.Count); + foreach (var i126 in i125) + { + if (i126 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(i126.Count); + int length127 = i126.Count; + for (int i128 = 0; i128 < length127; i128++) + { + var element129 = i126[i128]; + buffer.WriteInt(element129); + } + } + } + } + } + } + if (message.sss == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.sss.Count); + foreach (var i130 in message.sss) + { + if (i130 == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(i130.Count); + foreach (var i131 in i130) + { + ProtocolManager.GetProtocol(1116).Write(buffer, i131); + } + } + } + } + if (message.ssss == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ssss.Count); + foreach (var i132 in message.ssss) + { + buffer.WriteString(i132); + } + } + if (message.sssss == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.sssss.Count); + foreach (var i133 in message.sssss) + { + if ((i133 == null) || (i133.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(i133.Count); + foreach (var i134 in i133) + { + var keyElement135 = i134.Key; + var valueElement136 = i134.Value; + buffer.WriteInt(keyElement135); + buffer.WriteString(valueElement136); + } + } + } + } + } + + public IPacket Read(ByteBuffer buffer) + { + if (!buffer.ReadBool()) + { + return null; + } + ComplexObject packet = new ComplexObject(); + byte result137 = buffer.ReadByte(); + packet.a = result137; + byte result138 = buffer.ReadByte(); + packet.aa = result138; + int size141 = buffer.ReadInt(); + byte[] result139 = new byte[size141]; + if (size141 > 0) + { + for (int index140 = 0; index140 < size141; index140++) + { + byte result142 = buffer.ReadByte(); + result139[index140] = result142; + } + } + packet.aaa = result139; + int size145 = buffer.ReadInt(); + byte[] result143 = new byte[size145]; + if (size145 > 0) + { + for (int index144 = 0; index144 < size145; index144++) + { + byte result146 = buffer.ReadByte(); + result143[index144] = result146; + } + } + packet.aaaa = result143; + short result147 = buffer.ReadShort(); + packet.b = result147; + short result148 = buffer.ReadShort(); + packet.bb = result148; + int size151 = buffer.ReadInt(); + short[] result149 = new short[size151]; + if (size151 > 0) + { + for (int index150 = 0; index150 < size151; index150++) + { + short result152 = buffer.ReadShort(); + result149[index150] = result152; + } + } + packet.bbb = result149; + int size155 = buffer.ReadInt(); + short[] result153 = new short[size155]; + if (size155 > 0) + { + for (int index154 = 0; index154 < size155; index154++) + { + short result156 = buffer.ReadShort(); + result153[index154] = result156; + } + } + packet.bbbb = result153; + int result157 = buffer.ReadInt(); + packet.c = result157; + int result158 = buffer.ReadInt(); + packet.cc = result158; + int size161 = buffer.ReadInt(); + int[] result159 = new int[size161]; + if (size161 > 0) + { + for (int index160 = 0; index160 < size161; index160++) + { + int result162 = buffer.ReadInt(); + result159[index160] = result162; + } + } + packet.ccc = result159; + int size165 = buffer.ReadInt(); + int[] result163 = new int[size165]; + if (size165 > 0) + { + for (int index164 = 0; index164 < size165; index164++) + { + int result166 = buffer.ReadInt(); + result163[index164] = result166; + } + } + packet.cccc = result163; + long result167 = buffer.ReadLong(); + packet.d = result167; + long result168 = buffer.ReadLong(); + packet.dd = result168; + int size171 = buffer.ReadInt(); + long[] result169 = new long[size171]; + if (size171 > 0) + { + for (int index170 = 0; index170 < size171; index170++) + { + long result172 = buffer.ReadLong(); + result169[index170] = result172; + } + } + packet.ddd = result169; + int size175 = buffer.ReadInt(); + long[] result173 = new long[size175]; + if (size175 > 0) + { + for (int index174 = 0; index174 < size175; index174++) + { + long result176 = buffer.ReadLong(); + result173[index174] = result176; + } + } + packet.dddd = result173; + float result177 = buffer.ReadFloat(); + packet.e = result177; + float result178 = buffer.ReadFloat(); + packet.ee = result178; + int size181 = buffer.ReadInt(); + float[] result179 = new float[size181]; + if (size181 > 0) + { + for (int index180 = 0; index180 < size181; index180++) + { + float result182 = buffer.ReadFloat(); + result179[index180] = result182; + } + } + packet.eee = result179; + int size185 = buffer.ReadInt(); + float[] result183 = new float[size185]; + if (size185 > 0) + { + for (int index184 = 0; index184 < size185; index184++) + { + float result186 = buffer.ReadFloat(); + result183[index184] = result186; + } + } + packet.eeee = result183; + double result187 = buffer.ReadDouble(); + packet.f = result187; + double result188 = buffer.ReadDouble(); + packet.ff = result188; + int size191 = buffer.ReadInt(); + double[] result189 = new double[size191]; + if (size191 > 0) + { + for (int index190 = 0; index190 < size191; index190++) + { + double result192 = buffer.ReadDouble(); + result189[index190] = result192; + } + } + packet.fff = result189; + int size195 = buffer.ReadInt(); + double[] result193 = new double[size195]; + if (size195 > 0) + { + for (int index194 = 0; index194 < size195; index194++) + { + double result196 = buffer.ReadDouble(); + result193[index194] = result196; + } + } + packet.ffff = result193; + bool result197 = buffer.ReadBool(); + packet.g = result197; + bool result198 = buffer.ReadBool(); + packet.gg = result198; + int size201 = buffer.ReadInt(); + bool[] result199 = new bool[size201]; + if (size201 > 0) + { + for (int index200 = 0; index200 < size201; index200++) + { + bool result202 = buffer.ReadBool(); + result199[index200] = result202; + } + } + packet.ggg = result199; + int size205 = buffer.ReadInt(); + bool[] result203 = new bool[size205]; + if (size205 > 0) + { + for (int index204 = 0; index204 < size205; index204++) + { + bool result206 = buffer.ReadBool(); + result203[index204] = result206; + } + } + packet.gggg = result203; + char result207 = buffer.ReadChar(); + packet.h = result207; + char result208 = buffer.ReadChar(); + packet.hh = result208; + int size211 = buffer.ReadInt(); + char[] result209 = new char[size211]; + if (size211 > 0) + { + for (int index210 = 0; index210 < size211; index210++) + { + char result212 = buffer.ReadChar(); + result209[index210] = result212; + } + } + packet.hhh = result209; + int size215 = buffer.ReadInt(); + char[] result213 = new char[size215]; + if (size215 > 0) + { + for (int index214 = 0; index214 < size215; index214++) + { + char result216 = buffer.ReadChar(); + result213[index214] = result216; + } + } + packet.hhhh = result213; + string result217 = buffer.ReadString(); + packet.jj = result217; + int size220 = buffer.ReadInt(); + string[] result218 = new string[size220]; + if (size220 > 0) + { + for (int index219 = 0; index219 < size220; index219++) + { + string result221 = buffer.ReadString(); + result218[index219] = result221; + } + } + packet.jjj = result218; + ObjectA result222 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + packet.kk = result222; + int size225 = buffer.ReadInt(); + ObjectA[] result223 = new ObjectA[size225]; + if (size225 > 0) + { + for (int index224 = 0; index224 < size225; index224++) + { + ObjectA result226 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result223[index224] = result226; + } + } + packet.kkk = result223; + int size229 = buffer.ReadInt(); + var result227 = new List(size229); + if (size229 > 0) + { + for (int index228 = 0; index228 < size229; index228++) + { + int result230 = buffer.ReadInt(); + result227.Add(result230); + } + } + packet.l = result227; + int size233 = buffer.ReadInt(); + var result231 = new List>>(size233); + if (size233 > 0) + { + for (int index232 = 0; index232 < size233; index232++) + { + int size236 = buffer.ReadInt(); + var result234 = new List>(size236); + if (size236 > 0) + { + for (int index235 = 0; index235 < size236; index235++) + { + int size239 = buffer.ReadInt(); + var result237 = new List(size239); + if (size239 > 0) + { + for (int index238 = 0; index238 < size239; index238++) + { + int result240 = buffer.ReadInt(); + result237.Add(result240); + } + } + result234.Add(result237); + } + } + result231.Add(result234); + } + } + packet.ll = result231; + int size243 = buffer.ReadInt(); + var result241 = new List>(size243); + if (size243 > 0) + { + for (int index242 = 0; index242 < size243; index242++) + { + int size246 = buffer.ReadInt(); + var result244 = new List(size246); + if (size246 > 0) + { + for (int index245 = 0; index245 < size246; index245++) + { + ObjectA result247 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result244.Add(result247); + } + } + result241.Add(result244); + } + } + packet.lll = result241; + int size250 = buffer.ReadInt(); + var result248 = new List(size250); + if (size250 > 0) + { + for (int index249 = 0; index249 < size250; index249++) + { + string result251 = buffer.ReadString(); + result248.Add(result251); + } + } + packet.llll = result248; + int size254 = buffer.ReadInt(); + var result252 = new List>(size254); + if (size254 > 0) + { + for (int index253 = 0; index253 < size254; index253++) + { + int size256 = buffer.ReadInt(); + var result255 = new Dictionary(size256); + if (size256 > 0) + { + for (var index257 = 0; index257 < size256; index257++) + { + int result258 = buffer.ReadInt(); + string result259 = buffer.ReadString(); + result255[result258] = result259; + } + } + result252.Add(result255); + } + } + packet.lllll = result252; + int size261 = buffer.ReadInt(); + var result260 = new Dictionary(size261); + if (size261 > 0) + { + for (var index262 = 0; index262 < size261; index262++) + { + int result263 = buffer.ReadInt(); + string result264 = buffer.ReadString(); + result260[result263] = result264; + } + } + packet.m = result260; + int size266 = buffer.ReadInt(); + var result265 = new Dictionary(size266); + if (size266 > 0) + { + for (var index267 = 0; index267 < size266; index267++) + { + int result268 = buffer.ReadInt(); + ObjectA result269 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result265[result268] = result269; + } + } + packet.mm = result265; + int size271 = buffer.ReadInt(); + var result270 = new Dictionary>(size271); + if (size271 > 0) + { + for (var index272 = 0; index272 < size271; index272++) + { + ObjectA result273 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + int size276 = buffer.ReadInt(); + var result274 = new List(size276); + if (size276 > 0) + { + for (int index275 = 0; index275 < size276; index275++) + { + int result277 = buffer.ReadInt(); + result274.Add(result277); + } + } + result270[result273] = result274; + } + } + packet.mmm = result270; + int size279 = buffer.ReadInt(); + var result278 = new Dictionary>, List>>>(size279); + if (size279 > 0) + { + for (var index280 = 0; index280 < size279; index280++) + { + int size283 = buffer.ReadInt(); + var result281 = new List>(size283); + if (size283 > 0) + { + for (int index282 = 0; index282 < size283; index282++) + { + int size286 = buffer.ReadInt(); + var result284 = new List(size286); + if (size286 > 0) + { + for (int index285 = 0; index285 < size286; index285++) + { + ObjectA result287 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result284.Add(result287); + } + } + result281.Add(result284); + } + } + int size290 = buffer.ReadInt(); + var result288 = new List>>(size290); + if (size290 > 0) + { + for (int index289 = 0; index289 < size290; index289++) + { + int size293 = buffer.ReadInt(); + var result291 = new List>(size293); + if (size293 > 0) + { + for (int index292 = 0; index292 < size293; index292++) + { + int size296 = buffer.ReadInt(); + var result294 = new List(size296); + if (size296 > 0) + { + for (int index295 = 0; index295 < size296; index295++) + { + int result297 = buffer.ReadInt(); + result294.Add(result297); + } + } + result291.Add(result294); + } + } + result288.Add(result291); + } + } + result278[result281] = result288; + } + } + packet.mmmm = result278; + int size299 = buffer.ReadInt(); + var result298 = new Dictionary>, HashSet>>(size299); + if (size299 > 0) + { + for (var index300 = 0; index300 < size299; index300++) + { + int size303 = buffer.ReadInt(); + var result301 = new List>(size303); + if (size303 > 0) + { + for (int index302 = 0; index302 < size303; index302++) + { + int size305 = buffer.ReadInt(); + var result304 = new Dictionary(size305); + if (size305 > 0) + { + for (var index306 = 0; index306 < size305; index306++) + { + int result307 = buffer.ReadInt(); + string result308 = buffer.ReadString(); + result304[result307] = result308; + } + } + result301.Add(result304); + } + } + int size311 = buffer.ReadInt(); + var result309 = new HashSet>(); + if (size311 > 0) + { + for (int index310 = 0; index310 < size311; index310++) + { + int size313 = buffer.ReadInt(); + var result312 = new Dictionary(size313); + if (size313 > 0) + { + for (var index314 = 0; index314 < size313; index314++) + { + int result315 = buffer.ReadInt(); + string result316 = buffer.ReadString(); + result312[result315] = result316; + } + } + result309.Add(result312); + } + } + result298[result301] = result309; + } + } + packet.mmmmm = result298; + int size319 = buffer.ReadInt(); + var result317 = new HashSet(); + if (size319 > 0) + { + for (int index318 = 0; index318 < size319; index318++) + { + int result320 = buffer.ReadInt(); + result317.Add(result320); + } + } + packet.s = result317; + int size323 = buffer.ReadInt(); + var result321 = new HashSet>>(); + if (size323 > 0) + { + for (int index322 = 0; index322 < size323; index322++) + { + int size326 = buffer.ReadInt(); + var result324 = new HashSet>(); + if (size326 > 0) + { + for (int index325 = 0; index325 < size326; index325++) + { + int size329 = buffer.ReadInt(); + var result327 = new List(size329); + if (size329 > 0) + { + for (int index328 = 0; index328 < size329; index328++) + { + int result330 = buffer.ReadInt(); + result327.Add(result330); + } + } + result324.Add(result327); + } + } + result321.Add(result324); + } + } + packet.ss = result321; + int size333 = buffer.ReadInt(); + var result331 = new HashSet>(); + if (size333 > 0) + { + for (int index332 = 0; index332 < size333; index332++) + { + int size336 = buffer.ReadInt(); + var result334 = new HashSet(); + if (size336 > 0) + { + for (int index335 = 0; index335 < size336; index335++) + { + ObjectA result337 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result334.Add(result337); + } + } + result331.Add(result334); + } + } + packet.sss = result331; + int size340 = buffer.ReadInt(); + var result338 = new HashSet(); + if (size340 > 0) + { + for (int index339 = 0; index339 < size340; index339++) + { + string result341 = buffer.ReadString(); + result338.Add(result341); + } + } + packet.ssss = result338; + int size344 = buffer.ReadInt(); + var result342 = new HashSet>(); + if (size344 > 0) + { + for (int index343 = 0; index343 < size344; index343++) + { + int size346 = buffer.ReadInt(); + var result345 = new Dictionary(size346); + if (size346 > 0) + { + for (var index347 = 0; index347 < size346; index347++) + { + int result348 = buffer.ReadInt(); + string result349 = buffer.ReadString(); + result345[result348] = result349; + } + } + result342.Add(result345); + } + } + packet.sssss = result342; + return packet; + } + } +} diff --git a/protocol/src/test/resources/csTest/CsProtocol/Packet/NormalObject.cs b/protocol/src/test/resources/csTest/CsProtocol/Packet/NormalObject.cs new file mode 100644 index 00000000..90b4eff6 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Packet/NormalObject.cs @@ -0,0 +1,537 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + // @author jaysunxiao + // @version 1.0 + // @since 2021-02-07 17:18 + public class NormalObject : IPacket + { + public byte a; + public byte[] aaa; + public short b; + public short[] bbb; + public int c; + public int[] ccc; + public long d; + public long[] ddd; + public float e; + public float[] eee; + public double f; + public double[] fff; + public bool g; + public bool[] ggg; + public char h; + public char[] hhh; + public string jj; + public string[] jjj; + public ObjectA kk; + public ObjectA[] kkk; + public List l; + public List llll; + public Dictionary m; + public Dictionary mm; + public HashSet s; + public HashSet ssss; + + public static NormalObject ValueOf(byte a, byte[] aaa, short b, short[] bbb, int c, int[] ccc, long d, long[] ddd, float e, float[] eee, double f, double[] fff, bool g, bool[] ggg, char h, char[] hhh, string jj, string[] jjj, ObjectA kk, ObjectA[] kkk, List l, List llll, Dictionary m, Dictionary mm, HashSet s, HashSet ssss) + { + var packet = new NormalObject(); + packet.a = a; + packet.aaa = aaa; + packet.b = b; + packet.bbb = bbb; + packet.c = c; + packet.ccc = ccc; + packet.d = d; + packet.ddd = ddd; + packet.e = e; + packet.eee = eee; + packet.f = f; + packet.fff = fff; + packet.g = g; + packet.ggg = ggg; + packet.h = h; + packet.hhh = hhh; + packet.jj = jj; + packet.jjj = jjj; + packet.kk = kk; + packet.kkk = kkk; + packet.l = l; + packet.llll = llll; + packet.m = m; + packet.mm = mm; + packet.s = s; + packet.ssss = ssss; + return packet; + } + + + public short ProtocolId() + { + return 1161; + } + } + + + public class NormalObjectRegistration : IProtocolRegistration + { + public short ProtocolId() + { + return 1161; + } + + public void Write(ByteBuffer buffer, IPacket packet) + { + if (packet == null) + { + buffer.WriteBool(false); + return; + } + buffer.WriteBool(true); + NormalObject message = (NormalObject) packet; + buffer.WriteByte(message.a); + if ((message.aaa == null) || (message.aaa.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.aaa.Length); + int length0 = message.aaa.Length; + for (int i1 = 0; i1 < length0; i1++) + { + byte element2 = message.aaa[i1]; + buffer.WriteByte(element2); + } + } + buffer.WriteShort(message.b); + if ((message.bbb == null) || (message.bbb.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.bbb.Length); + int length3 = message.bbb.Length; + for (int i4 = 0; i4 < length3; i4++) + { + short element5 = message.bbb[i4]; + buffer.WriteShort(element5); + } + } + buffer.WriteInt(message.c); + if ((message.ccc == null) || (message.ccc.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ccc.Length); + int length6 = message.ccc.Length; + for (int i7 = 0; i7 < length6; i7++) + { + int element8 = message.ccc[i7]; + buffer.WriteInt(element8); + } + } + buffer.WriteLong(message.d); + if ((message.ddd == null) || (message.ddd.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ddd.Length); + int length9 = message.ddd.Length; + for (int i10 = 0; i10 < length9; i10++) + { + long element11 = message.ddd[i10]; + buffer.WriteLong(element11); + } + } + buffer.WriteFloat(message.e); + if ((message.eee == null) || (message.eee.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.eee.Length); + int length12 = message.eee.Length; + for (int i13 = 0; i13 < length12; i13++) + { + float element14 = message.eee[i13]; + buffer.WriteFloat(element14); + } + } + buffer.WriteDouble(message.f); + if ((message.fff == null) || (message.fff.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.fff.Length); + int length15 = message.fff.Length; + for (int i16 = 0; i16 < length15; i16++) + { + double element17 = message.fff[i16]; + buffer.WriteDouble(element17); + } + } + buffer.WriteBool(message.g); + if ((message.ggg == null) || (message.ggg.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ggg.Length); + int length18 = message.ggg.Length; + for (int i19 = 0; i19 < length18; i19++) + { + bool element20 = message.ggg[i19]; + buffer.WriteBool(element20); + } + } + buffer.WriteChar(message.h); + if ((message.hhh == null) || (message.hhh.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.hhh.Length); + int length21 = message.hhh.Length; + for (int i22 = 0; i22 < length21; i22++) + { + char element23 = message.hhh[i22]; + buffer.WriteChar(element23); + } + } + buffer.WriteString(message.jj); + if ((message.jjj == null) || (message.jjj.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.jjj.Length); + int length24 = message.jjj.Length; + for (int i25 = 0; i25 < length24; i25++) + { + string element26 = message.jjj[i25]; + buffer.WriteString(element26); + } + } + ProtocolManager.GetProtocol(1116).Write(buffer, message.kk); + if ((message.kkk == null) || (message.kkk.Length == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.kkk.Length); + int length27 = message.kkk.Length; + for (int i28 = 0; i28 < length27; i28++) + { + ObjectA element29 = message.kkk[i28]; + ProtocolManager.GetProtocol(1116).Write(buffer, element29); + } + } + if (message.l == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.l.Count); + int length30 = message.l.Count; + for (int i31 = 0; i31 < length30; i31++) + { + var element32 = message.l[i31]; + buffer.WriteInt(element32); + } + } + if (message.llll == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.llll.Count); + int length33 = message.llll.Count; + for (int i34 = 0; i34 < length33; i34++) + { + var element35 = message.llll[i34]; + buffer.WriteString(element35); + } + } + if ((message.m == null) || (message.m.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.m.Count); + foreach (var i36 in message.m) + { + var keyElement37 = i36.Key; + var valueElement38 = i36.Value; + buffer.WriteInt(keyElement37); + buffer.WriteString(valueElement38); + } + } + if ((message.mm == null) || (message.mm.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.mm.Count); + foreach (var i39 in message.mm) + { + var keyElement40 = i39.Key; + var valueElement41 = i39.Value; + buffer.WriteInt(keyElement40); + ProtocolManager.GetProtocol(1116).Write(buffer, valueElement41); + } + } + if (message.s == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.s.Count); + foreach (var i42 in message.s) + { + buffer.WriteInt(i42); + } + } + if (message.ssss == null) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.ssss.Count); + foreach (var i43 in message.ssss) + { + buffer.WriteString(i43); + } + } + } + + public IPacket Read(ByteBuffer buffer) + { + if (!buffer.ReadBool()) + { + return null; + } + NormalObject packet = new NormalObject(); + byte result44 = buffer.ReadByte(); + packet.a = result44; + int size47 = buffer.ReadInt(); + byte[] result45 = new byte[size47]; + if (size47 > 0) + { + for (int index46 = 0; index46 < size47; index46++) + { + byte result48 = buffer.ReadByte(); + result45[index46] = result48; + } + } + packet.aaa = result45; + short result49 = buffer.ReadShort(); + packet.b = result49; + int size52 = buffer.ReadInt(); + short[] result50 = new short[size52]; + if (size52 > 0) + { + for (int index51 = 0; index51 < size52; index51++) + { + short result53 = buffer.ReadShort(); + result50[index51] = result53; + } + } + packet.bbb = result50; + int result54 = buffer.ReadInt(); + packet.c = result54; + int size57 = buffer.ReadInt(); + int[] result55 = new int[size57]; + if (size57 > 0) + { + for (int index56 = 0; index56 < size57; index56++) + { + int result58 = buffer.ReadInt(); + result55[index56] = result58; + } + } + packet.ccc = result55; + long result59 = buffer.ReadLong(); + packet.d = result59; + int size62 = buffer.ReadInt(); + long[] result60 = new long[size62]; + if (size62 > 0) + { + for (int index61 = 0; index61 < size62; index61++) + { + long result63 = buffer.ReadLong(); + result60[index61] = result63; + } + } + packet.ddd = result60; + float result64 = buffer.ReadFloat(); + packet.e = result64; + int size67 = buffer.ReadInt(); + float[] result65 = new float[size67]; + if (size67 > 0) + { + for (int index66 = 0; index66 < size67; index66++) + { + float result68 = buffer.ReadFloat(); + result65[index66] = result68; + } + } + packet.eee = result65; + double result69 = buffer.ReadDouble(); + packet.f = result69; + int size72 = buffer.ReadInt(); + double[] result70 = new double[size72]; + if (size72 > 0) + { + for (int index71 = 0; index71 < size72; index71++) + { + double result73 = buffer.ReadDouble(); + result70[index71] = result73; + } + } + packet.fff = result70; + bool result74 = buffer.ReadBool(); + packet.g = result74; + int size77 = buffer.ReadInt(); + bool[] result75 = new bool[size77]; + if (size77 > 0) + { + for (int index76 = 0; index76 < size77; index76++) + { + bool result78 = buffer.ReadBool(); + result75[index76] = result78; + } + } + packet.ggg = result75; + char result79 = buffer.ReadChar(); + packet.h = result79; + int size82 = buffer.ReadInt(); + char[] result80 = new char[size82]; + if (size82 > 0) + { + for (int index81 = 0; index81 < size82; index81++) + { + char result83 = buffer.ReadChar(); + result80[index81] = result83; + } + } + packet.hhh = result80; + string result84 = buffer.ReadString(); + packet.jj = result84; + int size87 = buffer.ReadInt(); + string[] result85 = new string[size87]; + if (size87 > 0) + { + for (int index86 = 0; index86 < size87; index86++) + { + string result88 = buffer.ReadString(); + result85[index86] = result88; + } + } + packet.jjj = result85; + ObjectA result89 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + packet.kk = result89; + int size92 = buffer.ReadInt(); + ObjectA[] result90 = new ObjectA[size92]; + if (size92 > 0) + { + for (int index91 = 0; index91 < size92; index91++) + { + ObjectA result93 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result90[index91] = result93; + } + } + packet.kkk = result90; + int size96 = buffer.ReadInt(); + var result94 = new List(size96); + if (size96 > 0) + { + for (int index95 = 0; index95 < size96; index95++) + { + int result97 = buffer.ReadInt(); + result94.Add(result97); + } + } + packet.l = result94; + int size100 = buffer.ReadInt(); + var result98 = new List(size100); + if (size100 > 0) + { + for (int index99 = 0; index99 < size100; index99++) + { + string result101 = buffer.ReadString(); + result98.Add(result101); + } + } + packet.llll = result98; + int size103 = buffer.ReadInt(); + var result102 = new Dictionary(size103); + if (size103 > 0) + { + for (var index104 = 0; index104 < size103; index104++) + { + int result105 = buffer.ReadInt(); + string result106 = buffer.ReadString(); + result102[result105] = result106; + } + } + packet.m = result102; + int size108 = buffer.ReadInt(); + var result107 = new Dictionary(size108); + if (size108 > 0) + { + for (var index109 = 0; index109 < size108; index109++) + { + int result110 = buffer.ReadInt(); + ObjectA result111 = (ObjectA) ProtocolManager.GetProtocol(1116).Read(buffer); + result107[result110] = result111; + } + } + packet.mm = result107; + int size114 = buffer.ReadInt(); + var result112 = new HashSet(); + if (size114 > 0) + { + for (int index113 = 0; index113 < size114; index113++) + { + int result115 = buffer.ReadInt(); + result112.Add(result115); + } + } + packet.s = result112; + int size118 = buffer.ReadInt(); + var result116 = new HashSet(); + if (size118 > 0) + { + for (int index117 = 0; index117 < size118; index117++) + { + string result119 = buffer.ReadString(); + result116.Add(result119); + } + } + packet.ssss = result116; + return packet; + } + } +} diff --git a/protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectA.cs b/protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectA.cs new file mode 100644 index 00000000..0e4e9c6d --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectA.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + // @author jaysunxiao + // @version 1.0 + // @since 2017 10.12 15:39 + public class ObjectA : IPacket + { + public int a; + public Dictionary m; + public ObjectB objectB; + + public static ObjectA ValueOf(int a, Dictionary m, ObjectB objectB) + { + var packet = new ObjectA(); + packet.a = a; + packet.m = m; + packet.objectB = objectB; + return packet; + } + + + public short ProtocolId() + { + return 1116; + } + } + + + public class ObjectARegistration : IProtocolRegistration + { + public short ProtocolId() + { + return 1116; + } + + public void Write(ByteBuffer buffer, IPacket packet) + { + if (packet == null) + { + buffer.WriteBool(false); + return; + } + buffer.WriteBool(true); + ObjectA message = (ObjectA) packet; + buffer.WriteInt(message.a); + if ((message.m == null) || (message.m.Count == 0)) + { + buffer.WriteInt(0); + } + else + { + buffer.WriteInt(message.m.Count); + foreach (var i0 in message.m) + { + var keyElement1 = i0.Key; + var valueElement2 = i0.Value; + buffer.WriteInt(keyElement1); + buffer.WriteString(valueElement2); + } + } + ProtocolManager.GetProtocol(1117).Write(buffer, message.objectB); + } + + public IPacket Read(ByteBuffer buffer) + { + if (!buffer.ReadBool()) + { + return null; + } + ObjectA packet = new ObjectA(); + int result3 = buffer.ReadInt(); + packet.a = result3; + int size5 = buffer.ReadInt(); + var result4 = new Dictionary(size5); + if (size5 > 0) + { + for (var index6 = 0; index6 < size5; index6++) + { + int result7 = buffer.ReadInt(); + string result8 = buffer.ReadString(); + result4[result7] = result8; + } + } + packet.m = result4; + ObjectB result9 = (ObjectB) ProtocolManager.GetProtocol(1117).Read(buffer); + packet.objectB = result9; + return packet; + } + } +} diff --git a/protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectB.cs b/protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectB.cs new file mode 100644 index 00000000..f4401cec --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Packet/ObjectB.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + // @author jaysunxiao + // @version 1.0 + // @since 2017 10.12 15:39 + public class ObjectB : IPacket + { + public bool flag; + + public static ObjectB ValueOf(bool flag) + { + var packet = new ObjectB(); + packet.flag = flag; + return packet; + } + + + public short ProtocolId() + { + return 1117; + } + } + + + public class ObjectBRegistration : IProtocolRegistration + { + public short ProtocolId() + { + return 1117; + } + + public void Write(ByteBuffer buffer, IPacket packet) + { + if (packet == null) + { + buffer.WriteBool(false); + return; + } + buffer.WriteBool(true); + ObjectB message = (ObjectB) packet; + buffer.WriteBool(message.flag); + } + + public IPacket Read(ByteBuffer buffer) + { + if (!buffer.ReadBool()) + { + return null; + } + ObjectB packet = new ObjectB(); + bool result0 = buffer.ReadBool(); + packet.flag = result0; + return packet; + } + } +} diff --git a/protocol/src/test/resources/csTest/CsProtocol/Packet/SimpleObject.cs b/protocol/src/test/resources/csTest/CsProtocol/Packet/SimpleObject.cs new file mode 100644 index 00000000..51aef542 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/Packet/SimpleObject.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + // @author jaysunxiao + // @version 1.0 + // @since 2021-03-27 15:18 + public class SimpleObject : IPacket + { + public int c; + public bool g; + + public static SimpleObject ValueOf(int c, bool g) + { + var packet = new SimpleObject(); + packet.c = c; + packet.g = g; + return packet; + } + + + public short ProtocolId() + { + return 1163; + } + } + + + public class SimpleObjectRegistration : IProtocolRegistration + { + public short ProtocolId() + { + return 1163; + } + + public void Write(ByteBuffer buffer, IPacket packet) + { + if (packet == null) + { + buffer.WriteBool(false); + return; + } + buffer.WriteBool(true); + SimpleObject message = (SimpleObject) packet; + buffer.WriteInt(message.c); + buffer.WriteBool(message.g); + } + + public IPacket Read(ByteBuffer buffer) + { + if (!buffer.ReadBool()) + { + return null; + } + SimpleObject packet = new SimpleObject(); + int result0 = buffer.ReadInt(); + packet.c = result0; + bool result1 = buffer.ReadBool(); + packet.g = result1; + return packet; + } + } +} diff --git a/protocol/src/test/resources/csTest/CsProtocol/ProtocolManager.cs b/protocol/src/test/resources/csTest/CsProtocol/ProtocolManager.cs new file mode 100644 index 00000000..fe33cf75 --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocol/ProtocolManager.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using CsProtocol.Buffer; + +namespace CsProtocol +{ + public class ProtocolManager + { + public static readonly short MAX_PROTOCOL_NUM = short.MaxValue; + + + private static readonly IProtocolRegistration[] protocolList = new IProtocolRegistration[MAX_PROTOCOL_NUM]; + + + public static void InitProtocol() + { + var protocolRegistrationTypeList = new List(); + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (assembly.Equals(typeof(ProtocolManager).Assembly)) + { + var results = new List(); + results.AddRange(assembly.GetTypes()); + foreach (var type in results) + { + if (type.IsClass && !type.IsAbstract && typeof(IProtocolRegistration).IsAssignableFrom(type)) + { + protocolRegistrationTypeList.Add(type); + } + } + } + } + + foreach (var protocolRegistrationType in protocolRegistrationTypeList) + { + var protocolRegistration = (IProtocolRegistration) Activator.CreateInstance(protocolRegistrationType); + protocolList[protocolRegistration.ProtocolId()] = protocolRegistration; + } + } + + public static IProtocolRegistration GetProtocol(short protocolId) + { + var protocol = protocolList[protocolId]; + if (protocol == null) + { + throw new Exception("[protocolId:" + protocolId + "]协议不存在"); + } + + return protocol; + } + + public static void Write(ByteBuffer byteBuffer, IPacket packet) + { + var protocolId = packet.ProtocolId(); + // 写入协议号 + byteBuffer.WriteShort(protocolId); + + // 写入包体 + GetProtocol(protocolId).Write(byteBuffer, packet); + } + + public static IPacket Read(ByteBuffer byteBuffer) + { + var protocolId = byteBuffer.ReadShort(); + return GetProtocol(protocolId).Read(byteBuffer); + } + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/csTest/CsProtocolTest.cs b/protocol/src/test/resources/csTest/CsProtocolTest.cs new file mode 100644 index 00000000..1027e43c --- /dev/null +++ b/protocol/src/test/resources/csTest/CsProtocolTest.cs @@ -0,0 +1,193 @@ +using System; +using System.IO; +using CsProtocol; +using CsProtocol.Buffer; +using NUnit.Framework; + +namespace Test.Editor.Net +{ + public class CsProtocolTest + { + [Test] + public void ComplexObjectTest() + { + ProtocolManager.InitProtocol(); + // 获取复杂对象的字节流 + var complexObjectBytes = File.ReadAllBytes("D:\\zfoo\\protocol\\src\\test\\resources\\ComplexObject.bytes"); + var buffer = ByteBuffer.ValueOf(); + buffer.WriteBytes(complexObjectBytes); + var packet = ProtocolManager.Read(buffer); + + var newBuffer = ByteBuffer.ValueOf(); + ProtocolManager.Write(newBuffer, packet); + var bytes = newBuffer.ToBytes(); + + // set和map是无序的,所以有的时候输入和输出的字节流有可能不一致,但是长度一定是一致的 + AssertEquals(complexObjectBytes, bytes); + } + + + [Test] + public void ByteBufferTest() + { + byteTest(); + bytesTest(); + shortTest(); + intTest(); + longTest(); + floatTest(); + doubleTest(); + charTest(); + stringTest(); + } + + + public void byteTest() + { + byte value = 9; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteByte(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + byte readValue = readerByteBuffer.ReadByte(); + AssertEquals(value, readValue); + } + + public void bytesTest() + { + var value = new byte[] {1, 2, 3}; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteBytes(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + var readValue = readerByteBuffer.ReadBytes(3); + AssertEquals(value, readValue); + } + + public void shortTest() + { + short value = 9999; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteShort(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + short readValue = readerByteBuffer.ReadShort(); + AssertEquals(value, readValue); + } + + public void intTest() + { + int value = 99999999; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteInt(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + int readValue = readerByteBuffer.ReadInt(); + AssertEquals(value, readValue); + } + + public void longTest() + { + long value = 9999999999999999L; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteLong(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + long readValue = readerByteBuffer.ReadLong(); + AssertEquals(value, readValue); + } + + public void floatTest() + { + float value = 999999.56F; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteFloat(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + float readValue = readerByteBuffer.ReadFloat(); + AssertEquals(value, readValue); + } + + public void doubleTest() + { + double value = 999999.56; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteDouble(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + double readValue = readerByteBuffer.ReadDouble(); + AssertEquals(value, readValue); + } + + public void charTest() + { + char value = 'a'; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteChar(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + char readValue = readerByteBuffer.ReadChar(); + AssertEquals(value, readValue); + } + + public void stringTest() + { + string value = "aaa"; + ByteBuffer writerByteBuffer = ByteBuffer.ValueOf(); + writerByteBuffer.WriteString(value); + byte[] bytes = writerByteBuffer.ToBytes(); + + ByteBuffer readerByteBuffer = ByteBuffer.ValueOf(); + readerByteBuffer.WriteBytes(bytes); + string readValue = readerByteBuffer.ReadString(); + AssertEquals(value, readValue); + } + + public static void AssertEquals(object a, object b) + { + if (a.Equals(b)) + { + return; + } + + throw new Exception("a is not equals b"); + } + + public static void AssertEquals(T[] a, T[] b) + { + if (a == b) + { + return; + } + + + if (a != null && b != null && a.Length == b.Length) + { + for (var i = 0; i < a.Length; i++) + { + AssertEquals(a[i], b[i]); + } + + return; + } + + throw new Exception("a is not equals b"); + } + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/jsTest/.eslintrc.js b/protocol/src/test/resources/jsTest/.eslintrc.js new file mode 100644 index 00000000..36004518 --- /dev/null +++ b/protocol/src/test/resources/jsTest/.eslintrc.js @@ -0,0 +1,5 @@ +module.exports = { + env: { + jest: true + } +}; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/ProtocolManager.js b/protocol/src/test/resources/jsTest/jsProtocol/ProtocolManager.js new file mode 100644 index 00000000..c0a7f1c9 --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/ProtocolManager.js @@ -0,0 +1,42 @@ +import ObjectA from './packet/ObjectA.js'; +import ObjectB from './packet/ObjectB.js'; +import ComplexObject from './packet/ComplexObject.js'; +import NormalObject from './packet/NormalObject.js'; +import SimpleObject from './packet/SimpleObject.js'; + + +const protocols = new Map(); + +const ProtocolManager = {}; + +ProtocolManager.getProtocol = function getProtocol(protocolId) { + const protocol = protocols.get(protocolId); + if (protocol === null) { + throw new Error('[protocolId:' + protocolId + ']协议不存在'); + } + return protocol; +}; + +ProtocolManager.write = function write(byteBuffer, packet) { + const protocolId = packet.protocolId(); + byteBuffer.writeShort(protocolId); + const protocol = ProtocolManager.getProtocol(protocolId); + protocol.writeObject(byteBuffer, packet); +}; + +ProtocolManager.read = function read(byteBuffer) { + const protocolId = byteBuffer.readShort(); + const protocol = ProtocolManager.getProtocol(protocolId); + const packet = protocol.readObject(byteBuffer); + return packet; +}; + +ProtocolManager.initProtocol = function initProtocol() { + protocols.set(1116, ObjectA); + protocols.set(1117, ObjectB); + protocols.set(1160, ComplexObject); + protocols.set(1161, NormalObject); + protocols.set(1163, SimpleObject); +}; + +export default ProtocolManager; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/buffer/ByteBuffer.js b/protocol/src/test/resources/jsTest/jsProtocol/buffer/ByteBuffer.js new file mode 100644 index 00000000..2ed62166 --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/buffer/ByteBuffer.js @@ -0,0 +1,340 @@ +import {readInt64, writeInt64} from './longbits.js'; + +const initSize = 128; +const maxSize = 655537; + +const maxShort = 32767; +const minShort = -32768; + +const maxInt = 2147483647; +const minInt = -2147483648; + +// UTF-8编码与解码 +// const encoder = new TextEncoder('utf-8'); +// const decoder = new TextDecoder('utf-8'); + +// nodejs的测试环境需要用以下方式特殊处理 +const util = require('util'); +const encoder = new util.TextEncoder('utf-8'); +const decoder = new util.TextDecoder('utf-8'); + +// 在js中long可以支持的最大值 +// const maxLong = 9007199254740992; +// const minLong = -9007199254740992; + +const copy = function copy(original, newLength) { + if (original.byteLength > newLength) { + throw new Error('newLength is too small'); + } + const dst = new ArrayBuffer(newLength); + new Uint8Array(dst).set(new Uint8Array(original)); + return dst; +}; + +function encodeZigzagInt(n) { + // 有效位左移一位+符号位右移31位 + return (n << 1) ^ (n >> 31); +} + +function decodeZigzagInt(n) { + return (n >>> 1) ^ -(n & 1); +} + + +const ByteBuffer = function () { + this.writeOffset = 0; + this.readOffset = 0; + this.buffer = new ArrayBuffer(initSize); + this.bufferView = new DataView(this.buffer, 0, this.buffer.byteLength); + + this.setWriteOffset = function (writeOffset) { + if (writeOffset > this.buffer.byteLength) { + throw new Error('index out of bounds exception: readerIndex: ' + this.readOffset + + ', writerIndex: ' + this.writeOffset + + '(expected: 0 <= readerIndex <= writerIndex <= capacity:' + this.buffer.byteLength); + } + this.writeOffset = writeOffset; + }; + + this.setReadOffset = function (readOffset) { + if (readOffset > this.writeOffset) { + throw new Error('index out of bounds exception: readerIndex: ' + this.readOffset + + ', writerIndex: ' + this.writeOffset + + '(expected: 0 <= readerIndex <= writerIndex <= capacity:' + this.buffer.byteLength); + } + this.readOffset = readOffset; + }; + + this.getCapacity = function () { + return this.buffer.byteLength - this.writeOffset; + }; + + this.ensureCapacity = function (minCapacity) { + while (minCapacity - this.getCapacity() > 0) { + const newSize = this.buffer.byteLength * 2; + if (newSize > maxSize) { + throw new Error('out of memory error'); + } + this.buffer = copy(this.buffer, newSize); + this.bufferView = new DataView(this.buffer, 0, this.buffer.byteLength); + } + }; + + this.writeBoolean = function (value) { + if (!(value === true || value === false)) { + throw new Error('value must be true of false'); + } + this.ensureCapacity(1); + if (value === true) { + this.bufferView.setInt8(this.writeOffset, 1); + } else { + this.bufferView.setInt8(this.writeOffset, 0); + } + this.writeOffset++; + }; + + this.readBoolean = function () { + const value = this.bufferView.getInt8(this.readOffset); + this.readOffset++; + return (value === 1); + }; + + this.writeBytes = function (byteArray) { + const length = byteArray.byteLength; + this.ensureCapacity(length); + new Uint8Array(this.buffer).set(new Uint8Array(byteArray), this.writeOffset); + this.writeOffset += length; + }; + + this.writeByte = function (value) { + this.ensureCapacity(1); + this.bufferView.setInt8(this.writeOffset, value); + this.writeOffset++; + }; + + this.readByte = function () { + const value = this.bufferView.getInt8(this.readOffset); + this.readOffset++; + return value; + }; + + this.writeShort = function (value) { + if (!(minShort <= value && value <= maxShort)) { + throw new Error('value must range between minShort:-32768 and maxShort:32767'); + } + this.ensureCapacity(2); + this.bufferView.setInt16(this.writeOffset, value); + this.writeOffset += 2; + }; + + this.readShort = function () { + const value = this.bufferView.getInt16(this.readOffset); + this.readOffset += 2; + return value; + }; + + this.writeRawInt = function (value) { + if (!(minInt <= value && value <= maxInt)) { + throw new Error('value must range between minInt:-2147483648 and maxInt:2147483647'); + } + this.ensureCapacity(4); + this.bufferView.setInt32(this.writeOffset, value); + this.writeOffset += 4; + }; + + this.readRawInt = function () { + const value = this.bufferView.getInt32(this.readOffset); + this.readOffset += 4; + return value; + }; + + this.writeInt = function (value) { + if (!(minInt <= value && value <= maxInt)) { + throw new Error('value must range between minInt:-2147483648 and maxInt:2147483647'); + } + this.ensureCapacity(5); + + value = encodeZigzagInt(value); + + if (value >>> 7 === 0) { + this.writeByte(value); + return; + } + + if (value >>> 14 === 0) { + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7)); + return; + } + + if (value >>> 21 === 0) { + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7 | 0x80)); + this.writeByte(value >>> 14); + return; + } + + if (value >>> 28 === 0) { + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7 | 0x80)); + this.writeByte((value >>> 14 | 0x80)); + this.writeByte(value >>> 21); + return; + } + + this.writeByte((value & 0x7F) | 0x80); + this.writeByte((value >>> 7 | 0x80)); + this.writeByte((value >>> 14 | 0x80)); + this.writeByte((value >>> 21 | 0x80)); + this.writeByte(value >>> 28); + }; + + this.readInt = function () { + let b = this.readByte(); + let value = b & 0x7F; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 7; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 14; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 21; + if ((b & 0x80) !== 0) { + b = this.readByte(); + value |= (b & 0x7F) << 28; + } + } + } + } + + return decodeZigzagInt(value); + }; + + this.writeLong = function (value) { + if (value === null || value === undefined) { + throw new Error('value must not be null'); + } + this.ensureCapacity(9); + + writeInt64(this, value); + }; + + this.readLong = function () { + const buffer = new ArrayBuffer(9); + const bufferView = new DataView(buffer, 0, buffer.byteLength); + + let count = 0; + let b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + if ((b & 0x80) !== 0) { + b = this.readByte(); + bufferView.setUint8(count++, b); + } + } + } + } + } + } + } + } + return readInt64(new Uint8Array(buffer.slice(0, count))).toString(); + }; + + this.writeFloat = function (value) { + if (value === null || value === undefined) { + throw new Error('value must not be null'); + } + this.ensureCapacity(4); + this.bufferView.setFloat32(this.writeOffset, value); + this.writeOffset += 4; + }; + + this.readFloat = function () { + const value = this.bufferView.getFloat32(this.readOffset); + this.readOffset += 4; + return value; + }; + + this.writeDouble = function (value) { + if (value === null || value === undefined) { + throw new Error('value must not be null'); + } + this.ensureCapacity(8); + this.bufferView.setFloat64(this.writeOffset, value); + this.writeOffset += 8; + }; + + this.readDouble = function () { + const value = this.bufferView.getFloat64(this.readOffset); + this.readOffset += 8; + return value; + }; + + this.writeChar = function (value) { + if (value === null || value === undefined || value.length === 0) { + this.writeInt(0); + return; + } + this.writeString(value.charAt(0)); + }; + + this.readChar = function () { + return this.readString(); + }; + + this.writeString = function (value) { + if (value === null || value === undefined || value.trim().length === 0) { + this.writeInt(0); + return; + } + + const uint8Array = encoder.encode(value); + + this.ensureCapacity(5 + uint8Array.length); + + this.writeInt(uint8Array.length); + uint8Array.forEach((value) => this.writeByte(value)); + }; + + this.readString = function () { + const length = this.readInt(); + if (length <= 0) { + return ''; + } + const uint8Array = new Uint8Array(this.buffer.slice(this.readOffset, this.readOffset + length)); + const value = decoder.decode(uint8Array); + this.readOffset += length; + return value; + }; + + this.toBytes = function () { + const result = new ArrayBuffer(this.writeOffset); + new Uint8Array(result).set(new Uint8Array(this.buffer.slice(0, this.writeOffset))); + return result; + }; +}; + +export default ByteBuffer; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/buffer/long.js b/protocol/src/test/resources/jsTest/jsProtocol/buffer/long.js new file mode 100644 index 00000000..31a8e6d7 --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/buffer/long.js @@ -0,0 +1,1325 @@ +/* eslint-disable */ +// from https://github.com/dcodeIO/long.js/blob/master/src/long.js +module.exports = Long; + +/** + * wasm optimizations, to do native i64 multiplication and divide + */ +var wasm = null; + +try { + wasm = new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 13, 2, 96, 0, 1, 127, 96, 4, 127, 127, 127, 127, 1, 127, 3, 7, 6, 0, 1, 1, 1, 1, 1, 6, 6, 1, 127, 1, 65, 0, 11, 7, 50, 6, 3, 109, 117, 108, 0, 1, 5, 100, 105, 118, 95, 115, 0, 2, 5, 100, 105, 118, 95, 117, 0, 3, 5, 114, 101, 109, 95, 115, 0, 4, 5, 114, 101, 109, 95, 117, 0, 5, 8, 103, 101, 116, 95, 104, 105, 103, 104, 0, 0, 10, 191, 1, 6, 4, 0, 35, 0, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 126, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 127, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 128, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 129, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11, 36, 1, 1, 126, 32, 0, 173, 32, 1, 173, 66, 32, 134, 132, 32, 2, 173, 32, 3, 173, 66, 32, 134, 132, 130, 34, 4, 66, 32, 135, 167, 36, 0, 32, 4, 167, 11 + ])), {}).exports; +} catch (e) { + // no wasm support :( +} + +/** + * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers. + * See the from* functions below for more convenient ways of constructing Longs. + * @exports Long + * @class A Long class for representing a 64 bit two's-complement integer value. + * @param {number} low The low (signed) 32 bits of the long + * @param {number} high The high (signed) 32 bits of the long + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @constructor + */ +function Long(low, high, unsigned) { + + /** + * The low 32 bits as a signed value. + * @type {number} + */ + this.low = low | 0; + + /** + * The high 32 bits as a signed value. + * @type {number} + */ + this.high = high | 0; + + /** + * Whether unsigned or not. + * @type {boolean} + */ + this.unsigned = !!unsigned; +} + +// The internal representation of a long is the two given signed, 32-bit values. +// We use 32-bit pieces because these are the size of integers on which +// Javascript performs bit-operations. For operations like addition and +// multiplication, we split each number into 16 bit pieces, which can easily be +// multiplied within Javascript's floating-point representation without overflow +// or change in sign. +// +// In the algorithms below, we frequently reduce the negative case to the +// positive case by negating the input(s) and then post-processing the result. +// Note that we must ALWAYS check specially whether those values are MIN_VALUE +// (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as +// a positive number, it overflows back into a negative). Not handling this +// case would often result in infinite recursion. +// +// Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from* +// methods on which they depend. + +/** + * An indicator used to reliably determine if an object is a Long or not. + * @type {boolean} + * @const + * @private + */ +Long.prototype.__isLong__; + +Object.defineProperty(Long.prototype, "__isLong__", {value: true}); + +/** + * @function + * @param {*} obj Object + * @returns {boolean} + * @inner + */ +function isLong(obj) { + return (obj && obj["__isLong__"]) === true; +} + +/** + * Tests if the specified object is a Long. + * @function + * @param {*} obj Object + * @returns {boolean} + */ +Long.isLong = isLong; + +/** + * A cache of the Long representations of small integer values. + * @type {!Object} + * @inner + */ +var INT_CACHE = {}; + +/** + * A cache of the Long representations of small unsigned integer values. + * @type {!Object} + * @inner + */ +var UINT_CACHE = {}; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromInt(value, unsigned) { + var obj, cachedObj, cache; + if (unsigned) { + value >>>= 0; + if (cache = (0 <= value && value < 256)) { + cachedObj = UINT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true); + if (cache) + UINT_CACHE[value] = obj; + return obj; + } else { + value |= 0; + if (cache = (-128 <= value && value < 128)) { + cachedObj = INT_CACHE[value]; + if (cachedObj) + return cachedObj; + } + obj = fromBits(value, value < 0 ? -1 : 0, false); + if (cache) + INT_CACHE[value] = obj; + return obj; + } +} + +/** + * Returns a Long representing the given 32 bit integer value. + * @function + * @param {number} value The 32 bit integer in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromInt = fromInt; + +/** + * @param {number} value + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromNumber(value, unsigned) { + if (isNaN(value)) + return unsigned ? UZERO : ZERO; + if (unsigned) { + if (value < 0) + return UZERO; + if (value >= TWO_PWR_64_DBL) + return MAX_UNSIGNED_VALUE; + } else { + if (value <= -TWO_PWR_63_DBL) + return MIN_VALUE; + if (value + 1 >= TWO_PWR_63_DBL) + return MAX_VALUE; + } + if (value < 0) + return fromNumber(-value, unsigned).neg(); + return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned); +} + +/** + * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned. + * @function + * @param {number} value The number in question + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromNumber = fromNumber; + +/** + * @param {number} lowBits + * @param {number} highBits + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromBits(lowBits, highBits, unsigned) { + return new Long(lowBits, highBits, unsigned); +} + +/** + * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is + * assumed to use 32 bits. + * @function + * @param {number} lowBits The low 32 bits + * @param {number} highBits The high 32 bits + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} The corresponding Long value + */ +Long.fromBits = fromBits; + +/** + * @function + * @param {number} base + * @param {number} exponent + * @returns {number} + * @inner + */ +var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4) + +/** + * @param {string} str + * @param {(boolean|number)=} unsigned + * @param {number=} radix + * @returns {!Long} + * @inner + */ +function fromString(str, unsigned, radix) { + if (str.length === 0) + throw Error('empty string'); + if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity") + return ZERO; + if (typeof unsigned === 'number') { + // For goog.math.long compatibility + radix = unsigned, + unsigned = false; + } else { + unsigned = !!unsigned; + } + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + + var p; + if ((p = str.indexOf('-')) > 0) + throw Error('interior hyphen'); + else if (p === 0) { + return fromString(str.substring(1), unsigned, radix).neg(); + } + + // Do several (8) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 8)); + + var result = ZERO; + for (var i = 0; i < str.length; i += 8) { + var size = Math.min(8, str.length - i), + value = parseInt(str.substring(i, i + size), radix); + if (size < 8) { + var power = fromNumber(pow_dbl(radix, size)); + result = result.mul(power).add(fromNumber(value)); + } else { + result = result.mul(radixToPower); + result = result.add(fromNumber(value)); + } + } + result.unsigned = unsigned; + return result; +} + +/** + * Returns a Long representation of the given string, written using the specified radix. + * @function + * @param {string} str The textual representation of the Long + * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to signed + * @param {number=} radix The radix in which the text is written (2-36), defaults to 10 + * @returns {!Long} The corresponding Long value + */ +Long.fromString = fromString; + +/** + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val + * @param {boolean=} unsigned + * @returns {!Long} + * @inner + */ +function fromValue(val, unsigned) { + if (typeof val === 'number') + return fromNumber(val, unsigned); + if (typeof val === 'string') + return fromString(val, unsigned); + // Throws for non-objects, converts non-instanceof Long: + return fromBits(val.low, val.high, typeof unsigned === 'boolean' ? unsigned : val.unsigned); +} + +/** + * Converts the specified value to a Long using the appropriate from* function for its type. + * @function + * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {!Long} + */ +Long.fromValue = fromValue; + +// NOTE: the compiler should inline these constant values below and then remove these variables, so there should be +// no runtime penalty for these. + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_16_DBL = 1 << 16; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_24_DBL = 1 << 24; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL; + +/** + * @type {number} + * @const + * @inner + */ +var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2; + +/** + * @type {!Long} + * @const + * @inner + */ +var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL); + +/** + * @type {!Long} + * @inner + */ +var ZERO = fromInt(0); + +/** + * Signed zero. + * @type {!Long} + */ +Long.ZERO = ZERO; + +/** + * @type {!Long} + * @inner + */ +var UZERO = fromInt(0, true); + +/** + * Unsigned zero. + * @type {!Long} + */ +Long.UZERO = UZERO; + +/** + * @type {!Long} + * @inner + */ +var ONE = fromInt(1); + +/** + * Signed one. + * @type {!Long} + */ +Long.ONE = ONE; + +/** + * @type {!Long} + * @inner + */ +var UONE = fromInt(1, true); + +/** + * Unsigned one. + * @type {!Long} + */ +Long.UONE = UONE; + +/** + * @type {!Long} + * @inner + */ +var NEG_ONE = fromInt(-1); + +/** + * Signed negative one. + * @type {!Long} + */ +Long.NEG_ONE = NEG_ONE; + +/** + * @type {!Long} + * @inner + */ +var MAX_VALUE = fromBits(0xFFFFFFFF | 0, 0x7FFFFFFF | 0, false); + +/** + * Maximum signed value. + * @type {!Long} + */ +Long.MAX_VALUE = MAX_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF | 0, 0xFFFFFFFF | 0, true); + +/** + * Maximum unsigned value. + * @type {!Long} + */ +Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE; + +/** + * @type {!Long} + * @inner + */ +var MIN_VALUE = fromBits(0, 0x80000000 | 0, false); + +/** + * Minimum signed value. + * @type {!Long} + */ +Long.MIN_VALUE = MIN_VALUE; + +/** + * @alias Long.prototype + * @inner + */ +var LongPrototype = Long.prototype; + +/** + * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. + * @returns {number} + */ +LongPrototype.toInt = function toInt() { + return this.unsigned ? this.low >>> 0 : this.low; +}; + +/** + * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). + * @returns {number} + */ +LongPrototype.toNumber = function toNumber() { + if (this.unsigned) + return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0); + return this.high * TWO_PWR_32_DBL + (this.low >>> 0); +}; + +/** + * Converts the Long to a string written in the specified radix. + * @param {number=} radix Radix (2-36), defaults to 10 + * @returns {string} + * @override + * @throws {RangeError} If `radix` is out of range + */ +LongPrototype.toString = function toString(radix) { + radix = radix || 10; + if (radix < 2 || 36 < radix) + throw RangeError('radix'); + if (this.isZero()) + return '0'; + if (this.isNegative()) { // Unsigned Longs are never negative + if (this.eq(MIN_VALUE)) { + // We need to change the Long value before it can be negated, so we remove + // the bottom-most digit in this base and then recurse to do the rest. + var radixLong = fromNumber(radix), + div = this.div(radixLong), + rem1 = div.mul(radixLong).sub(this); + return div.toString(radix) + rem1.toInt().toString(radix); + } else + return '-' + this.neg().toString(radix); + } + + // Do several (6) digits each time through the loop, so as to + // minimize the calls to the very expensive emulated div. + var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned), + rem = this; + var result = ''; + while (true) { + var remDiv = rem.div(radixToPower), + intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0, + digits = intval.toString(radix); + rem = remDiv; + if (rem.isZero()) + return digits + result; + else { + while (digits.length < 6) + digits = '0' + digits; + result = '' + digits + result; + } + } +}; + +/** + * Gets the high 32 bits as a signed integer. + * @returns {number} Signed high bits + */ +LongPrototype.getHighBits = function getHighBits() { + return this.high; +}; + +/** + * Gets the high 32 bits as an unsigned integer. + * @returns {number} Unsigned high bits + */ +LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() { + return this.high >>> 0; +}; + +/** + * Gets the low 32 bits as a signed integer. + * @returns {number} Signed low bits + */ +LongPrototype.getLowBits = function getLowBits() { + return this.low; +}; + +/** + * Gets the low 32 bits as an unsigned integer. + * @returns {number} Unsigned low bits + */ +LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() { + return this.low >>> 0; +}; + +/** + * Gets the number of bits needed to represent the absolute value of this Long. + * @returns {number} + */ +LongPrototype.getNumBitsAbs = function getNumBitsAbs() { + if (this.isNegative()) // Unsigned Longs are never negative + return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs(); + var val = this.high != 0 ? this.high : this.low; + for (var bit = 31; bit > 0; bit--) + if ((val & (1 << bit)) != 0) + break; + return this.high != 0 ? bit + 33 : bit + 1; +}; + +/** + * Tests if this Long's value equals zero. + * @returns {boolean} + */ +LongPrototype.isZero = function isZero() { + return this.high === 0 && this.low === 0; +}; + +/** + * Tests if this Long's value equals zero. This is an alias of {@link Long#isZero}. + * @returns {boolean} + */ +LongPrototype.eqz = LongPrototype.isZero; + +/** + * Tests if this Long's value is negative. + * @returns {boolean} + */ +LongPrototype.isNegative = function isNegative() { + return !this.unsigned && this.high < 0; +}; + +/** + * Tests if this Long's value is positive. + * @returns {boolean} + */ +LongPrototype.isPositive = function isPositive() { + return this.unsigned || this.high >= 0; +}; + +/** + * Tests if this Long's value is odd. + * @returns {boolean} + */ +LongPrototype.isOdd = function isOdd() { + return (this.low & 1) === 1; +}; + +/** + * Tests if this Long's value is even. + * @returns {boolean} + */ +LongPrototype.isEven = function isEven() { + return (this.low & 1) === 0; +}; + +/** + * Tests if this Long's value equals the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.equals = function equals(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1) + return false; + return this.high === other.high && this.low === other.low; +}; + +/** + * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.eq = LongPrototype.equals; + +/** + * Tests if this Long's value differs from the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.notEquals = function notEquals(other) { + return !this.eq(/* validates */ other); +}; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.neq = LongPrototype.notEquals; + +/** + * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ne = LongPrototype.notEquals; + +/** + * Tests if this Long's value is less than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThan = function lessThan(other) { + return this.comp(/* validates */ other) < 0; +}; + +/** + * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lt = LongPrototype.lessThan; + +/** + * Tests if this Long's value is less than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) { + return this.comp(/* validates */ other) <= 0; +}; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.lte = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.le = LongPrototype.lessThanOrEqual; + +/** + * Tests if this Long's value is greater than the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThan = function greaterThan(other) { + return this.comp(/* validates */ other) > 0; +}; + +/** + * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gt = LongPrototype.greaterThan; + +/** + * Tests if this Long's value is greater than or equal the specified's. + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) { + return this.comp(/* validates */ other) >= 0; +}; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.gte = LongPrototype.greaterThanOrEqual; + +/** + * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}. + * @function + * @param {!Long|number|string} other Other value + * @returns {boolean} + */ +LongPrototype.ge = LongPrototype.greaterThanOrEqual; + +/** + * Compares this Long's value with the specified's. + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.compare = function compare(other) { + if (!isLong(other)) + other = fromValue(other); + if (this.eq(other)) + return 0; + var thisNeg = this.isNegative(), + otherNeg = other.isNegative(); + if (thisNeg && !otherNeg) + return -1; + if (!thisNeg && otherNeg) + return 1; + // At this point the sign bits are the same + if (!this.unsigned) + return this.sub(other).isNegative() ? -1 : 1; + // Both are positive if at least one is unsigned + return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1; +}; + +/** + * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}. + * @function + * @param {!Long|number|string} other Other value + * @returns {number} 0 if they are the same, 1 if the this is greater and -1 + * if the given one is greater + */ +LongPrototype.comp = LongPrototype.compare; + +/** + * Negates this Long's value. + * @returns {!Long} Negated Long + */ +LongPrototype.negate = function negate() { + if (!this.unsigned && this.eq(MIN_VALUE)) + return MIN_VALUE; + return this.not().add(ONE); +}; + +/** + * Negates this Long's value. This is an alias of {@link Long#negate}. + * @function + * @returns {!Long} Negated Long + */ +LongPrototype.neg = LongPrototype.negate; + +/** + * Returns the sum of this and the specified Long. + * @param {!Long|number|string} addend Addend + * @returns {!Long} Sum + */ +LongPrototype.add = function add(addend) { + if (!isLong(addend)) + addend = fromValue(addend); + + // Divide each number into 4 chunks of 16 bits, and then sum the chunks. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = addend.high >>> 16; + var b32 = addend.high & 0xFFFF; + var b16 = addend.low >>> 16; + var b00 = addend.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 + b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 + b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 + b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 + b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the difference of this and the specified Long. + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.subtract = function subtract(subtrahend) { + if (!isLong(subtrahend)) + subtrahend = fromValue(subtrahend); + return this.add(subtrahend.neg()); +}; + +/** + * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}. + * @function + * @param {!Long|number|string} subtrahend Subtrahend + * @returns {!Long} Difference + */ +LongPrototype.sub = LongPrototype.subtract; + +/** + * Returns the product of this and the specified Long. + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.multiply = function multiply(multiplier) { + if (this.isZero()) + return ZERO; + if (!isLong(multiplier)) + multiplier = fromValue(multiplier); + + // use wasm support if present + if (wasm) { + var low = wasm.mul(this.low, + this.high, + multiplier.low, + multiplier.high); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (multiplier.isZero()) + return ZERO; + if (this.eq(MIN_VALUE)) + return multiplier.isOdd() ? MIN_VALUE : ZERO; + if (multiplier.eq(MIN_VALUE)) + return this.isOdd() ? MIN_VALUE : ZERO; + + if (this.isNegative()) { + if (multiplier.isNegative()) + return this.neg().mul(multiplier.neg()); + else + return this.neg().mul(multiplier).neg(); + } else if (multiplier.isNegative()) + return this.mul(multiplier.neg()).neg(); + + // If both longs are small, use float multiplication + if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24)) + return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned); + + // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products. + // We can skip products that would overflow. + + var a48 = this.high >>> 16; + var a32 = this.high & 0xFFFF; + var a16 = this.low >>> 16; + var a00 = this.low & 0xFFFF; + + var b48 = multiplier.high >>> 16; + var b32 = multiplier.high & 0xFFFF; + var b16 = multiplier.low >>> 16; + var b00 = multiplier.low & 0xFFFF; + + var c48 = 0, c32 = 0, c16 = 0, c00 = 0; + c00 += a00 * b00; + c16 += c00 >>> 16; + c00 &= 0xFFFF; + c16 += a16 * b00; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c16 += a00 * b16; + c32 += c16 >>> 16; + c16 &= 0xFFFF; + c32 += a32 * b00; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a16 * b16; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c32 += a00 * b32; + c48 += c32 >>> 16; + c32 &= 0xFFFF; + c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48; + c48 &= 0xFFFF; + return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned); +}; + +/** + * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}. + * @function + * @param {!Long|number|string} multiplier Multiplier + * @returns {!Long} Product + */ +LongPrototype.mul = LongPrototype.multiply; + +/** + * Returns this Long divided by the specified. The result is signed if this Long is signed or + * unsigned if this Long is unsigned. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.divide = function divide(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + if (divisor.isZero()) + throw Error('division by zero'); + + // use wasm support if present + if (wasm) { + // guard against signed division overflow: the largest + // negative number / -1 would be 1 larger than the largest + // positive number, due to two's complement. + if (!this.unsigned && + this.high === -0x80000000 && + divisor.low === -1 && divisor.high === -1) { + // be consistent with non-wasm code path + return this; + } + var low = (this.unsigned ? wasm.div_u : wasm.div_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + if (this.isZero()) + return this.unsigned ? UZERO : ZERO; + var approx, rem, res; + if (!this.unsigned) { + // This section is only relevant for signed longs and is derived from the + // closure library as a whole. + if (this.eq(MIN_VALUE)) { + if (divisor.eq(ONE) || divisor.eq(NEG_ONE)) + return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE + else if (divisor.eq(MIN_VALUE)) + return ONE; + else { + // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|. + var halfThis = this.shr(1); + approx = halfThis.div(divisor).shl(1); + if (approx.eq(ZERO)) { + return divisor.isNegative() ? ONE : NEG_ONE; + } else { + rem = this.sub(divisor.mul(approx)); + res = approx.add(rem.div(divisor)); + return res; + } + } + } else if (divisor.eq(MIN_VALUE)) + return this.unsigned ? UZERO : ZERO; + if (this.isNegative()) { + if (divisor.isNegative()) + return this.neg().div(divisor.neg()); + return this.neg().div(divisor).neg(); + } else if (divisor.isNegative()) + return this.div(divisor.neg()).neg(); + res = ZERO; + } else { + // The algorithm below has not been made for unsigned longs. It's therefore + // required to take special care of the MSB prior to running it. + if (!divisor.unsigned) + divisor = divisor.toUnsigned(); + if (divisor.gt(this)) + return UZERO; + if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true + return UONE; + res = UZERO; + } + + // Repeat the following until the remainder is less than other: find a + // floating-point that approximates remainder / other *from below*, add this + // into the result, and subtract it from the remainder. It is critical that + // the approximate value is less than or equal to the real value so that the + // remainder never becomes negative. + rem = this; + while (rem.gte(divisor)) { + // Approximate the result of division. This may be a little greater or + // smaller than the actual value. + approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber())); + + // We will tweak the approximate result by changing it in the 48-th digit or + // the smallest non-fractional digit, whichever is larger. + var log2 = Math.ceil(Math.log(approx) / Math.LN2), + delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48), + + // Decrease the approximation until it is smaller than the remainder. Note + // that if it is too large, the product overflows and is negative. + approxRes = fromNumber(approx), + approxRem = approxRes.mul(divisor); + while (approxRem.isNegative() || approxRem.gt(rem)) { + approx -= delta; + approxRes = fromNumber(approx, this.unsigned); + approxRem = approxRes.mul(divisor); + } + + // We know the answer can't be zero... and actually, zero would cause + // infinite recursion since we would make no progress. + if (approxRes.isZero()) + approxRes = ONE; + + res = res.add(approxRes); + rem = rem.sub(approxRem); + } + return res; +}; + +/** + * Returns this Long divided by the specified. This is an alias of {@link Long#divide}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Quotient + */ +LongPrototype.div = LongPrototype.divide; + +/** + * Returns this Long modulo the specified. + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.modulo = function modulo(divisor) { + if (!isLong(divisor)) + divisor = fromValue(divisor); + + // use wasm support if present + if (wasm) { + var low = (this.unsigned ? wasm.rem_u : wasm.rem_s)( + this.low, + this.high, + divisor.low, + divisor.high + ); + return fromBits(low, wasm.get_high(), this.unsigned); + } + + return this.sub(this.div(divisor).mul(divisor)); +}; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.mod = LongPrototype.modulo; + +/** + * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}. + * @function + * @param {!Long|number|string} divisor Divisor + * @returns {!Long} Remainder + */ +LongPrototype.rem = LongPrototype.modulo; + +/** + * Returns the bitwise NOT of this Long. + * @returns {!Long} + */ +LongPrototype.not = function not() { + return fromBits(~this.low, ~this.high, this.unsigned); +}; + +/** + * Returns the bitwise AND of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.and = function and(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low & other.low, this.high & other.high, this.unsigned); +}; + +/** + * Returns the bitwise OR of this Long and the specified. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.or = function or(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low | other.low, this.high | other.high, this.unsigned); +}; + +/** + * Returns the bitwise XOR of this Long and the given one. + * @param {!Long|number|string} other Other Long + * @returns {!Long} + */ +LongPrototype.xor = function xor(other) { + if (!isLong(other)) + other = fromValue(other); + return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftLeft = function shiftLeft(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned); + else + return fromBits(0, this.low << (numBits - 32), this.unsigned); +}; + +/** + * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shl = LongPrototype.shiftLeft; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRight = function shiftRight(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + if ((numBits &= 63) === 0) + return this; + else if (numBits < 32) + return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned); + else + return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned); +}; + +/** + * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr = LongPrototype.shiftRight; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) { + if (isLong(numBits)) + numBits = numBits.toInt(); + numBits &= 63; + if (numBits === 0) + return this; + else { + var high = this.high; + if (numBits < 32) { + var low = this.low; + return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned); + } else if (numBits === 32) + return fromBits(high, 0, this.unsigned); + else + return fromBits(high >>> (numBits - 32), 0, this.unsigned); + } +}; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shru = LongPrototype.shiftRightUnsigned; + +/** + * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}. + * @function + * @param {number|!Long} numBits Number of bits + * @returns {!Long} Shifted Long + */ +LongPrototype.shr_u = LongPrototype.shiftRightUnsigned; + +/** + * Converts this Long to signed. + * @returns {!Long} Signed long + */ +LongPrototype.toSigned = function toSigned() { + if (!this.unsigned) + return this; + return fromBits(this.low, this.high, false); +}; + +/** + * Converts this Long to unsigned. + * @returns {!Long} Unsigned long + */ +LongPrototype.toUnsigned = function toUnsigned() { + if (this.unsigned) + return this; + return fromBits(this.low, this.high, true); +}; + +/** + * Converts this Long to its byte representation. + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {!Array.} Byte representation + */ +LongPrototype.toBytes = function toBytes(le) { + return le ? this.toBytesLE() : this.toBytesBE(); +}; + +/** + * Converts this Long to its little endian byte representation. + * @returns {!Array.} Little endian byte representation + */ +LongPrototype.toBytesLE = function toBytesLE() { + var hi = this.high, + lo = this.low; + return [ + lo & 0xff, + lo >>> 8 & 0xff, + lo >>> 16 & 0xff, + lo >>> 24, + hi & 0xff, + hi >>> 8 & 0xff, + hi >>> 16 & 0xff, + hi >>> 24 + ]; +}; + +/** + * Converts this Long to its big endian byte representation. + * @returns {!Array.} Big endian byte representation + */ +LongPrototype.toBytesBE = function toBytesBE() { + var hi = this.high, + lo = this.low; + return [ + hi >>> 24, + hi >>> 16 & 0xff, + hi >>> 8 & 0xff, + hi & 0xff, + lo >>> 24, + lo >>> 16 & 0xff, + lo >>> 8 & 0xff, + lo & 0xff + ]; +}; + +/** + * Creates a Long from its byte representation. + * @param {!Array.} bytes Byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @param {boolean=} le Whether little or big endian, defaults to big endian + * @returns {Long} The corresponding Long value + */ +Long.fromBytes = function fromBytes(bytes, unsigned, le) { + return le ? Long.fromBytesLE(bytes, unsigned) : Long.fromBytesBE(bytes, unsigned); +}; + +/** + * Creates a Long from its little endian byte representation. + * @param {!Array.} bytes Little endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesLE = function fromBytesLE(bytes, unsigned) { + return new Long( + bytes[0] | + bytes[1] << 8 | + bytes[2] << 16 | + bytes[3] << 24, + bytes[4] | + bytes[5] << 8 | + bytes[6] << 16 | + bytes[7] << 24, + unsigned + ); +}; + +/** + * Creates a Long from its big endian byte representation. + * @param {!Array.} bytes Big endian byte representation + * @param {boolean=} unsigned Whether unsigned or not, defaults to signed + * @returns {Long} The corresponding Long value + */ +Long.fromBytesBE = function fromBytesBE(bytes, unsigned) { + return new Long( + bytes[4] << 24 | + bytes[5] << 16 | + bytes[6] << 8 | + bytes[7], + bytes[0] << 24 | + bytes[1] << 16 | + bytes[2] << 8 | + bytes[3], + unsigned + ); +}; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/buffer/longbits.js b/protocol/src/test/resources/jsTest/jsProtocol/buffer/longbits.js new file mode 100644 index 00000000..62e7893a --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/buffer/longbits.js @@ -0,0 +1,184 @@ +// from protobuf +import Long from './long.js'; + +/** + * Constructs new long bits. + * @classdesc Helper class for working with the low and high bits of a 64 bit value. + * @memberof util + * @constructor + * @param {number} lo Low 32 bits, unsigned + * @param {number} hi High 32 bits, unsigned + */ +function LongBits(lo, hi) { + // note that the casts below are theoretically unnecessary as of today, but older statically + // generated converter code might still call the ctor with signed 32bits. kept for compat. + + /** + * Low bits. + * @type {number} + */ + this.lo = lo >>> 0; + + /** + * High bits. + * @type {number} + */ + this.hi = hi >>> 0; +} + +/** + * Zig-zag encodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzEncode = function zzEncode() { + const mask = this.hi >> 31; + this.hi = ((this.hi << 1 | this.lo >>> 31) ^ mask) >>> 0; + this.lo = (this.lo << 1 ^ mask) >>> 0; + return this; +}; + +/** + * Zig-zag decodes this long bits. + * @returns {util.LongBits} `this` + */ +LongBits.prototype.zzDecode = function zzDecode() { + const mask = -(this.lo & 1); + this.lo = ((this.lo >>> 1 | this.hi << 31) ^ mask) >>> 0; + this.hi = (this.hi >>> 1 ^ mask) >>> 0; + return this; +}; + +/** + * Converts this long bits to a long. + * @param {boolean} [unsigned=false] Whether unsigned or not + * @returns {Long} Long + */ +LongBits.prototype.toLong = function toLong(unsigned) { + return new Long(this.lo | 0, this.hi | 0, Boolean(unsigned)); +}; + +/** + * Zero bits. + * @memberof util.LongBits + * @type {util.LongBits} + */ +const zero = LongBits.zero = new LongBits(0, 0); + +function from(value) { + if (typeof value === 'number') { + return fromNumber(value); + } + if (typeof value === 'string' || value instanceof String) { + value = Long.fromString(value); + } + return value.low || value.high ? new LongBits(value.low >>> 0, value.high >>> 0) : zero; +} + + +/** + * Constructs new long bits from the specified number. + * @param {number} value Value + * @returns {util.LongBits} Instance + */ +function fromNumber(value) { + if (value === 0) { + return zero; + } + const sign = value < 0; + if (sign) { + value = -value; + } + let lo = value >>> 0; + let hi = (value - lo) / 4294967296 >>> 0; + if (sign) { + hi = ~hi >>> 0; + lo = ~lo >>> 0; + if (++lo > 4294967295) { + lo = 0; + if (++hi > 4294967295) { + hi = 0; + } + } + } + return new LongBits(lo, hi); +} + +function writeVarint64(byteBuffer, value) { + let count = 0; + while (value.hi) { + byteBuffer.writeByte(value.lo & 127 | 128); + value.lo = (value.lo >>> 7 | value.hi << 25) >>> 0; + value.hi >>>= 7; + count = count + 7; + } + while (value.lo > 127) { + if (count >= 56) { + byteBuffer.writeByte(value.lo); + return; + } + byteBuffer.writeByte(value.lo & 127 | 128); + value.lo = value.lo >>> 7; + count = count + 7; + } + byteBuffer.writeByte(value.lo); +} + +function readLongVarint(buffer) { + // tends to deopt with local vars for octet etc. + const bits = new LongBits(0, 0); + let i = 0; + const len = buffer.length; + let pos = 0; + if (len - pos > 4) { // fast route (lo) + for (; i < 4; ++i) { + // 1st..4th + bits.lo = (bits.lo | (buffer[pos] & 127) << i * 7) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + } + // 5th + bits.lo = (bits.lo | (buffer[pos] & 127) << 28) >>> 0; + bits.hi = (bits.hi | (buffer[pos] & 127) >> 4) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + i = 0; + } else { + for (; i < 3; ++i) { + // 1st..3th + bits.lo = (bits.lo | (buffer[pos] & 127) << i * 7) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + } + // 4th + bits.lo = (bits.lo | (buffer[pos++] & 127) << i * 7) >>> 0; + return bits; + } + + // 6th..9th + for (; i < 4; ++i) { + // 最后一位直接写入 + if (pos === 8) { + bits.hi = (bits.hi | buffer[pos] << i * 7 + 3) >>> 0; + return bits; + } + bits.hi = (bits.hi | (buffer[pos] & 127) << i * 7 + 3) >>> 0; + if (buffer[pos++] < 128) { + return bits; + } + } + + return bits; +} + + +export function writeInt64(byteBuffer, value) { + const bits = from(value).zzEncode(); + writeVarint64(byteBuffer, bits); +} + +export function readInt64(buffer) { + return readLongVarint(buffer).zzDecode().toLong(false); +} diff --git a/protocol/src/test/resources/jsTest/jsProtocol/packet/ComplexObject.js b/protocol/src/test/resources/jsTest/jsProtocol/packet/ComplexObject.js new file mode 100644 index 00000000..21e1691b --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/packet/ComplexObject.js @@ -0,0 +1,971 @@ +import ProtocolManager from '../ProtocolManager.js'; +// 复杂的对象 +// 包括了各种复杂的结构,数组,List,Set,Map +// +// @author jaysunxiao +// @version 1.0 +// @since 2017 10.14 11:19 +const ComplexObject = function (a, aa, aaa, aaaa, b, bb, bbb, bbbb, c, cc, ccc, cccc, d, dd, ddd, dddd, e, ee, eee, eeee, f, ff, fff, ffff, g, gg, ggg, gggg, h, hh, hhh, hhhh, jj, jjj, kk, kkk, l, ll, lll, llll, lllll, m, mm, mmm, mmmm, mmmmm, s, ss, sss, ssss, sssss) { + // byte类型,最简单的整形 + this.a = a; // byte + // byte的包装类型 + // 优先使用基础类型,包装类型会有装箱拆箱 + this.aa = aa; // java.lang.Byte + // 数组类型 + this.aaa = aaa; // byte[] + this.aaaa = aaaa; // java.lang.Byte[] + this.b = b; // short + this.bb = bb; // java.lang.Short + this.bbb = bbb; // short[] + this.bbbb = bbbb; // java.lang.Short[] + this.c = c; // int + this.cc = cc; // java.lang.Integer + this.ccc = ccc; // int[] + this.cccc = cccc; // java.lang.Integer[] + this.d = d; // long + this.dd = dd; // java.lang.Long + this.ddd = ddd; // long[] + this.dddd = dddd; // java.lang.Long[] + this.e = e; // float + this.ee = ee; // java.lang.Float + this.eee = eee; // float[] + this.eeee = eeee; // java.lang.Float[] + this.f = f; // double + this.ff = ff; // java.lang.Double + this.fff = fff; // double[] + this.ffff = ffff; // java.lang.Double[] + this.g = g; // boolean + this.gg = gg; // java.lang.Boolean + this.ggg = ggg; // boolean[] + this.gggg = gggg; // java.lang.Boolean[] + this.h = h; // char + this.hh = hh; // java.lang.Character + this.hhh = hhh; // char[] + this.hhhh = hhhh; // java.lang.Character[] + this.jj = jj; // java.lang.String + this.jjj = jjj; // java.lang.String[] + this.kk = kk; // com.zfoo.protocol.packet.ObjectA + this.kkk = kkk; // com.zfoo.protocol.packet.ObjectA[] + this.l = l; // java.util.List + this.ll = ll; // java.util.List>> + this.lll = lll; // java.util.List> + this.llll = llll; // java.util.List + this.lllll = lllll; // java.util.List> + this.m = m; // java.util.Map + this.mm = mm; // java.util.Map + this.mmm = mmm; // java.util.Map> + this.mmmm = mmmm; // java.util.Map>, java.util.List>>> + this.mmmmm = mmmmm; // java.util.Map>, java.util.Set>> + this.s = s; // java.util.Set + this.ss = ss; // java.util.Set>> + this.sss = sss; // java.util.Set> + this.ssss = ssss; // java.util.Set + this.sssss = sssss; // java.util.Set> +}; + +ComplexObject.prototype.protocolId = function () { + return 1160; +}; + +ComplexObject.writeObject = function (byteBuffer, packet) { + if (packet === null) { + byteBuffer.writeBoolean(false); + return; + } + byteBuffer.writeBoolean(true); + byteBuffer.writeByte(packet.a); + byteBuffer.writeByte(packet.aa); + if (packet.aaa === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.aaa.length); + packet.aaa.forEach(element0 => { + byteBuffer.writeByte(element0); + }); + } + if (packet.aaaa === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.aaaa.length); + packet.aaaa.forEach(element1 => { + byteBuffer.writeByte(element1); + }); + } + byteBuffer.writeShort(packet.b); + byteBuffer.writeShort(packet.bb); + if (packet.bbb === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.bbb.length); + packet.bbb.forEach(element2 => { + byteBuffer.writeShort(element2); + }); + } + if (packet.bbbb === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.bbbb.length); + packet.bbbb.forEach(element3 => { + byteBuffer.writeShort(element3); + }); + } + byteBuffer.writeInt(packet.c); + byteBuffer.writeInt(packet.cc); + if (packet.ccc === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ccc.length); + packet.ccc.forEach(element4 => { + byteBuffer.writeInt(element4); + }); + } + if (packet.cccc === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.cccc.length); + packet.cccc.forEach(element5 => { + byteBuffer.writeInt(element5); + }); + } + byteBuffer.writeLong(packet.d); + byteBuffer.writeLong(packet.dd); + if (packet.ddd === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ddd.length); + packet.ddd.forEach(element6 => { + byteBuffer.writeLong(element6); + }); + } + if (packet.dddd === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.dddd.length); + packet.dddd.forEach(element7 => { + byteBuffer.writeLong(element7); + }); + } + byteBuffer.writeFloat(packet.e); + byteBuffer.writeFloat(packet.ee); + if (packet.eee === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.eee.length); + packet.eee.forEach(element8 => { + byteBuffer.writeFloat(element8); + }); + } + if (packet.eeee === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.eeee.length); + packet.eeee.forEach(element9 => { + byteBuffer.writeFloat(element9); + }); + } + byteBuffer.writeDouble(packet.f); + byteBuffer.writeDouble(packet.ff); + if (packet.fff === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.fff.length); + packet.fff.forEach(element10 => { + byteBuffer.writeDouble(element10); + }); + } + if (packet.ffff === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ffff.length); + packet.ffff.forEach(element11 => { + byteBuffer.writeDouble(element11); + }); + } + byteBuffer.writeBoolean(packet.g); + byteBuffer.writeBoolean(packet.gg); + if (packet.ggg === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ggg.length); + packet.ggg.forEach(element12 => { + byteBuffer.writeBoolean(element12); + }); + } + if (packet.gggg === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.gggg.length); + packet.gggg.forEach(element13 => { + byteBuffer.writeBoolean(element13); + }); + } + byteBuffer.writeChar(packet.h); + byteBuffer.writeChar(packet.hh); + if (packet.hhh === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.hhh.length); + packet.hhh.forEach(element14 => { + byteBuffer.writeChar(element14); + }); + } + if (packet.hhhh === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.hhhh.length); + packet.hhhh.forEach(element15 => { + byteBuffer.writeChar(element15); + }); + } + byteBuffer.writeString(packet.jj); + if (packet.jjj === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.jjj.length); + packet.jjj.forEach(element16 => { + byteBuffer.writeString(element16); + }); + } + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, packet.kk); + if (packet.kkk === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.kkk.length); + packet.kkk.forEach(element17 => { + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, element17); + }); + } + if (packet.l === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.l.length); + packet.l.forEach(element18 => { + byteBuffer.writeInt(element18); + }); + } + if (packet.ll === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ll.length); + packet.ll.forEach(element19 => { + if (element19 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element19.length); + element19.forEach(element20 => { + if (element20 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element20.length); + element20.forEach(element21 => { + byteBuffer.writeInt(element21); + }); + } + }); + } + }); + } + if (packet.lll === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.lll.length); + packet.lll.forEach(element22 => { + if (element22 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element22.length); + element22.forEach(element23 => { + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, element23); + }); + } + }); + } + if (packet.llll === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.llll.length); + packet.llll.forEach(element24 => { + byteBuffer.writeString(element24); + }); + } + if (packet.lllll === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.lllll.length); + packet.lllll.forEach(element25 => { + if (element25 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element25.size); + element25.forEach((value27, key26) => { + byteBuffer.writeInt(key26); + byteBuffer.writeString(value27); + }); + } + }); + } + if (packet.m === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.m.size); + packet.m.forEach((value29, key28) => { + byteBuffer.writeInt(key28); + byteBuffer.writeString(value29); + }); + } + if (packet.mm === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.mm.size); + packet.mm.forEach((value31, key30) => { + byteBuffer.writeInt(key30); + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, value31); + }); + } + if (packet.mmm === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.mmm.size); + packet.mmm.forEach((value33, key32) => { + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, key32); + if (value33 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(value33.length); + value33.forEach(element34 => { + byteBuffer.writeInt(element34); + }); + } + }); + } + if (packet.mmmm === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.mmmm.size); + packet.mmmm.forEach((value36, key35) => { + if (key35 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(key35.length); + key35.forEach(element37 => { + if (element37 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element37.length); + element37.forEach(element38 => { + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, element38); + }); + } + }); + } + if (value36 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(value36.length); + value36.forEach(element39 => { + if (element39 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element39.length); + element39.forEach(element40 => { + if (element40 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element40.length); + element40.forEach(element41 => { + byteBuffer.writeInt(element41); + }); + } + }); + } + }); + } + }); + } + if (packet.mmmmm === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.mmmmm.size); + packet.mmmmm.forEach((value43, key42) => { + if (key42 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(key42.length); + key42.forEach(element44 => { + if (element44 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element44.size); + element44.forEach((value46, key45) => { + byteBuffer.writeInt(key45); + byteBuffer.writeString(value46); + }); + } + }); + } + if (value43 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(value43.size); + value43.forEach(element47 => { + if (element47 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element47.size); + element47.forEach((value49, key48) => { + byteBuffer.writeInt(key48); + byteBuffer.writeString(value49); + }); + } + }); + } + }); + } + if (packet.s === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.s.size); + packet.s.forEach(element50 => { + byteBuffer.writeInt(element50); + }); + } + if (packet.ss === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ss.size); + packet.ss.forEach(element51 => { + if (element51 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element51.size); + element51.forEach(element52 => { + if (element52 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element52.length); + element52.forEach(element53 => { + byteBuffer.writeInt(element53); + }); + } + }); + } + }); + } + if (packet.sss === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.sss.size); + packet.sss.forEach(element54 => { + if (element54 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element54.size); + element54.forEach(element55 => { + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, element55); + }); + } + }); + } + if (packet.ssss === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ssss.size); + packet.ssss.forEach(element56 => { + byteBuffer.writeString(element56); + }); + } + if (packet.sssss === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.sssss.size); + packet.sssss.forEach(element57 => { + if (element57 === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(element57.size); + element57.forEach((value59, key58) => { + byteBuffer.writeInt(key58); + byteBuffer.writeString(value59); + }); + } + }); + } +}; + +ComplexObject.readObject = function (byteBuffer) { + if (!byteBuffer.readBoolean()) { + return null; + } + const packet = new ComplexObject(); + const result60 = byteBuffer.readByte(); + packet.a = result60; + const result61 = byteBuffer.readByte(); + packet.aa = result61; + const result62 = []; + const size64 = byteBuffer.readInt(); + if (size64 > 0) { + for (let index63 = 0; index63 < size64; index63++) { + const result65 = byteBuffer.readByte(); + result62.push(result65); + } + } + packet.aaa = result62; + const result66 = []; + const size68 = byteBuffer.readInt(); + if (size68 > 0) { + for (let index67 = 0; index67 < size68; index67++) { + const result69 = byteBuffer.readByte(); + result66.push(result69); + } + } + packet.aaaa = result66; + const result70 = byteBuffer.readShort(); + packet.b = result70; + const result71 = byteBuffer.readShort(); + packet.bb = result71; + const result72 = []; + const size74 = byteBuffer.readInt(); + if (size74 > 0) { + for (let index73 = 0; index73 < size74; index73++) { + const result75 = byteBuffer.readShort(); + result72.push(result75); + } + } + packet.bbb = result72; + const result76 = []; + const size78 = byteBuffer.readInt(); + if (size78 > 0) { + for (let index77 = 0; index77 < size78; index77++) { + const result79 = byteBuffer.readShort(); + result76.push(result79); + } + } + packet.bbbb = result76; + const result80 = byteBuffer.readInt(); + packet.c = result80; + const result81 = byteBuffer.readInt(); + packet.cc = result81; + const result82 = []; + const size84 = byteBuffer.readInt(); + if (size84 > 0) { + for (let index83 = 0; index83 < size84; index83++) { + const result85 = byteBuffer.readInt(); + result82.push(result85); + } + } + packet.ccc = result82; + const result86 = []; + const size88 = byteBuffer.readInt(); + if (size88 > 0) { + for (let index87 = 0; index87 < size88; index87++) { + const result89 = byteBuffer.readInt(); + result86.push(result89); + } + } + packet.cccc = result86; + const result90 = byteBuffer.readLong(); + packet.d = result90; + const result91 = byteBuffer.readLong(); + packet.dd = result91; + const result92 = []; + const size94 = byteBuffer.readInt(); + if (size94 > 0) { + for (let index93 = 0; index93 < size94; index93++) { + const result95 = byteBuffer.readLong(); + result92.push(result95); + } + } + packet.ddd = result92; + const result96 = []; + const size98 = byteBuffer.readInt(); + if (size98 > 0) { + for (let index97 = 0; index97 < size98; index97++) { + const result99 = byteBuffer.readLong(); + result96.push(result99); + } + } + packet.dddd = result96; + const result100 = byteBuffer.readFloat(); + packet.e = result100; + const result101 = byteBuffer.readFloat(); + packet.ee = result101; + const result102 = []; + const size104 = byteBuffer.readInt(); + if (size104 > 0) { + for (let index103 = 0; index103 < size104; index103++) { + const result105 = byteBuffer.readFloat(); + result102.push(result105); + } + } + packet.eee = result102; + const result106 = []; + const size108 = byteBuffer.readInt(); + if (size108 > 0) { + for (let index107 = 0; index107 < size108; index107++) { + const result109 = byteBuffer.readFloat(); + result106.push(result109); + } + } + packet.eeee = result106; + const result110 = byteBuffer.readDouble(); + packet.f = result110; + const result111 = byteBuffer.readDouble(); + packet.ff = result111; + const result112 = []; + const size114 = byteBuffer.readInt(); + if (size114 > 0) { + for (let index113 = 0; index113 < size114; index113++) { + const result115 = byteBuffer.readDouble(); + result112.push(result115); + } + } + packet.fff = result112; + const result116 = []; + const size118 = byteBuffer.readInt(); + if (size118 > 0) { + for (let index117 = 0; index117 < size118; index117++) { + const result119 = byteBuffer.readDouble(); + result116.push(result119); + } + } + packet.ffff = result116; + const result120 = byteBuffer.readBoolean(); + packet.g = result120; + const result121 = byteBuffer.readBoolean(); + packet.gg = result121; + const result122 = []; + const size124 = byteBuffer.readInt(); + if (size124 > 0) { + for (let index123 = 0; index123 < size124; index123++) { + const result125 = byteBuffer.readBoolean(); + result122.push(result125); + } + } + packet.ggg = result122; + const result126 = []; + const size128 = byteBuffer.readInt(); + if (size128 > 0) { + for (let index127 = 0; index127 < size128; index127++) { + const result129 = byteBuffer.readBoolean(); + result126.push(result129); + } + } + packet.gggg = result126; + const result130 = byteBuffer.readChar(); + packet.h = result130; + const result131 = byteBuffer.readChar(); + packet.hh = result131; + const result132 = []; + const size134 = byteBuffer.readInt(); + if (size134 > 0) { + for (let index133 = 0; index133 < size134; index133++) { + const result135 = byteBuffer.readChar(); + result132.push(result135); + } + } + packet.hhh = result132; + const result136 = []; + const size138 = byteBuffer.readInt(); + if (size138 > 0) { + for (let index137 = 0; index137 < size138; index137++) { + const result139 = byteBuffer.readChar(); + result136.push(result139); + } + } + packet.hhhh = result136; + const result140 = byteBuffer.readString(); + packet.jj = result140; + const result141 = []; + const size143 = byteBuffer.readInt(); + if (size143 > 0) { + for (let index142 = 0; index142 < size143; index142++) { + const result144 = byteBuffer.readString(); + result141.push(result144); + } + } + packet.jjj = result141; + const result145 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + packet.kk = result145; + const result146 = []; + const size148 = byteBuffer.readInt(); + if (size148 > 0) { + for (let index147 = 0; index147 < size148; index147++) { + const result149 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result146.push(result149); + } + } + packet.kkk = result146; + const result150 = []; + const size151 = byteBuffer.readInt(); + if (size151 > 0) { + for (let index152 = 0; index152 < size151; index152++) { + const result153 = byteBuffer.readInt(); + result150.push(result153); + } + } + packet.l = result150; + const result154 = []; + const size155 = byteBuffer.readInt(); + if (size155 > 0) { + for (let index156 = 0; index156 < size155; index156++) { + const result157 = []; + const size158 = byteBuffer.readInt(); + if (size158 > 0) { + for (let index159 = 0; index159 < size158; index159++) { + const result160 = []; + const size161 = byteBuffer.readInt(); + if (size161 > 0) { + for (let index162 = 0; index162 < size161; index162++) { + const result163 = byteBuffer.readInt(); + result160.push(result163); + } + } + result157.push(result160); + } + } + result154.push(result157); + } + } + packet.ll = result154; + const result164 = []; + const size165 = byteBuffer.readInt(); + if (size165 > 0) { + for (let index166 = 0; index166 < size165; index166++) { + const result167 = []; + const size168 = byteBuffer.readInt(); + if (size168 > 0) { + for (let index169 = 0; index169 < size168; index169++) { + const result170 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result167.push(result170); + } + } + result164.push(result167); + } + } + packet.lll = result164; + const result171 = []; + const size172 = byteBuffer.readInt(); + if (size172 > 0) { + for (let index173 = 0; index173 < size172; index173++) { + const result174 = byteBuffer.readString(); + result171.push(result174); + } + } + packet.llll = result171; + const result175 = []; + const size176 = byteBuffer.readInt(); + if (size176 > 0) { + for (let index177 = 0; index177 < size176; index177++) { + const result178 = new Map(); + const size179 = byteBuffer.readInt(); + if (size179 > 0) { + for (let index180 = 0; index180 < size179; index180++) { + const result181 = byteBuffer.readInt(); + const result182 = byteBuffer.readString(); + result178.set(result181, result182); + } + } + result175.push(result178); + } + } + packet.lllll = result175; + const result183 = new Map(); + const size184 = byteBuffer.readInt(); + if (size184 > 0) { + for (let index185 = 0; index185 < size184; index185++) { + const result186 = byteBuffer.readInt(); + const result187 = byteBuffer.readString(); + result183.set(result186, result187); + } + } + packet.m = result183; + const result188 = new Map(); + const size189 = byteBuffer.readInt(); + if (size189 > 0) { + for (let index190 = 0; index190 < size189; index190++) { + const result191 = byteBuffer.readInt(); + const result192 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result188.set(result191, result192); + } + } + packet.mm = result188; + const result193 = new Map(); + const size194 = byteBuffer.readInt(); + if (size194 > 0) { + for (let index195 = 0; index195 < size194; index195++) { + const result196 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + const result197 = []; + const size198 = byteBuffer.readInt(); + if (size198 > 0) { + for (let index199 = 0; index199 < size198; index199++) { + const result200 = byteBuffer.readInt(); + result197.push(result200); + } + } + result193.set(result196, result197); + } + } + packet.mmm = result193; + const result201 = new Map(); + const size202 = byteBuffer.readInt(); + if (size202 > 0) { + for (let index203 = 0; index203 < size202; index203++) { + const result204 = []; + const size205 = byteBuffer.readInt(); + if (size205 > 0) { + for (let index206 = 0; index206 < size205; index206++) { + const result207 = []; + const size208 = byteBuffer.readInt(); + if (size208 > 0) { + for (let index209 = 0; index209 < size208; index209++) { + const result210 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result207.push(result210); + } + } + result204.push(result207); + } + } + const result211 = []; + const size212 = byteBuffer.readInt(); + if (size212 > 0) { + for (let index213 = 0; index213 < size212; index213++) { + const result214 = []; + const size215 = byteBuffer.readInt(); + if (size215 > 0) { + for (let index216 = 0; index216 < size215; index216++) { + const result217 = []; + const size218 = byteBuffer.readInt(); + if (size218 > 0) { + for (let index219 = 0; index219 < size218; index219++) { + const result220 = byteBuffer.readInt(); + result217.push(result220); + } + } + result214.push(result217); + } + } + result211.push(result214); + } + } + result201.set(result204, result211); + } + } + packet.mmmm = result201; + const result221 = new Map(); + const size222 = byteBuffer.readInt(); + if (size222 > 0) { + for (let index223 = 0; index223 < size222; index223++) { + const result224 = []; + const size225 = byteBuffer.readInt(); + if (size225 > 0) { + for (let index226 = 0; index226 < size225; index226++) { + const result227 = new Map(); + const size228 = byteBuffer.readInt(); + if (size228 > 0) { + for (let index229 = 0; index229 < size228; index229++) { + const result230 = byteBuffer.readInt(); + const result231 = byteBuffer.readString(); + result227.set(result230, result231); + } + } + result224.push(result227); + } + } + const result232 = new Set(); + const size233 = byteBuffer.readInt(); + if (size233 > 0) { + for (let index234 = 0; index234 < size233; index234++) { + const result235 = new Map(); + const size236 = byteBuffer.readInt(); + if (size236 > 0) { + for (let index237 = 0; index237 < size236; index237++) { + const result238 = byteBuffer.readInt(); + const result239 = byteBuffer.readString(); + result235.set(result238, result239); + } + } + result232.add(result235); + } + } + result221.set(result224, result232); + } + } + packet.mmmmm = result221; + const result240 = new Set(); + const size241 = byteBuffer.readInt(); + if (size241 > 0) { + for (let index242 = 0; index242 < size241; index242++) { + const result243 = byteBuffer.readInt(); + result240.add(result243); + } + } + packet.s = result240; + const result244 = new Set(); + const size245 = byteBuffer.readInt(); + if (size245 > 0) { + for (let index246 = 0; index246 < size245; index246++) { + const result247 = new Set(); + const size248 = byteBuffer.readInt(); + if (size248 > 0) { + for (let index249 = 0; index249 < size248; index249++) { + const result250 = []; + const size251 = byteBuffer.readInt(); + if (size251 > 0) { + for (let index252 = 0; index252 < size251; index252++) { + const result253 = byteBuffer.readInt(); + result250.push(result253); + } + } + result247.add(result250); + } + } + result244.add(result247); + } + } + packet.ss = result244; + const result254 = new Set(); + const size255 = byteBuffer.readInt(); + if (size255 > 0) { + for (let index256 = 0; index256 < size255; index256++) { + const result257 = new Set(); + const size258 = byteBuffer.readInt(); + if (size258 > 0) { + for (let index259 = 0; index259 < size258; index259++) { + const result260 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result257.add(result260); + } + } + result254.add(result257); + } + } + packet.sss = result254; + const result261 = new Set(); + const size262 = byteBuffer.readInt(); + if (size262 > 0) { + for (let index263 = 0; index263 < size262; index263++) { + const result264 = byteBuffer.readString(); + result261.add(result264); + } + } + packet.ssss = result261; + const result265 = new Set(); + const size266 = byteBuffer.readInt(); + if (size266 > 0) { + for (let index267 = 0; index267 < size266; index267++) { + const result268 = new Map(); + const size269 = byteBuffer.readInt(); + if (size269 > 0) { + for (let index270 = 0; index270 < size269; index270++) { + const result271 = byteBuffer.readInt(); + const result272 = byteBuffer.readString(); + result268.set(result271, result272); + } + } + result265.add(result268); + } + } + packet.sssss = result265; + return packet; +}; + +export default ComplexObject; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/packet/NormalObject.js b/protocol/src/test/resources/jsTest/jsProtocol/packet/NormalObject.js new file mode 100644 index 00000000..e25f76b6 --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/packet/NormalObject.js @@ -0,0 +1,360 @@ +import ProtocolManager from '../ProtocolManager.js'; +// @author jaysunxiao +// @version 1.0 +// @since 2021-02-07 17:18 +const NormalObject = function (a, aaa, b, bbb, c, ccc, d, ddd, e, eee, f, fff, g, ggg, h, hhh, jj, jjj, kk, kkk, l, llll, m, mm, s, ssss) { + this.a = a; // byte + this.aaa = aaa; // byte[] + this.b = b; // short + this.bbb = bbb; // short[] + this.c = c; // int + this.ccc = ccc; // int[] + this.d = d; // long + this.ddd = ddd; // long[] + this.e = e; // float + this.eee = eee; // float[] + this.f = f; // double + this.fff = fff; // double[] + this.g = g; // boolean + this.ggg = ggg; // boolean[] + this.h = h; // char + this.hhh = hhh; // char[] + this.jj = jj; // java.lang.String + this.jjj = jjj; // java.lang.String[] + this.kk = kk; // com.zfoo.protocol.packet.ObjectA + this.kkk = kkk; // com.zfoo.protocol.packet.ObjectA[] + this.l = l; // java.util.List + this.llll = llll; // java.util.List + this.m = m; // java.util.Map + this.mm = mm; // java.util.Map + this.s = s; // java.util.Set + this.ssss = ssss; // java.util.Set +}; + +NormalObject.prototype.protocolId = function () { + return 1161; +}; + +NormalObject.writeObject = function (byteBuffer, packet) { + if (packet === null) { + byteBuffer.writeBoolean(false); + return; + } + byteBuffer.writeBoolean(true); + byteBuffer.writeByte(packet.a); + if (packet.aaa === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.aaa.length); + packet.aaa.forEach(element0 => { + byteBuffer.writeByte(element0); + }); + } + byteBuffer.writeShort(packet.b); + if (packet.bbb === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.bbb.length); + packet.bbb.forEach(element1 => { + byteBuffer.writeShort(element1); + }); + } + byteBuffer.writeInt(packet.c); + if (packet.ccc === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ccc.length); + packet.ccc.forEach(element2 => { + byteBuffer.writeInt(element2); + }); + } + byteBuffer.writeLong(packet.d); + if (packet.ddd === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ddd.length); + packet.ddd.forEach(element3 => { + byteBuffer.writeLong(element3); + }); + } + byteBuffer.writeFloat(packet.e); + if (packet.eee === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.eee.length); + packet.eee.forEach(element4 => { + byteBuffer.writeFloat(element4); + }); + } + byteBuffer.writeDouble(packet.f); + if (packet.fff === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.fff.length); + packet.fff.forEach(element5 => { + byteBuffer.writeDouble(element5); + }); + } + byteBuffer.writeBoolean(packet.g); + if (packet.ggg === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ggg.length); + packet.ggg.forEach(element6 => { + byteBuffer.writeBoolean(element6); + }); + } + byteBuffer.writeChar(packet.h); + if (packet.hhh === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.hhh.length); + packet.hhh.forEach(element7 => { + byteBuffer.writeChar(element7); + }); + } + byteBuffer.writeString(packet.jj); + if (packet.jjj === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.jjj.length); + packet.jjj.forEach(element8 => { + byteBuffer.writeString(element8); + }); + } + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, packet.kk); + if (packet.kkk === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.kkk.length); + packet.kkk.forEach(element9 => { + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, element9); + }); + } + if (packet.l === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.l.length); + packet.l.forEach(element10 => { + byteBuffer.writeInt(element10); + }); + } + if (packet.llll === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.llll.length); + packet.llll.forEach(element11 => { + byteBuffer.writeString(element11); + }); + } + if (packet.m === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.m.size); + packet.m.forEach((value13, key12) => { + byteBuffer.writeInt(key12); + byteBuffer.writeString(value13); + }); + } + if (packet.mm === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.mm.size); + packet.mm.forEach((value15, key14) => { + byteBuffer.writeInt(key14); + ProtocolManager.getProtocol(1116).writeObject(byteBuffer, value15); + }); + } + if (packet.s === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.s.size); + packet.s.forEach(element16 => { + byteBuffer.writeInt(element16); + }); + } + if (packet.ssss === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.ssss.size); + packet.ssss.forEach(element17 => { + byteBuffer.writeString(element17); + }); + } +}; + +NormalObject.readObject = function (byteBuffer) { + if (!byteBuffer.readBoolean()) { + return null; + } + const packet = new NormalObject(); + const result18 = byteBuffer.readByte(); + packet.a = result18; + const result19 = []; + const size21 = byteBuffer.readInt(); + if (size21 > 0) { + for (let index20 = 0; index20 < size21; index20++) { + const result22 = byteBuffer.readByte(); + result19.push(result22); + } + } + packet.aaa = result19; + const result23 = byteBuffer.readShort(); + packet.b = result23; + const result24 = []; + const size26 = byteBuffer.readInt(); + if (size26 > 0) { + for (let index25 = 0; index25 < size26; index25++) { + const result27 = byteBuffer.readShort(); + result24.push(result27); + } + } + packet.bbb = result24; + const result28 = byteBuffer.readInt(); + packet.c = result28; + const result29 = []; + const size31 = byteBuffer.readInt(); + if (size31 > 0) { + for (let index30 = 0; index30 < size31; index30++) { + const result32 = byteBuffer.readInt(); + result29.push(result32); + } + } + packet.ccc = result29; + const result33 = byteBuffer.readLong(); + packet.d = result33; + const result34 = []; + const size36 = byteBuffer.readInt(); + if (size36 > 0) { + for (let index35 = 0; index35 < size36; index35++) { + const result37 = byteBuffer.readLong(); + result34.push(result37); + } + } + packet.ddd = result34; + const result38 = byteBuffer.readFloat(); + packet.e = result38; + const result39 = []; + const size41 = byteBuffer.readInt(); + if (size41 > 0) { + for (let index40 = 0; index40 < size41; index40++) { + const result42 = byteBuffer.readFloat(); + result39.push(result42); + } + } + packet.eee = result39; + const result43 = byteBuffer.readDouble(); + packet.f = result43; + const result44 = []; + const size46 = byteBuffer.readInt(); + if (size46 > 0) { + for (let index45 = 0; index45 < size46; index45++) { + const result47 = byteBuffer.readDouble(); + result44.push(result47); + } + } + packet.fff = result44; + const result48 = byteBuffer.readBoolean(); + packet.g = result48; + const result49 = []; + const size51 = byteBuffer.readInt(); + if (size51 > 0) { + for (let index50 = 0; index50 < size51; index50++) { + const result52 = byteBuffer.readBoolean(); + result49.push(result52); + } + } + packet.ggg = result49; + const result53 = byteBuffer.readChar(); + packet.h = result53; + const result54 = []; + const size56 = byteBuffer.readInt(); + if (size56 > 0) { + for (let index55 = 0; index55 < size56; index55++) { + const result57 = byteBuffer.readChar(); + result54.push(result57); + } + } + packet.hhh = result54; + const result58 = byteBuffer.readString(); + packet.jj = result58; + const result59 = []; + const size61 = byteBuffer.readInt(); + if (size61 > 0) { + for (let index60 = 0; index60 < size61; index60++) { + const result62 = byteBuffer.readString(); + result59.push(result62); + } + } + packet.jjj = result59; + const result63 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + packet.kk = result63; + const result64 = []; + const size66 = byteBuffer.readInt(); + if (size66 > 0) { + for (let index65 = 0; index65 < size66; index65++) { + const result67 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result64.push(result67); + } + } + packet.kkk = result64; + const result68 = []; + const size69 = byteBuffer.readInt(); + if (size69 > 0) { + for (let index70 = 0; index70 < size69; index70++) { + const result71 = byteBuffer.readInt(); + result68.push(result71); + } + } + packet.l = result68; + const result72 = []; + const size73 = byteBuffer.readInt(); + if (size73 > 0) { + for (let index74 = 0; index74 < size73; index74++) { + const result75 = byteBuffer.readString(); + result72.push(result75); + } + } + packet.llll = result72; + const result76 = new Map(); + const size77 = byteBuffer.readInt(); + if (size77 > 0) { + for (let index78 = 0; index78 < size77; index78++) { + const result79 = byteBuffer.readInt(); + const result80 = byteBuffer.readString(); + result76.set(result79, result80); + } + } + packet.m = result76; + const result81 = new Map(); + const size82 = byteBuffer.readInt(); + if (size82 > 0) { + for (let index83 = 0; index83 < size82; index83++) { + const result84 = byteBuffer.readInt(); + const result85 = ProtocolManager.getProtocol(1116).readObject(byteBuffer); + result81.set(result84, result85); + } + } + packet.mm = result81; + const result86 = new Set(); + const size87 = byteBuffer.readInt(); + if (size87 > 0) { + for (let index88 = 0; index88 < size87; index88++) { + const result89 = byteBuffer.readInt(); + result86.add(result89); + } + } + packet.s = result86; + const result90 = new Set(); + const size91 = byteBuffer.readInt(); + if (size91 > 0) { + for (let index92 = 0; index92 < size91; index92++) { + const result93 = byteBuffer.readString(); + result90.add(result93); + } + } + packet.ssss = result90; + return packet; +}; + +export default NormalObject; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectA.js b/protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectA.js new file mode 100644 index 00000000..0abdab2e --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectA.js @@ -0,0 +1,56 @@ +import ProtocolManager from '../ProtocolManager.js'; +// @author jaysunxiao +// @version 1.0 +// @since 2017 10.12 15:39 +const ObjectA = function (a, m, objectB) { + this.a = a; // int + this.m = m; // java.util.Map + this.objectB = objectB; // com.zfoo.protocol.packet.ObjectB +}; + +ObjectA.prototype.protocolId = function () { + return 1116; +}; + +ObjectA.writeObject = function (byteBuffer, packet) { + if (packet === null) { + byteBuffer.writeBoolean(false); + return; + } + byteBuffer.writeBoolean(true); + byteBuffer.writeInt(packet.a); + if (packet.m === null) { + byteBuffer.writeInt(0); + } else { + byteBuffer.writeInt(packet.m.size); + packet.m.forEach((value1, key0) => { + byteBuffer.writeInt(key0); + byteBuffer.writeString(value1); + }); + } + ProtocolManager.getProtocol(1117).writeObject(byteBuffer, packet.objectB); +}; + +ObjectA.readObject = function (byteBuffer) { + if (!byteBuffer.readBoolean()) { + return null; + } + const packet = new ObjectA(); + const result2 = byteBuffer.readInt(); + packet.a = result2; + const result3 = new Map(); + const size4 = byteBuffer.readInt(); + if (size4 > 0) { + for (let index5 = 0; index5 < size4; index5++) { + const result6 = byteBuffer.readInt(); + const result7 = byteBuffer.readString(); + result3.set(result6, result7); + } + } + packet.m = result3; + const result8 = ProtocolManager.getProtocol(1117).readObject(byteBuffer); + packet.objectB = result8; + return packet; +}; + +export default ObjectA; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectB.js b/protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectB.js new file mode 100644 index 00000000..8ed4d4ac --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/packet/ObjectB.js @@ -0,0 +1,31 @@ +// @author jaysunxiao +// @version 1.0 +// @since 2017 10.12 15:39 +const ObjectB = function (flag) { + this.flag = flag; // boolean +}; + +ObjectB.prototype.protocolId = function () { + return 1117; +}; + +ObjectB.writeObject = function (byteBuffer, packet) { + if (packet === null) { + byteBuffer.writeBoolean(false); + return; + } + byteBuffer.writeBoolean(true); + byteBuffer.writeBoolean(packet.flag); +}; + +ObjectB.readObject = function (byteBuffer) { + if (!byteBuffer.readBoolean()) { + return null; + } + const packet = new ObjectB(); + const result0 = byteBuffer.readBoolean(); + packet.flag = result0; + return packet; +}; + +export default ObjectB; diff --git a/protocol/src/test/resources/jsTest/jsProtocol/packet/SimpleObject.js b/protocol/src/test/resources/jsTest/jsProtocol/packet/SimpleObject.js new file mode 100644 index 00000000..d5b9c72f --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocol/packet/SimpleObject.js @@ -0,0 +1,35 @@ +// @author jaysunxiao +// @version 1.0 +// @since 2021-03-27 15:18 +const SimpleObject = function (c, g) { + this.c = c; // int + this.g = g; // boolean +}; + +SimpleObject.prototype.protocolId = function () { + return 1163; +}; + +SimpleObject.writeObject = function (byteBuffer, packet) { + if (packet === null) { + byteBuffer.writeBoolean(false); + return; + } + byteBuffer.writeBoolean(true); + byteBuffer.writeInt(packet.c); + byteBuffer.writeBoolean(packet.g); +}; + +SimpleObject.readObject = function (byteBuffer) { + if (!byteBuffer.readBoolean()) { + return null; + } + const packet = new SimpleObject(); + const result0 = byteBuffer.readInt(); + packet.c = result0; + const result1 = byteBuffer.readBoolean(); + packet.g = result1; + return packet; +}; + +export default SimpleObject; diff --git a/protocol/src/test/resources/jsTest/jsProtocolTest.spec.js b/protocol/src/test/resources/jsTest/jsProtocolTest.spec.js new file mode 100644 index 00000000..0bee0407 --- /dev/null +++ b/protocol/src/test/resources/jsTest/jsProtocolTest.spec.js @@ -0,0 +1,107 @@ +import ByteBuffer from './jsProtocol/buffer/ByteBuffer.js'; +import ProtocolManager from './jsProtocol/ProtocolManager.js'; + +const fs = require('fs'); + +describe('jsProtocolTest', () => { + + it('complexObjectTest', () => { + const data = fs.readFileSync('D:\\zfoo\\protocol\\src\\test\\resources\\ComplexObject.bytes'); + + console.log(data.buffer); + + ProtocolManager.initProtocol(); + + const byteBuffer = new ByteBuffer(); + byteBuffer.writeBytes(data.buffer); + + const packet = ProtocolManager.read(byteBuffer); + console.log(packet); + + const newByteBuffer = new ByteBuffer(); + ProtocolManager.write(newByteBuffer, packet); + + const newPacket = ProtocolManager.read(newByteBuffer); + console.log(newPacket); + + expect(byteBuffer.readOffset).toBe(newByteBuffer.writeOffset); + + // set和map是无序的,所以有的时候输入和输出的字节流有可能不一致,但是长度一定是一致的 + const length = newByteBuffer.writeOffset; + byteBuffer.setReadOffset(0); + newByteBuffer.setReadOffset(0); + for (let i = 0; i < length; i++) { + expect(byteBuffer.readByte()).toBe(newByteBuffer.readByte()); + } + }); + + it('byteBufferTest', () => { + let buffer = new ByteBuffer(); + expect(buffer.getCapacity()).toBe(128); + + // boolean + const testBoolean = [false, true]; + testBoolean.forEach((value) => { + buffer.writeBoolean(value); + expect(buffer.readBoolean()).toBe(value); + }); + expect(buffer.writeOffset).toBe(testBoolean.length); + expect(buffer.readOffset).toBe(testBoolean.length); + + // byte + buffer = new ByteBuffer(); + const testByte = [-128, -99, 0, 99, 127]; + testByte.forEach((value) => { + buffer.writeByte(value); + expect(buffer.readByte()).toBe(value); + }); + expect(buffer.writeOffset).toBe(testByte.length); + expect(buffer.readOffset).toBe(testByte.length); + + // short + buffer = new ByteBuffer(); + const testShort = [-32768, -99, 0, 99, 32767]; + testShort.forEach((value) => { + buffer.writeShort(value); + expect(buffer.readShort()).toBe(value); + }); + expect(buffer.writeOffset).toBe(testShort.length * 2); + expect(buffer.readOffset).toBe(testShort.length * 2); + + // int + buffer = new ByteBuffer(); + const testInt = [-2147483648, -99, 0, 99, 2147483647]; + testInt.forEach((value) => { + buffer.writeInt(value); + expect(buffer.readInt()).toBe(value); + }); + + // float + buffer = new ByteBuffer(); + const testFloat = [-999.5, -99.5, 0, 99.5, 999.5]; + testFloat.forEach((value) => { + buffer.writeFloat(value); + expect(buffer.readFloat()).toBe(value); + }); + + // double + buffer = new ByteBuffer(); + const testDouble = [-999.5, -99.5, 0, 99.5, 999.5]; + testDouble.forEach((value) => { + buffer.writeDouble(value); + expect(buffer.readDouble()).toBe(value); + }); + + // string + buffer = new ByteBuffer(); + const testString = 'hello world!'; + buffer.writeString(testString); + expect(buffer.readString()).toBe(testString); + + // char + buffer = new ByteBuffer(); + const testChar = 'h'; + buffer.writeChar(testString); + expect(buffer.readChar()).toBe(testChar); + }); +}); diff --git a/protocol/src/test/resources/logback-test.xml b/protocol/src/test/resources/logback-test.xml new file mode 100644 index 00000000..077f4d6f --- /dev/null +++ b/protocol/src/test/resources/logback-test.xml @@ -0,0 +1,43 @@ + + + + + com.zfoo.protocol + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Buffer/ByteBuffer.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Buffer/ByteBuffer.lua new file mode 100644 index 00000000..83ac7b6d --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Buffer/ByteBuffer.lua @@ -0,0 +1,448 @@ +--默认为大端模式 +--支持的lua版本为>=5.3 +--支持标准的Lua是使用64-bit的int以及64-bit的双精度float +--当lua只能支持32位的整数类型时,可以考虑用Long来替代,需要修改原代码 + +--local Long = require("Long") + +local maxInt = 2147483647 +local minInt = -2147483648 +local initSize = 128 +local zeroByte = string.char(0) + +local ByteBuffer = {} + +local trueBooleanStrValue = string.char(1) +local falseBooleanStrValue = string.char(0) + +-------------------------------------构造器------------------------------------- +function ByteBuffer:new() + --buffer里的每一个元素为一个长度为1的字符串 + local obj = { + buffer = {}, + writeOffset = 1, + readOffset = 1 + } + setmetatable(obj, self) + self.__index = self + + for i = 1, initSize do + table.insert(obj.buffer, zeroByte) + end + return obj +end + + +-------------------------------------UTF8------------------------------------- +-- 判断utf8字符byte长度 +-- 0xxxxxxx - 1 byte +-- 110yxxxx - 192, 2 byte +-- 1110yyyy - 225, 3 byte +-- 11110zzz - 240, 4 byte +local function chsize(char) + if not char then + print("not char") + return 0 + elseif char > 240 then + return 4 + elseif char > 225 then + return 3 + elseif char > 192 then + return 2 + else + return 1 + end +end + + +-- 截取utf8 字符串 +-- str: 要截取的字符串 +-- startChar: 开始字符下标,从1开始 +-- numChars: 要截取的字符长度 +local function utf8sub(str, startChar, numChars) + local startIndex = 1 + while startChar > 1 do + local char = string.byte(str, startIndex) + startIndex = startIndex + chsize(char) + startChar = startChar - 1 + end + + local currentIndex = startIndex + + while numChars > 0 and currentIndex <= #str do + local char = string.byte(str, currentIndex) + currentIndex = currentIndex + chsize(char) + numChars = numChars - 1 + end + return str:sub(startIndex, currentIndex - 1) +end + + +-------------------------------------get和set------------------------------------- +function ByteBuffer:getWriteOffset() + return self.writeOffset +end + +function ByteBuffer:setWriteOffset(writeOffset) + if writeOffset > #self.buffer then + error("index out of bounds exception: readerIndex: " + self.readOffset + + ", writerIndex: " + self.writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + #self.buffer) + end + self.writeOffset = writeOffset + return self +end + +function ByteBuffer:getReadOffset() + return self.readOffset +end + +function ByteBuffer:setReadOffset(readOffset) + if readOffset > self.writeOffset then + error("index out of bounds exception: readerIndex: " + self.readOffset + + ", writerIndex: " + this.writeOffset + + "(expected: 0 <= readerIndex <= writerIndex <= capacity:" + #self.buffer) + end + self.readOffset = readOffset + return self +end + +function ByteBuffer:getLen() + return #self.buffer +end + +function ByteBuffer:getAvailable() + return #self.buffer - self.writeOffset + 1 +end + +-------------------------------------write和read------------------------------------- + +--bool +function ByteBuffer:writeBoolean(boolValue) + if boolValue then + self:writeRawByteStr(trueBooleanStrValue) + else + self:writeRawByteStr(falseBooleanStrValue) + end + return self +end + +function ByteBuffer:readBoolean() + -- When char > 256, the readUByte method will show an error. + -- So, we have to use readChar + return self:readRawByteStr() == trueBooleanStrValue +end + + +--- byte +-- The byte is a number between -128 and 127, otherwise, the lua will get an error. +function ByteBuffer:writeByte(byteValue) + local str = string.pack("b", byteValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readByte() + local result = string.unpack("b", self:readRawByteStr()) + return result +end + +-- The byte is a number between 0 and 255, otherwise, the lua will get an error. +function ByteBuffer:writeUByte(ubyteValue) + self:writeRawByteStr(string.char(ubyteValue)) + return self +end + +function ByteBuffer:readUByte() + return string.byte(self:readRawByteStr()) +end + + +-- short +function ByteBuffer:writeShort(shortValue) + local str = string.pack(">h", shortValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readShort() + local byteStrArray = self:readBuffer(2) + local result = string.unpack(">h", byteStrArray) + return result +end + + +-- int +function ByteBuffer:writeInt(intValue) + if (math.type(intValue) ~= "integer") then + error("intValue must be integer") + end + if ((minInt > intValue) or (intValue > maxInt)) then + error("intValue must range between minInt:-2147483648 and maxInt:2147483647") + end + + return self:writeLong(intValue) +end + +function ByteBuffer:readInt() + return self:readLong() +end + +-- int +function ByteBuffer:writeRawInt(intValue) + local str = string.pack(">i", intValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readRawInt() + local byteStrArray = self:readBuffer(4) + local result = string.unpack(">i", byteStrArray) + return result +end + +--long +function ByteBuffer:writeLong(longValue) + --Long:writeLong(self, longValue) + + if (math.type(longValue) ~= "integer") then + error("longValue must be integer") + end + + --lua中的右移为无符号右移,要特殊处理 + local mask = longValue >> 63 + local value = longValue << 1 + if (mask == 1) then + value = value ~ 0xFFFFFFFFFFFFFFFF + end + + if (value >> 7) == 0 then + self:writeUByte(value) + return + end + + if (value >> 14) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte((value >> 7) & 0x7F) + return + end + + if (value >> 21) == 0 then + self:writeUByte((value & 0x7F) | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte((value >> 14) & 0x7F) + return + end + + if (value >> 28) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte((value >> 21) & 0x7F) + return + end + + if (value >> 35) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte((value >> 28) & 0x7F) + return + end + + if (value >> 42) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte((value >> 35) & 0x7F) + return + end + + if (value >> 49) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte(((value >> 35) & 0x7F | 0x80)) + self:writeUByte((value >> 42) & 0x7F) + return + end + + if (value >> 56) == 0 then + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte(((value >> 35) & 0x7F | 0x80)) + self:writeUByte(((value >> 42) & 0x7F | 0x80)) + self:writeUByte((value >> 49) & 0x7F) + return + end + + self:writeUByte(value & 0x7F | 0x80) + self:writeUByte(((value >> 7) & 0x7F | 0x80)) + self:writeUByte(((value >> 14) & 0x7F | 0x80)) + self:writeUByte(((value >> 21) & 0x7F | 0x80)) + self:writeUByte(((value >> 28) & 0x7F | 0x80)) + self:writeUByte(((value >> 35) & 0x7F | 0x80)) + self:writeUByte(((value >> 42) & 0x7F | 0x80)) + self:writeUByte(((value >> 49) & 0x7F | 0x80)) + self:writeUByte(value >> 56) + return self +end + +function ByteBuffer:readLong() + --return Long:readLong(self):toString() + local b = self:readUByte() + local value = b & 0x7F + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 7) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 14) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 21) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 28) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 35) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 42) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | ((b & 0x7F) << 49) + if (b & 0x80) ~= 0 then + b = self:readUByte() + value = value | (b << 56) + end + end + end + end + end + end + end + end + return (value >> 1) ~ -(value & 1) +end + +--固定8位的lua数字类型 +function ByteBuffer:writeLuaNumber(luaNumberValue) + local str = string.pack(">n", luaNumberValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readLuaNumber() + local result = string.unpack(">n", self:readBuffer(8)) + return result +end + + + + +--float +function ByteBuffer:writeFloat(floatValue) + local str = string.pack(">f", floatValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readFloat() + local byteStrArray = self:readBuffer(4) + local result = string.unpack(">f", byteStrArray) + return result +end + + +--double +function ByteBuffer:writeDouble(doubleValue) + local str = string.pack(">d", doubleValue) + self:writeBuffer(str) + return self +end + +function ByteBuffer:readDouble() + local byteStrArray = self:readBuffer(8) + local result = string.unpack(">d", byteStrArray) + return result +end + + +--string +function ByteBuffer:writeString(str) + if str == nil or #str == 0 then + self:writeInt(0) + return + end + self:writeInt(#str) + self:writeBuffer(str) + return self + end + +function ByteBuffer:readString() + local length = self:readInt() + return self:readBuffer(length) +end + +--char +function ByteBuffer:writeChar(charValue) + local str = utf8sub(charValue, 1, 1) + self:writeString(str) + return self +end + +function ByteBuffer:readChar() + return self:readString() +end + +--- Write a encoded char array into buf +function ByteBuffer:writeBuffer(str) + for i = 1, #str do + self:writeRawByteStr(string.sub(str, i, i)) + end + return self +end + +--- Read a byte array as string from current position, then update the position. +function ByteBuffer:readBuffer(length) + local byteStrArray = self:getBytes(self.readOffset, self.readOffset + length - 1) + self.readOffset = self.readOffset + length + return byteStrArray +end + +function ByteBuffer:writeRawByteStr(byteStrValue) + if self.writeOffset > #self.buffer + 1 then + for i = #self.buffer + 1, self.writeOffset - 1 do + table.insert(self.buffer, zeroByte) + end + end + self.buffer[self.writeOffset] = string.sub(byteStrValue, 1, 1) + self.writeOffset = self.writeOffset + 1 + return self +end + +function ByteBuffer:readRawByteStr() + local byteStrValue = self.buffer[self.readOffset] + self.readOffset = self.readOffset + 1 + return byteStrValue +end + +--- Get all byte array as a lua string. +-- Do not update position. +function ByteBuffer:getBytes(startIndex, endIndex) + startIndex = startIndex or 1 + endIndex = endIndex or #self.buffer + return table.concat(self.buffer, "", startIndex, endIndex) +end + +return ByteBuffer \ No newline at end of file diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Buffer/Long.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Buffer/Long.lua new file mode 100644 index 00000000..2e0a2080 --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Buffer/Long.lua @@ -0,0 +1,542 @@ +al MAX_LONG_4BYTE = 1 << 32 +local MIN_INT = -2147483648 +local MAX_INT = 2147483647 +local MIN_LONG = 0x8000000000000000 +local MAX_LONG = 0x7fffffffffffffff +local MIN_LONG_STRING = "-9223372036854775808" +--The natural logarithm of 2. +local LN2 = 0.6931471805599453 + +Long = {} + +function Long:new(low, high) + local obj = { + low = low & 0xFFFFFFFF, + high = high & 0xFFFFFFFF + } + + setmetatable(obj, self) + self.__index = self + return obj +end + +local function clone(value) + return Long:new(value.low, value.high) +end + +local function fromBits(lowBits, highBits) + return Long:new(lowBits, highBits) +end + +local function fromInt(value) + value = math.tointeger(value) + local param = 0 + if value < 0 then + param = -1 + end + return fromBits(value, param) +end + +local ZERO = fromInt(0) +local ONE = fromInt(1) +local NEG_ONE = fromInt(-1) +local MAX_VALUE = fromBits(0xFFFFFFFF, 0x7FFFFFFF) +local MIN_VALUE = fromBits(0, 0x80000000) + +local function fromNumber(value) + if (value <= -MIN_LONG) then + return clone(MIN_VALUE) + + end + + if (value + 1 >= MAX_LONG) then + return clone(MAX_VALUE) + end + + if (value < 0) then + return fromNumber(-value):negate() + end + return fromBits(math.floor((value % MAX_LONG_4BYTE)) | 0, math.floor(value / MAX_LONG_4BYTE) | 0) +end + +function Long:fromString(str, radix) + if type(radix) == "nil" then + radix = 10 + end + + if (type(str) ~= "string") then + error("str不是string类型参数") + end + + --进制必须在2到36 + if radix < 2 or 36 < radix then + error("range radix error") + end + + local p = string.find(str, "-") + if p ~= nil then + if (p > 1) then + error("interior hyphen") + end + + if (p == 1) then + return Long:fromString(string.sub(str, 2), radix):negate() + end + end + + local radixToPower = fromNumber(radix ^ 8) + local result = clone(ZERO) + str = tostring(str) + for i = 1, #str, 8 do + local size = math.min(8, #str - i + 1) + if (size < 8) then + local value = tonumber(string.sub(str, i), radix) + local power = fromNumber(radix ^ size) + result = result:multiply(power):add(fromNumber(value)) + else + local value = tonumber(string.sub(str, i, i + 7), radix) + result = result:multiply(radixToPower):add(fromNumber(value)) + end + end + return result +end + +--转为10进制的string符号的long +function Long:toString() + local radix = 10 + if (Long:isZero()) then + return "0" + end + + if (self:isNegative()) then + if (self:equals(MIN_VALUE)) then + return MIN_LONG_STRING + else + return '-' .. self:negate():toString(radix) + end + end + + local radixToPower = fromNumber(radix ^ 6) + local rem = self + local result = '' + while (true) do + local remDiv = rem:divide(radixToPower) + local digits = tostring(rem:subtract(remDiv:multiply(radixToPower)):toInt() & 0xFFFFFFFF) + rem = remDiv + if (rem:isZero()) then + return digits .. result + else + while (#digits < 6) do + digits = '0' .. digits + end + result = '' .. digits .. result + end + end +end + +--Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa). +function Long:toNumber() + return self.high * MAX_LONG_4BYTE + self.low +end + +--Converts the Long to a 32 bit integer, assuming it is a 32 bit integer. +function Long:toInt() + return self.low +end + +function Long:isNegative() + return (self.high & 0x80000000) ~= 0 +end + +function Long:negate() + if self:equals(MIN_VALUE) then + return clone(MIN_VALUE) + end + --正数转为负数的二进制编码,取反加1 + local notSelf = fromBits(~self.low, ~self.high) + return notSelf:add(ONE) +end + +function Long:equals(other) + return self.high == other.high and self.low == other.low +end + +function Long:isZero() + return self.high == 0 and self.low == 0 +end + +function Long:add(addend) + local a48 = (self.high >> 16) + local a32 = (self.high & 0xFFFF) + local a16 = (self.low >> 16) + local a00 = (self.low & 0xFFFF) + + local b48 = (addend.high >> 16) + local b32 = (addend.high & 0xFFFF) + local b16 = (addend.low >> 16) + local b00 = (addend.low & 0xFFFF) + + local c48 = 0 + local c32 = 0 + local c16 = 0 + local c00 = 0 + c00 = c00 + a00 + b00 + c16 = c16 + (c00 >> 16) + c00 = (c00 & 0xFFFF) + c16 = c16 + a16 + b16 + c32 = c32 + (c16 >> 16) + c16 = (c16 & 0xFFFF) + c32 = c32 + a32 + b32 + c48 = c48 + (c32 >> 16) + c32 = (c32 & 0xFFFF) + c48 = c48 + a48 + b48 + c48 = (c48 & 0xFFFF) + return fromBits((c16 << 16) | c00, (c48 << 16) | c32) +end + +function Long:subtract(subtrahend) + return self:add(subtrahend:negate()) +end + +function Long:multiply(multiplier) + if (self:isZero()) then + return clone(ZERO) + end + + if (multiplier:isZero()) then + return clone(ZERO) + end + + local a48 = (self.high >> 16) + local a32 = (self.high & 0xFFFF) + local a16 = (self.low >> 16) + local a00 = (self.low & 0xFFFF) + + local b48 = (multiplier.high >> 16) + local b32 = (multiplier.high & 0xFFFF) + local b16 = (multiplier.low >> 16) + local b00 = (multiplier.low & 0xFFFF) + + local c48 = 0 + local c32 = 0 + local c16 = 0 + local c00 = 0 + c00 = c00 + a00 * b00 + c16 = c16 + (c00 >> 16) + c00 = c00 & 0xFFFF + c16 = c16 + a16 * b00 + c32 = c32 + (c16 >> 16) + c16 = c16 & 0xFFFF + c16 = c16 + a00 * b16 + c32 = c32 + (c16 >> 16) + c16 = c16 & 0xFFFF + c32 = c32 + a32 * b00 + c48 = c48 + (c32 >> 16) + c32 = c32 & 0xFFFF + c32 = c32 + a16 * b16 + c48 = c48 + (c32 >> 16) + c32 = c32 & 0xFFFF + c32 = c32 + a00 * b32 + c48 = c48 + (c32 >> 16) + c32 = c32 & 0xFFFF + c48 = c48 + a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48 + c48 = c48 & 0xFFFF + return fromBits((c16 << 16) | c00, (c48 << 16) | c32) +end + +function Long:divide(divisor) + if (divisor:isZero()) then + error('division by zero') + end + + if (self:isZero()) then + return clone(ZERO) + end + + local approx + local rem + local res + if (self:equals(MIN_VALUE)) then + if (divisor:equals(ONE) or divisor:equals(NEG_ONE)) then + return clone(MIN_VALUE) + elseif (divisor:equals(MIN_VALUE)) then + return clone(ONE) + else + local halfThis = self:shiftRight(1) + approx = halfThis:divide(divisor):shiftLeft(1) + if (approx:equals(ZERO)) then + if (divisor:isNegative()) then + return clone(ONE) + else + return clone(NEG_ONE) + end + else + rem = self:subtract(divisor:multiply(approx)) + res = approx:add(rem:divide(divisor)) + return res + end + end + elseif (divisor:equals(MIN_VALUE)) then + return clone(ZERO) + end + if (self:isNegative()) then + if (divisor:isNegative()) then + return self:neg():divide(divisor:negate()) + end + return self:negate():divide(divisor):negate() + elseif (divisor:isNegative()) then + return self:divide(divisor:negate()):negate() + end + res = clone(ZERO) + + rem = self + while (rem:greaterThanOrEqual(divisor)) do + approx = math.max(1, math.floor(rem:toNumber() / divisor:toNumber())) + + local log2 = math.ceil(math.log(approx) / LN2) + + local delta = 1 + if log2 <= 48 then + delta = 2 ^ (log2 - 48) + end + + local approxRes = fromNumber(approx) + local approxRem = approxRes:multiply(divisor) + while (approxRem:isNegative() or approxRem:greaterThan(rem)) do + approx = approx - delta + approxRes = fromNumber(approx) + approxRem = approxRes:multiply(divisor) + end + + if (approxRes:isZero()) then + approxRes = clone(ONE) + end + + res = res:add(approxRes) + rem = rem:subtract(approxRem) + end + return res +end + +function shiftRight(numBits) + numBits = numBits & 63 + if (numBits == 0) then + return self + elseif (numBits < 32) then + return fromBits((self.low >> numBits) | (self.high << (32 - numBits)), self.high >> numBits) + else + if (self.high >= 0) then + return fromBits(self.high >> (numBits - 32), 0) + else + return fromBits(self.high >> (numBits - 32), -1) + end + end +end + +function shiftLeft(numBits) + numBits = numBits & 63 + if (numBits == 0) then + return self + elseif (numBits < 32) then + return fromBits(self.low << numBits, (self.high << numBits) | (self.low >> (32 - numBits))) + else + return fromBits(0, self.low << (numBits - 32)) + end +end + +function Long:compare(other) + if (self:equals(other)) then + return 0 + end + local thisNeg = self:isNegative() + local otherNeg = other:isNegative() + if (thisNeg and not (otherNeg)) then + return -1 + end + if (not (thisNeg) and otherNeg) then + return 1 + end + if self:subtract(other):isNegative() then + return -1 + else + return 1 + end +end + +function Long:greaterThanOrEqual(other) + return self:compare(other) >= 0 +end + +function Long:greaterThan(other) + return self:compare(other) > 0 +end + +function Long:encodeZigzagLong() + local mask = self.high >> 31 + if mask == 1 then + self.high = ((self.high << 1 | self.low >> 31) ~ 0xFFFFFFFF) & 0xFFFFFFFF + self.low = ((self.low << 1 | mask) ~ 0xFFFFFFFE) & 0xFFFFFFFF + else + self.high = (self.high << 1 | self.low >> 31) & 0xFFFFFFFF + self.low = (self.low << 1) & 0xFFFFFFFF + end + return self +end + +function Long:decodeZigzagLong() + local mask = self.low & 1 + if mask == 1 then + self.low = (((self.low >> 1) | (self.high << 31)) ~ 0xFFFFFFFF) & 0xFFFFFFFF + self.high = ((self.high >> 1 | (0x80000000)) ~ 0x7FFFFFFF) & 0xFFFFFFFF + else + self.low = ((self.low >> 1) | (self.high << 31)) & 0xFFFFFFFF + self.high = (self.high >> 1) & 0xFFFFFFFF + end + return self +end + +function Long:writeLong(byteBuffer, longValue) + if type(longValue) == "string" then + local len = #longValue + if len <= 11 then + local num = tonumber(longValue) + if (MIN_INT <= num) and (num <= MAX_INT) then + byteBuffer:writeInt(num) + return + end + end + end + + if type(longValue) == number then + if (MIN_INT <= longValue) and (longValue <= MAX_INT) then + byteBuffer:writeInt(tonumber(longValue)) + return + end + end + + --写入Long + local value = Long:fromString(longValue) + value:encodeZigzagLong() + local count = 0 + while (value.high ~= 0) do + byteBuffer:writeByte(value.low & 127 | 128) + value.low = ((value.low >> 7) | (value.high << 25)) + value.high = (value.high >> 7) + count = count + 7 + end + while (value.low > 127) do + if count >= 56 then + byteBuffer:writeByte(value.low) + return + end + byteBuffer:writeByte(value.low & 127 | 128) + value.low = value.low >> 7 + count = count + 7 + end + byteBuffer:writeByte(value.low) +end + +local function fromByteBuffer(byteBuffer) + local bits = Long:new(0, 0) + local count = #byteBuffer + local i = 0 + local pos = 1 + if (count > 4) then + --先读入1到4位 + while i < 4 do + bits.low = (bits.low | ((byteBuffer[pos] & 127) << (i * 7))) & 0xFFFFFFFF + i = i + 1 + pos = pos + 1 + end + --读第5位,第5位底位置读到low,高位置读到high + bits.low = (bits.low | ((byteBuffer[pos] & 127) << 28)) & 0xFFFFFFFF + bits.high = (bits.high | ((byteBuffer[pos] & 127) >> 4)) & 0xFFFFFFFF + if (byteBuffer[pos] < 128) then + return bits + end + i = 0 + pos = pos + 1 + else + while i < 3 do + bits.low = (bits.low | ((byteBuffer[pos] & 127) << (i * 7))) & 0xFFFFFFFF + if (byteBuffer[pos] < 128) then + return bits + end + i = i + 1 + pos = pos + 1 + end + bits.low = (bits.low | ((byteBuffer[pos] & 127) << (i * 7))) & 0xFFFFFFFF + return bits + end + + --读最后4位 + while i < 4 do + if (pos == 9) then + bits.high = (bits.high | (byteBuffer[pos] << (i * 7 + 3))) & 0xFFFFFFFF + return bits + end + bits.high = (bits.high | ((byteBuffer[pos] & 127) << (i * 7 + 3))) & 0xFFFFFFFF + if (byteBuffer[pos] < 128) then + return bits + end + i = i + 1 + pos = pos + 1 + end + + return bits +end + +function Long:readLong(buffer) + local byteBuffer = {} + local b = buffer:readByte() + local count = 1 + + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + if ((b & 0x80) ~= 0) then + b = buffer:readByte() + byteBuffer[count] = b + count = count + 1 + end + end + end + end + end + end + end + end + + local longValue = fromByteBuffer(byteBuffer) + longValue:decodeZigzagLong() + return longValue +end + +return Long \ No newline at end of file diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ComplexObject.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ComplexObject.lua new file mode 100644 index 00000000..7c5cee94 --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ComplexObject.lua @@ -0,0 +1,980 @@ +-- 复杂的对象 +-- 包括了各种复杂的结构,数组,List,Set,Map +-- +-- @author jaysunxiao +-- @version 1.0 +-- @since 2017 10.14 11:19 + +local ProtocolManager = require("LuaProtocol.ProtocolManager") + +local ComplexObject = {} + +function ComplexObject:new(a, aa, aaa, aaaa, b, bb, bbb, bbbb, c, cc, ccc, cccc, d, dd, ddd, dddd, e, ee, eee, eeee, f, ff, fff, ffff, g, gg, ggg, gggg, h, hh, hhh, hhhh, jj, jjj, kk, kkk, l, ll, lll, llll, lllll, m, mm, mmm, mmmm, mmmmm, s, ss, sss, ssss, sssss) + local obj = { + -- byte类型,最简单的整形 + a = a, -- byte + -- byte的包装类型 + -- 优先使用基础类型,包装类型会有装箱拆箱 + aa = aa, -- java.lang.Byte + -- 数组类型 + aaa = aaa, -- byte[] + aaaa = aaaa, -- java.lang.Byte[] + b = b, -- short + bb = bb, -- java.lang.Short + bbb = bbb, -- short[] + bbbb = bbbb, -- java.lang.Short[] + c = c, -- int + cc = cc, -- java.lang.Integer + ccc = ccc, -- int[] + cccc = cccc, -- java.lang.Integer[] + d = d, -- long + dd = dd, -- java.lang.Long + ddd = ddd, -- long[] + dddd = dddd, -- java.lang.Long[] + e = e, -- float + ee = ee, -- java.lang.Float + eee = eee, -- float[] + eeee = eeee, -- java.lang.Float[] + f = f, -- double + ff = ff, -- java.lang.Double + fff = fff, -- double[] + ffff = ffff, -- java.lang.Double[] + g = g, -- boolean + gg = gg, -- java.lang.Boolean + ggg = ggg, -- boolean[] + gggg = gggg, -- java.lang.Boolean[] + h = h, -- char + hh = hh, -- java.lang.Character + hhh = hhh, -- char[] + hhhh = hhhh, -- java.lang.Character[] + jj = jj, -- java.lang.String + jjj = jjj, -- java.lang.String[] + kk = kk, -- com.zfoo.protocol.packet.ObjectA + kkk = kkk, -- com.zfoo.protocol.packet.ObjectA[] + l = l, -- java.util.List + ll = ll, -- java.util.List>> + lll = lll, -- java.util.List> + llll = llll, -- java.util.List + lllll = lllll, -- java.util.List> + m = m, -- java.util.Map + mm = mm, -- java.util.Map + mmm = mmm, -- java.util.Map> + mmmm = mmmm, -- java.util.Map>, java.util.List>>> + mmmmm = mmmmm, -- java.util.Map>, java.util.Set>> + s = s, -- java.util.Set + ss = ss, -- java.util.Set>> + sss = sss, -- java.util.Set> + ssss = ssss, -- java.util.Set + sssss = sssss -- java.util.Set> + } + setmetatable(obj, self) + self.__index = self + return obj +end + +function ComplexObject:protocolId() + return 1160 +end + +function ComplexObject:write(byteBuffer, packet) + if packet == null then + byteBuffer:writeBoolean(false) + return + end + byteBuffer:writeBoolean(true) + byteBuffer:writeByte(packet.a) + byteBuffer:writeByte(packet.aa) + if packet.aaa == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.aaa); + for index0, element1 in pairs(packet.aaa) do + byteBuffer:writeByte(element1) + end + end + if packet.aaaa == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.aaaa); + for index2, element3 in pairs(packet.aaaa) do + byteBuffer:writeByte(element3) + end + end + byteBuffer:writeShort(packet.b) + byteBuffer:writeShort(packet.bb) + if packet.bbb == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.bbb); + for index4, element5 in pairs(packet.bbb) do + byteBuffer:writeShort(element5) + end + end + if packet.bbbb == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.bbbb); + for index6, element7 in pairs(packet.bbbb) do + byteBuffer:writeShort(element7) + end + end + byteBuffer:writeInt(packet.c) + byteBuffer:writeInt(packet.cc) + if packet.ccc == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ccc); + for index8, element9 in pairs(packet.ccc) do + byteBuffer:writeInt(element9) + end + end + if packet.cccc == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.cccc); + for index10, element11 in pairs(packet.cccc) do + byteBuffer:writeInt(element11) + end + end + byteBuffer:writeLong(packet.d) + byteBuffer:writeLong(packet.dd) + if packet.ddd == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ddd); + for index12, element13 in pairs(packet.ddd) do + byteBuffer:writeLong(element13) + end + end + if packet.dddd == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.dddd); + for index14, element15 in pairs(packet.dddd) do + byteBuffer:writeLong(element15) + end + end + byteBuffer:writeFloat(packet.e) + byteBuffer:writeFloat(packet.ee) + if packet.eee == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.eee); + for index16, element17 in pairs(packet.eee) do + byteBuffer:writeFloat(element17) + end + end + if packet.eeee == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.eeee); + for index18, element19 in pairs(packet.eeee) do + byteBuffer:writeFloat(element19) + end + end + byteBuffer:writeDouble(packet.f) + byteBuffer:writeDouble(packet.ff) + if packet.fff == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.fff); + for index20, element21 in pairs(packet.fff) do + byteBuffer:writeDouble(element21) + end + end + if packet.ffff == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ffff); + for index22, element23 in pairs(packet.ffff) do + byteBuffer:writeDouble(element23) + end + end + byteBuffer:writeBoolean(packet.g) + byteBuffer:writeBoolean(packet.gg) + if packet.ggg == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ggg); + for index24, element25 in pairs(packet.ggg) do + byteBuffer:writeBoolean(element25) + end + end + if packet.gggg == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.gggg); + for index26, element27 in pairs(packet.gggg) do + byteBuffer:writeBoolean(element27) + end + end + byteBuffer:writeChar(packet.h) + byteBuffer:writeChar(packet.hh) + if packet.hhh == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.hhh); + for index28, element29 in pairs(packet.hhh) do + byteBuffer:writeChar(element29) + end + end + if packet.hhhh == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.hhhh); + for index30, element31 in pairs(packet.hhhh) do + byteBuffer:writeChar(element31) + end + end + byteBuffer:writeString(packet.jj) + if packet.jjj == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.jjj); + for index32, element33 in pairs(packet.jjj) do + byteBuffer:writeString(element33) + end + end + ProtocolManager.getProtocol(1116):write(byteBuffer, packet.kk) + if packet.kkk == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.kkk); + for index34, element35 in pairs(packet.kkk) do + ProtocolManager.getProtocol(1116):write(byteBuffer, element35) + end + end + if packet.l == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.l) + for index36, element37 in pairs(packet.l) do + byteBuffer:writeInt(element37) + end + end + if packet.ll == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ll) + for index38, element39 in pairs(packet.ll) do + if element39 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element39) + for index40, element41 in pairs(element39) do + if element41 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element41) + for index42, element43 in pairs(element41) do + byteBuffer:writeInt(element43) + end + end + end + end + end + end + if packet.lll == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.lll) + for index44, element45 in pairs(packet.lll) do + if element45 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element45) + for index46, element47 in pairs(element45) do + ProtocolManager.getProtocol(1116):write(byteBuffer, element47) + end + end + end + end + if packet.llll == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.llll) + for index48, element49 in pairs(packet.llll) do + byteBuffer:writeString(element49) + end + end + if packet.lllll == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.lllll) + for index50, element51 in pairs(packet.lllll) do + if element51 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(element51)) + for key52, value53 in pairs(element51) do + byteBuffer:writeInt(key52) + byteBuffer:writeString(value53) + end + end + end + end + if packet.m == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.m)) + for key54, value55 in pairs(packet.m) do + byteBuffer:writeInt(key54) + byteBuffer:writeString(value55) + end + end + if packet.mm == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.mm)) + for key56, value57 in pairs(packet.mm) do + byteBuffer:writeInt(key56) + ProtocolManager.getProtocol(1116):write(byteBuffer, value57) + end + end + if packet.mmm == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.mmm)) + for key58, value59 in pairs(packet.mmm) do + ProtocolManager.getProtocol(1116):write(byteBuffer, key58) + if value59 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#value59) + for index60, element61 in pairs(value59) do + byteBuffer:writeInt(element61) + end + end + end + end + if packet.mmmm == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.mmmm)) + for key62, value63 in pairs(packet.mmmm) do + if key62 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#key62) + for index64, element65 in pairs(key62) do + if element65 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element65) + for index66, element67 in pairs(element65) do + ProtocolManager.getProtocol(1116):write(byteBuffer, element67) + end + end + end + end + if value63 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#value63) + for index68, element69 in pairs(value63) do + if element69 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element69) + for index70, element71 in pairs(element69) do + if element71 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element71) + for index72, element73 in pairs(element71) do + byteBuffer:writeInt(element73) + end + end + end + end + end + end + end + end + if packet.mmmmm == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.mmmmm)) + for key74, value75 in pairs(packet.mmmmm) do + if key74 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#key74) + for index76, element77 in pairs(key74) do + if element77 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(element77)) + for key78, value79 in pairs(element77) do + byteBuffer:writeInt(key78) + byteBuffer:writeString(value79) + end + end + end + end + if value75 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(value75)) + for index80, element81 in pairs(value75) do + if element81 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(element81)) + for key82, value83 in pairs(element81) do + byteBuffer:writeInt(key82) + byteBuffer:writeString(value83) + end + end + end + end + end + end + if packet.s == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.s)) + for index84, element85 in pairs(packet.s) do + byteBuffer:writeInt(element85) + end + end + if packet.ss == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.ss)) + for index86, element87 in pairs(packet.ss) do + if element87 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(element87)) + for index88, element89 in pairs(element87) do + if element89 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#element89) + for index90, element91 in pairs(element89) do + byteBuffer:writeInt(element91) + end + end + end + end + end + end + if packet.sss == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.sss)) + for index92, element93 in pairs(packet.sss) do + if element93 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(element93)) + for index94, element95 in pairs(element93) do + ProtocolManager.getProtocol(1116):write(byteBuffer, element95) + end + end + end + end + if packet.ssss == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.ssss)) + for index96, element97 in pairs(packet.ssss) do + byteBuffer:writeString(element97) + end + end + if packet.sssss == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.sssss)) + for index98, element99 in pairs(packet.sssss) do + if element99 == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(element99)) + for key100, value101 in pairs(element99) do + byteBuffer:writeInt(key100) + byteBuffer:writeString(value101) + end + end + end + end +end + +function ComplexObject:read(byteBuffer) + if not(byteBuffer:readBoolean()) then + return nil + end + local packet = ComplexObject:new() + local result102 = byteBuffer:readByte() + packet.a = result102 + local result103 = byteBuffer:readByte() + packet.aa = result103 + local result104 = {} + local size106 = byteBuffer:readInt() + if size106 > 0 then + for index105 = 1, size106 do + local result107 = byteBuffer:readByte() + table.insert(result104, result107) + end + end + packet.aaa = result104 + local result108 = {} + local size110 = byteBuffer:readInt() + if size110 > 0 then + for index109 = 1, size110 do + local result111 = byteBuffer:readByte() + table.insert(result108, result111) + end + end + packet.aaaa = result108 + local result112 = byteBuffer:readShort() + packet.b = result112 + local result113 = byteBuffer:readShort() + packet.bb = result113 + local result114 = {} + local size116 = byteBuffer:readInt() + if size116 > 0 then + for index115 = 1, size116 do + local result117 = byteBuffer:readShort() + table.insert(result114, result117) + end + end + packet.bbb = result114 + local result118 = {} + local size120 = byteBuffer:readInt() + if size120 > 0 then + for index119 = 1, size120 do + local result121 = byteBuffer:readShort() + table.insert(result118, result121) + end + end + packet.bbbb = result118 + local result122 = byteBuffer:readInt() + packet.c = result122 + local result123 = byteBuffer:readInt() + packet.cc = result123 + local result124 = {} + local size126 = byteBuffer:readInt() + if size126 > 0 then + for index125 = 1, size126 do + local result127 = byteBuffer:readInt() + table.insert(result124, result127) + end + end + packet.ccc = result124 + local result128 = {} + local size130 = byteBuffer:readInt() + if size130 > 0 then + for index129 = 1, size130 do + local result131 = byteBuffer:readInt() + table.insert(result128, result131) + end + end + packet.cccc = result128 + local result132 = byteBuffer:readLong() + packet.d = result132 + local result133 = byteBuffer:readLong() + packet.dd = result133 + local result134 = {} + local size136 = byteBuffer:readInt() + if size136 > 0 then + for index135 = 1, size136 do + local result137 = byteBuffer:readLong() + table.insert(result134, result137) + end + end + packet.ddd = result134 + local result138 = {} + local size140 = byteBuffer:readInt() + if size140 > 0 then + for index139 = 1, size140 do + local result141 = byteBuffer:readLong() + table.insert(result138, result141) + end + end + packet.dddd = result138 + local result142 = byteBuffer:readFloat() + packet.e = result142 + local result143 = byteBuffer:readFloat() + packet.ee = result143 + local result144 = {} + local size146 = byteBuffer:readInt() + if size146 > 0 then + for index145 = 1, size146 do + local result147 = byteBuffer:readFloat() + table.insert(result144, result147) + end + end + packet.eee = result144 + local result148 = {} + local size150 = byteBuffer:readInt() + if size150 > 0 then + for index149 = 1, size150 do + local result151 = byteBuffer:readFloat() + table.insert(result148, result151) + end + end + packet.eeee = result148 + local result152 = byteBuffer:readDouble() + packet.f = result152 + local result153 = byteBuffer:readDouble() + packet.ff = result153 + local result154 = {} + local size156 = byteBuffer:readInt() + if size156 > 0 then + for index155 = 1, size156 do + local result157 = byteBuffer:readDouble() + table.insert(result154, result157) + end + end + packet.fff = result154 + local result158 = {} + local size160 = byteBuffer:readInt() + if size160 > 0 then + for index159 = 1, size160 do + local result161 = byteBuffer:readDouble() + table.insert(result158, result161) + end + end + packet.ffff = result158 + local result162 = byteBuffer:readBoolean() + packet.g = result162 + local result163 = byteBuffer:readBoolean() + packet.gg = result163 + local result164 = {} + local size166 = byteBuffer:readInt() + if size166 > 0 then + for index165 = 1, size166 do + local result167 = byteBuffer:readBoolean() + table.insert(result164, result167) + end + end + packet.ggg = result164 + local result168 = {} + local size170 = byteBuffer:readInt() + if size170 > 0 then + for index169 = 1, size170 do + local result171 = byteBuffer:readBoolean() + table.insert(result168, result171) + end + end + packet.gggg = result168 + local result172 = byteBuffer:readChar() + packet.h = result172 + local result173 = byteBuffer:readChar() + packet.hh = result173 + local result174 = {} + local size176 = byteBuffer:readInt() + if size176 > 0 then + for index175 = 1, size176 do + local result177 = byteBuffer:readChar() + table.insert(result174, result177) + end + end + packet.hhh = result174 + local result178 = {} + local size180 = byteBuffer:readInt() + if size180 > 0 then + for index179 = 1, size180 do + local result181 = byteBuffer:readChar() + table.insert(result178, result181) + end + end + packet.hhhh = result178 + local result182 = byteBuffer:readString() + packet.jj = result182 + local result183 = {} + local size185 = byteBuffer:readInt() + if size185 > 0 then + for index184 = 1, size185 do + local result186 = byteBuffer:readString() + table.insert(result183, result186) + end + end + packet.jjj = result183 + local result187 = ProtocolManager.getProtocol(1116):read(byteBuffer) + packet.kk = result187 + local result188 = {} + local size190 = byteBuffer:readInt() + if size190 > 0 then + for index189 = 1, size190 do + local result191 = ProtocolManager.getProtocol(1116):read(byteBuffer) + table.insert(result188, result191) + end + end + packet.kkk = result188 + local result192 = {} + local size193 = byteBuffer:readInt() + if size193 > 0 then + for index194 = 1, size193 do + local result195 = byteBuffer:readInt() + table.insert(result192, result195) + end + end + packet.l = result192 + local result196 = {} + local size197 = byteBuffer:readInt() + if size197 > 0 then + for index198 = 1, size197 do + local result199 = {} + local size200 = byteBuffer:readInt() + if size200 > 0 then + for index201 = 1, size200 do + local result202 = {} + local size203 = byteBuffer:readInt() + if size203 > 0 then + for index204 = 1, size203 do + local result205 = byteBuffer:readInt() + table.insert(result202, result205) + end + end + table.insert(result199, result202) + end + end + table.insert(result196, result199) + end + end + packet.ll = result196 + local result206 = {} + local size207 = byteBuffer:readInt() + if size207 > 0 then + for index208 = 1, size207 do + local result209 = {} + local size210 = byteBuffer:readInt() + if size210 > 0 then + for index211 = 1, size210 do + local result212 = ProtocolManager.getProtocol(1116):read(byteBuffer) + table.insert(result209, result212) + end + end + table.insert(result206, result209) + end + end + packet.lll = result206 + local result213 = {} + local size214 = byteBuffer:readInt() + if size214 > 0 then + for index215 = 1, size214 do + local result216 = byteBuffer:readString() + table.insert(result213, result216) + end + end + packet.llll = result213 + local result217 = {} + local size218 = byteBuffer:readInt() + if size218 > 0 then + for index219 = 1, size218 do + local result220 = {} + local size221 = byteBuffer:readInt() + if size221 > 0 then + for index222 = 1, size221 do + local result223 = byteBuffer:readInt() + local result224 = byteBuffer:readString() + result220[result223] = result224 + end + end + table.insert(result217, result220) + end + end + packet.lllll = result217 + local result225 = {} + local size226 = byteBuffer:readInt() + if size226 > 0 then + for index227 = 1, size226 do + local result228 = byteBuffer:readInt() + local result229 = byteBuffer:readString() + result225[result228] = result229 + end + end + packet.m = result225 + local result230 = {} + local size231 = byteBuffer:readInt() + if size231 > 0 then + for index232 = 1, size231 do + local result233 = byteBuffer:readInt() + local result234 = ProtocolManager.getProtocol(1116):read(byteBuffer) + result230[result233] = result234 + end + end + packet.mm = result230 + local result235 = {} + local size236 = byteBuffer:readInt() + if size236 > 0 then + for index237 = 1, size236 do + local result238 = ProtocolManager.getProtocol(1116):read(byteBuffer) + local result239 = {} + local size240 = byteBuffer:readInt() + if size240 > 0 then + for index241 = 1, size240 do + local result242 = byteBuffer:readInt() + table.insert(result239, result242) + end + end + result235[result238] = result239 + end + end + packet.mmm = result235 + local result243 = {} + local size244 = byteBuffer:readInt() + if size244 > 0 then + for index245 = 1, size244 do + local result246 = {} + local size247 = byteBuffer:readInt() + if size247 > 0 then + for index248 = 1, size247 do + local result249 = {} + local size250 = byteBuffer:readInt() + if size250 > 0 then + for index251 = 1, size250 do + local result252 = ProtocolManager.getProtocol(1116):read(byteBuffer) + table.insert(result249, result252) + end + end + table.insert(result246, result249) + end + end + local result253 = {} + local size254 = byteBuffer:readInt() + if size254 > 0 then + for index255 = 1, size254 do + local result256 = {} + local size257 = byteBuffer:readInt() + if size257 > 0 then + for index258 = 1, size257 do + local result259 = {} + local size260 = byteBuffer:readInt() + if size260 > 0 then + for index261 = 1, size260 do + local result262 = byteBuffer:readInt() + table.insert(result259, result262) + end + end + table.insert(result256, result259) + end + end + table.insert(result253, result256) + end + end + result243[result246] = result253 + end + end + packet.mmmm = result243 + local result263 = {} + local size264 = byteBuffer:readInt() + if size264 > 0 then + for index265 = 1, size264 do + local result266 = {} + local size267 = byteBuffer:readInt() + if size267 > 0 then + for index268 = 1, size267 do + local result269 = {} + local size270 = byteBuffer:readInt() + if size270 > 0 then + for index271 = 1, size270 do + local result272 = byteBuffer:readInt() + local result273 = byteBuffer:readString() + result269[result272] = result273 + end + end + table.insert(result266, result269) + end + end + local result274 = {} + local size275 = byteBuffer:readInt() + if size275 > 0 then + for index276 = 1, size275 do + local result277 = {} + local size278 = byteBuffer:readInt() + if size278 > 0 then + for index279 = 1, size278 do + local result280 = byteBuffer:readInt() + local result281 = byteBuffer:readString() + result277[result280] = result281 + end + end + result274[result277] = result277 + end + end + result263[result266] = result274 + end + end + packet.mmmmm = result263 + local result282 = {} + local size283 = byteBuffer:readInt() + if size283 > 0 then + for index284 = 1, size283 do + local result285 = byteBuffer:readInt() + result282[result285] = result285 + end + end + packet.s = result282 + local result286 = {} + local size287 = byteBuffer:readInt() + if size287 > 0 then + for index288 = 1, size287 do + local result289 = {} + local size290 = byteBuffer:readInt() + if size290 > 0 then + for index291 = 1, size290 do + local result292 = {} + local size293 = byteBuffer:readInt() + if size293 > 0 then + for index294 = 1, size293 do + local result295 = byteBuffer:readInt() + table.insert(result292, result295) + end + end + result289[result292] = result292 + end + end + result286[result289] = result289 + end + end + packet.ss = result286 + local result296 = {} + local size297 = byteBuffer:readInt() + if size297 > 0 then + for index298 = 1, size297 do + local result299 = {} + local size300 = byteBuffer:readInt() + if size300 > 0 then + for index301 = 1, size300 do + local result302 = ProtocolManager.getProtocol(1116):read(byteBuffer) + result299[result302] = result302 + end + end + result296[result299] = result299 + end + end + packet.sss = result296 + local result303 = {} + local size304 = byteBuffer:readInt() + if size304 > 0 then + for index305 = 1, size304 do + local result306 = byteBuffer:readString() + result303[result306] = result306 + end + end + packet.ssss = result303 + local result307 = {} + local size308 = byteBuffer:readInt() + if size308 > 0 then + for index309 = 1, size308 do + local result310 = {} + local size311 = byteBuffer:readInt() + if size311 > 0 then + for index312 = 1, size311 do + local result313 = byteBuffer:readInt() + local result314 = byteBuffer:readString() + result310[result313] = result314 + end + end + result307[result310] = result310 + end + end + packet.sssss = result307 + return packet +end + +return ComplexObject diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Packet/NormalObject.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/NormalObject.lua new file mode 100644 index 00000000..216098de --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/NormalObject.lua @@ -0,0 +1,369 @@ +-- @author jaysunxiao +-- @version 1.0 +-- @since 2021-02-07 17:18 + +local ProtocolManager = require("LuaProtocol.ProtocolManager") + +local NormalObject = {} + +function NormalObject:new(a, aaa, b, bbb, c, ccc, d, ddd, e, eee, f, fff, g, ggg, h, hhh, jj, jjj, kk, kkk, l, llll, m, mm, s, ssss) + local obj = { + a = a, -- byte + aaa = aaa, -- byte[] + b = b, -- short + bbb = bbb, -- short[] + c = c, -- int + ccc = ccc, -- int[] + d = d, -- long + ddd = ddd, -- long[] + e = e, -- float + eee = eee, -- float[] + f = f, -- double + fff = fff, -- double[] + g = g, -- boolean + ggg = ggg, -- boolean[] + h = h, -- char + hhh = hhh, -- char[] + jj = jj, -- java.lang.String + jjj = jjj, -- java.lang.String[] + kk = kk, -- com.zfoo.protocol.packet.ObjectA + kkk = kkk, -- com.zfoo.protocol.packet.ObjectA[] + l = l, -- java.util.List + llll = llll, -- java.util.List + m = m, -- java.util.Map + mm = mm, -- java.util.Map + s = s, -- java.util.Set + ssss = ssss -- java.util.Set + } + setmetatable(obj, self) + self.__index = self + return obj +end + +function NormalObject:protocolId() + return 1161 +end + +function NormalObject:write(byteBuffer, packet) + if packet == null then + byteBuffer:writeBoolean(false) + return + end + byteBuffer:writeBoolean(true) + byteBuffer:writeByte(packet.a) + if packet.aaa == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.aaa); + for index0, element1 in pairs(packet.aaa) do + byteBuffer:writeByte(element1) + end + end + byteBuffer:writeShort(packet.b) + if packet.bbb == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.bbb); + for index2, element3 in pairs(packet.bbb) do + byteBuffer:writeShort(element3) + end + end + byteBuffer:writeInt(packet.c) + if packet.ccc == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ccc); + for index4, element5 in pairs(packet.ccc) do + byteBuffer:writeInt(element5) + end + end + byteBuffer:writeLong(packet.d) + if packet.ddd == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ddd); + for index6, element7 in pairs(packet.ddd) do + byteBuffer:writeLong(element7) + end + end + byteBuffer:writeFloat(packet.e) + if packet.eee == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.eee); + for index8, element9 in pairs(packet.eee) do + byteBuffer:writeFloat(element9) + end + end + byteBuffer:writeDouble(packet.f) + if packet.fff == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.fff); + for index10, element11 in pairs(packet.fff) do + byteBuffer:writeDouble(element11) + end + end + byteBuffer:writeBoolean(packet.g) + if packet.ggg == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.ggg); + for index12, element13 in pairs(packet.ggg) do + byteBuffer:writeBoolean(element13) + end + end + byteBuffer:writeChar(packet.h) + if packet.hhh == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.hhh); + for index14, element15 in pairs(packet.hhh) do + byteBuffer:writeChar(element15) + end + end + byteBuffer:writeString(packet.jj) + if packet.jjj == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.jjj); + for index16, element17 in pairs(packet.jjj) do + byteBuffer:writeString(element17) + end + end + ProtocolManager.getProtocol(1116):write(byteBuffer, packet.kk) + if packet.kkk == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.kkk); + for index18, element19 in pairs(packet.kkk) do + ProtocolManager.getProtocol(1116):write(byteBuffer, element19) + end + end + if packet.l == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.l) + for index20, element21 in pairs(packet.l) do + byteBuffer:writeInt(element21) + end + end + if packet.llll == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(#packet.llll) + for index22, element23 in pairs(packet.llll) do + byteBuffer:writeString(element23) + end + end + if packet.m == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.m)) + for key24, value25 in pairs(packet.m) do + byteBuffer:writeInt(key24) + byteBuffer:writeString(value25) + end + end + if packet.mm == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.mm)) + for key26, value27 in pairs(packet.mm) do + byteBuffer:writeInt(key26) + ProtocolManager.getProtocol(1116):write(byteBuffer, value27) + end + end + if packet.s == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.s)) + for index28, element29 in pairs(packet.s) do + byteBuffer:writeInt(element29) + end + end + if packet.ssss == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.setSize(packet.ssss)) + for index30, element31 in pairs(packet.ssss) do + byteBuffer:writeString(element31) + end + end +end + +function NormalObject:read(byteBuffer) + if not(byteBuffer:readBoolean()) then + return nil + end + local packet = NormalObject:new() + local result32 = byteBuffer:readByte() + packet.a = result32 + local result33 = {} + local size35 = byteBuffer:readInt() + if size35 > 0 then + for index34 = 1, size35 do + local result36 = byteBuffer:readByte() + table.insert(result33, result36) + end + end + packet.aaa = result33 + local result37 = byteBuffer:readShort() + packet.b = result37 + local result38 = {} + local size40 = byteBuffer:readInt() + if size40 > 0 then + for index39 = 1, size40 do + local result41 = byteBuffer:readShort() + table.insert(result38, result41) + end + end + packet.bbb = result38 + local result42 = byteBuffer:readInt() + packet.c = result42 + local result43 = {} + local size45 = byteBuffer:readInt() + if size45 > 0 then + for index44 = 1, size45 do + local result46 = byteBuffer:readInt() + table.insert(result43, result46) + end + end + packet.ccc = result43 + local result47 = byteBuffer:readLong() + packet.d = result47 + local result48 = {} + local size50 = byteBuffer:readInt() + if size50 > 0 then + for index49 = 1, size50 do + local result51 = byteBuffer:readLong() + table.insert(result48, result51) + end + end + packet.ddd = result48 + local result52 = byteBuffer:readFloat() + packet.e = result52 + local result53 = {} + local size55 = byteBuffer:readInt() + if size55 > 0 then + for index54 = 1, size55 do + local result56 = byteBuffer:readFloat() + table.insert(result53, result56) + end + end + packet.eee = result53 + local result57 = byteBuffer:readDouble() + packet.f = result57 + local result58 = {} + local size60 = byteBuffer:readInt() + if size60 > 0 then + for index59 = 1, size60 do + local result61 = byteBuffer:readDouble() + table.insert(result58, result61) + end + end + packet.fff = result58 + local result62 = byteBuffer:readBoolean() + packet.g = result62 + local result63 = {} + local size65 = byteBuffer:readInt() + if size65 > 0 then + for index64 = 1, size65 do + local result66 = byteBuffer:readBoolean() + table.insert(result63, result66) + end + end + packet.ggg = result63 + local result67 = byteBuffer:readChar() + packet.h = result67 + local result68 = {} + local size70 = byteBuffer:readInt() + if size70 > 0 then + for index69 = 1, size70 do + local result71 = byteBuffer:readChar() + table.insert(result68, result71) + end + end + packet.hhh = result68 + local result72 = byteBuffer:readString() + packet.jj = result72 + local result73 = {} + local size75 = byteBuffer:readInt() + if size75 > 0 then + for index74 = 1, size75 do + local result76 = byteBuffer:readString() + table.insert(result73, result76) + end + end + packet.jjj = result73 + local result77 = ProtocolManager.getProtocol(1116):read(byteBuffer) + packet.kk = result77 + local result78 = {} + local size80 = byteBuffer:readInt() + if size80 > 0 then + for index79 = 1, size80 do + local result81 = ProtocolManager.getProtocol(1116):read(byteBuffer) + table.insert(result78, result81) + end + end + packet.kkk = result78 + local result82 = {} + local size83 = byteBuffer:readInt() + if size83 > 0 then + for index84 = 1, size83 do + local result85 = byteBuffer:readInt() + table.insert(result82, result85) + end + end + packet.l = result82 + local result86 = {} + local size87 = byteBuffer:readInt() + if size87 > 0 then + for index88 = 1, size87 do + local result89 = byteBuffer:readString() + table.insert(result86, result89) + end + end + packet.llll = result86 + local result90 = {} + local size91 = byteBuffer:readInt() + if size91 > 0 then + for index92 = 1, size91 do + local result93 = byteBuffer:readInt() + local result94 = byteBuffer:readString() + result90[result93] = result94 + end + end + packet.m = result90 + local result95 = {} + local size96 = byteBuffer:readInt() + if size96 > 0 then + for index97 = 1, size96 do + local result98 = byteBuffer:readInt() + local result99 = ProtocolManager.getProtocol(1116):read(byteBuffer) + result95[result98] = result99 + end + end + packet.mm = result95 + local result100 = {} + local size101 = byteBuffer:readInt() + if size101 > 0 then + for index102 = 1, size101 do + local result103 = byteBuffer:readInt() + result100[result103] = result103 + end + end + packet.s = result100 + local result104 = {} + local size105 = byteBuffer:readInt() + if size105 > 0 then + for index106 = 1, size105 do + local result107 = byteBuffer:readString() + result104[result107] = result107 + end + end + packet.ssss = result104 + return packet +end + +return NormalObject diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectA.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectA.lua new file mode 100644 index 00000000..df3bfab8 --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectA.lua @@ -0,0 +1,65 @@ +-- @author jaysunxiao +-- @version 1.0 +-- @since 2017 10.12 15:39 + +local ProtocolManager = require("LuaProtocol.ProtocolManager") + +local ObjectA = {} + +function ObjectA:new(a, m, objectB) + local obj = { + a = a, -- int + m = m, -- java.util.Map + objectB = objectB -- com.zfoo.protocol.packet.ObjectB + } + setmetatable(obj, self) + self.__index = self + return obj +end + +function ObjectA:protocolId() + return 1116 +end + +function ObjectA:write(byteBuffer, packet) + if packet == null then + byteBuffer:writeBoolean(false) + return + end + byteBuffer:writeBoolean(true) + byteBuffer:writeInt(packet.a) + if packet.m == null then + byteBuffer:writeInt(0) + else + byteBuffer:writeInt(table.mapSize(packet.m)) + for key0, value1 in pairs(packet.m) do + byteBuffer:writeInt(key0) + byteBuffer:writeString(value1) + end + end + ProtocolManager.getProtocol(1117):write(byteBuffer, packet.objectB) +end + +function ObjectA:read(byteBuffer) + if not(byteBuffer:readBoolean()) then + return nil + end + local packet = ObjectA:new() + local result2 = byteBuffer:readInt() + packet.a = result2 + local result3 = {} + local size4 = byteBuffer:readInt() + if size4 > 0 then + for index5 = 1, size4 do + local result6 = byteBuffer:readInt() + local result7 = byteBuffer:readString() + result3[result6] = result7 + end + end + packet.m = result3 + local result8 = ProtocolManager.getProtocol(1117):read(byteBuffer) + packet.objectB = result8 + return packet +end + +return ObjectA diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectB.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectB.lua new file mode 100644 index 00000000..37c58491 --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/ObjectB.lua @@ -0,0 +1,39 @@ +-- @author jaysunxiao +-- @version 1.0 +-- @since 2017 10.12 15:39 + +local ObjectB = {} + +function ObjectB:new(flag) + local obj = { + flag = flag -- boolean + } + setmetatable(obj, self) + self.__index = self + return obj +end + +function ObjectB:protocolId() + return 1117 +end + +function ObjectB:write(byteBuffer, packet) + if packet == null then + byteBuffer:writeBoolean(false) + return + end + byteBuffer:writeBoolean(true) + byteBuffer:writeBoolean(packet.flag) +end + +function ObjectB:read(byteBuffer) + if not(byteBuffer:readBoolean()) then + return nil + end + local packet = ObjectB:new() + local result0 = byteBuffer:readBoolean() + packet.flag = result0 + return packet +end + +return ObjectB diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/Packet/SimpleObject.lua b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/SimpleObject.lua new file mode 100644 index 00000000..37e61e48 --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/Packet/SimpleObject.lua @@ -0,0 +1,43 @@ +-- @author jaysunxiao +-- @version 1.0 +-- @since 2021-03-27 15:18 + +local SimpleObject = {} + +function SimpleObject:new(c, g) + local obj = { + c = c, -- int + g = g -- boolean + } + setmetatable(obj, self) + self.__index = self + return obj +end + +function SimpleObject:protocolId() + return 1163 +end + +function SimpleObject:write(byteBuffer, packet) + if packet == null then + byteBuffer:writeBoolean(false) + return + end + byteBuffer:writeBoolean(true) + byteBuffer:writeInt(packet.c) + byteBuffer:writeBoolean(packet.g) +end + +function SimpleObject:read(byteBuffer) + if not(byteBuffer:readBoolean()) then + return nil + end + local packet = SimpleObject:new() + local result0 = byteBuffer:readInt() + packet.c = result0 + local result1 = byteBuffer:readBoolean() + packet.g = result1 + return packet +end + +return SimpleObject diff --git a/protocol/src/test/resources/luaTest/LuaProtocol/ProtocolManager.lua b/protocol/src/test/resources/luaTest/LuaProtocol/ProtocolManager.lua new file mode 100644 index 00000000..b02b37de --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocol/ProtocolManager.lua @@ -0,0 +1,68 @@ +local ByteBuffer = require("LuaProtocol.Buffer.ByteBuffer") + +protocols = {} + +ProtocolManager = {} + +-- table扩展方法,后去set和map的大小 +function table.setSize(set) + local size = 0 + for _,_ in pairs(set) do + size = size + 1 + end + return size +end + + +function table.mapSize(map) + local size = 0 + for _,_ in pairs(map) do + size = size + 1 + end + return size +end + +function ProtocolManager.getProtocol(protocolId) + local protocol = protocols[protocolId] + if protocol == nil then + error("[protocolId:" + protocolId + "]协议不存在") + end + return protocol +end + +function ProtocolManager.write(byteBuffer, packet) + local protocolId = packet:protocolId() + -- 写入协议号 + byteBuffer:writeShort(protocolId) + -- 写入包体 + ProtocolManager.getProtocol(protocolId):write(byteBuffer, packet) +end + +function ProtocolManager.read(byteBuffer) + local protocolId = byteBuffer:readShort() + return ProtocolManager.getProtocol(protocolId):read(byteBuffer) +end + +-- C#传进来的byte数组到lua里就会变成string +function readBytes(bytes) + local byteBuffer = ByteBuffer:new() + byteBuffer:writeBuffer(bytes) + local packet = ProtocolManager.read(byteBuffer) + return packet +end + +function initProtocol() + local ObjectA = require("LuaProtocol.Packet.ObjectA") + local ObjectB = require("LuaProtocol.Packet.ObjectB") + local ComplexObject = require("LuaProtocol.Packet.ComplexObject") + local NormalObject = require("LuaProtocol.Packet.NormalObject") + local SimpleObject = require("LuaProtocol.Packet.SimpleObject") + protocols[1116] = ObjectA + protocols[1117] = ObjectB + protocols[1160] = ComplexObject + protocols[1161] = NormalObject + protocols[1163] = SimpleObject +end + +ProtocolManager.initProtocol = initProtocol +return ProtocolManager diff --git a/protocol/src/test/resources/luaTest/LuaProtocolTest.cs b/protocol/src/test/resources/luaTest/LuaProtocolTest.cs new file mode 100644 index 00000000..31dc6a1c --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocolTest.cs @@ -0,0 +1,47 @@ +using System.IO; +using System.Text; +using NUnit.Framework; +using XLua; + +namespace Test.Editor.LuaTest +{ + public class LuaProtocolTest + { + public static readonly string TEST_PATH = "Assets/Test/Editor/LuaTest/"; + + [Test] + public void ComplexObjectTest() + { + // 获取复杂对象的字节流 + var complexObjectBytes = File.ReadAllBytes("D:\\zfoo\\protocol\\src\\test\\resources\\ComplexObject.bytes"); + + var luaEnv = new LuaEnv(); + var luaDebugBuilder = new StringBuilder(); + // Rider的断点调试 + // luaDebugBuilder.Append("package.cpath = package.cpath .. ';C:/Users/jm/AppData/Roaming/JetBrains/Rider2020.1/plugins/intellij-emmylua/classes/debugger/emmy/windows/x64/?.dll'").Append(FileUtils.LS); + // luaDebugBuilder.Append("local dbg = require('emmy_core')").Append(FileUtils.LS); + // luaDebugBuilder.Append("dbg.tcpListen('localhost', 9966)").Append(FileUtils.LS); + // luaDebugBuilder.Append("dbg.waitIDE()").Append(FileUtils.LS); + + luaEnv.DoString(luaDebugBuilder.ToString()); + + luaEnv.AddLoader(CustomLoader); + + var luaProtocolTestStr = File.ReadAllText(TEST_PATH + "LuaProtocolTest.lua"); + luaEnv.DoString(luaProtocolTestStr, "LuaProtocolTest"); + + LuaFunction byteBufferTestFunction = luaEnv.Global.Get("byteBufferTest"); + byteBufferTestFunction.Call(); + + LuaFunction complexObjectTestFuction = luaEnv.Global.Get("complexObjectTest"); + complexObjectTestFuction.Call(complexObjectBytes); + } + + public static byte[] CustomLoader(ref string filepath) + { + filepath = filepath.Replace(".", "/") + ".lua"; + + return File.ReadAllBytes(TEST_PATH + filepath); + } + } +} \ No newline at end of file diff --git a/protocol/src/test/resources/luaTest/LuaProtocolTest.lua b/protocol/src/test/resources/luaTest/LuaProtocolTest.lua new file mode 100644 index 00000000..60278de5 --- /dev/null +++ b/protocol/src/test/resources/luaTest/LuaProtocolTest.lua @@ -0,0 +1,137 @@ +local ByteBuffer = require("LuaProtocol.Buffer.ByteBuffer") +local ProtocolManager = require("LuaProtocol.ProtocolManager") + + +-------------------------------------ProtocolManager的测试------------------------------------- +function complexObjectTest(bytes) + ProtocolManager.initProtocol() + + local byteBuffer = ByteBuffer:new() + byteBuffer:writeBuffer(bytes) + local packet = ProtocolManager.read(byteBuffer) + + local newByteBuffer = ByteBuffer:new() + ProtocolManager.write(newByteBuffer, packet) + assert(#byteBuffer.buffer == #newByteBuffer.buffer) + + -- set和map是无序的,所以有的时候输入和输出的字节流有可能不一致,但是长度一定是一致的 + --for i = 1, #byteBuffer.buffer do + -- print(i) + -- assert(byteBuffer.buffer[i] == newByteBuffer.buffer[i], i) + --end + + local newPacket = ProtocolManager.read(newByteBuffer) + return packet +end + + + +-------------------------------------ByteBuffer的测试------------------------------------- +function byteBufferTest() + local byteBuffer = ByteBuffer:new() + + byteBuffer:writeBoolean(true) + byteBuffer:writeBoolean(false) + assert(byteBuffer:readBoolean() == true) + assert(byteBuffer:readBoolean() == false) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeUByte(99) + byteBuffer:writeUByte(128) + assert(byteBuffer:readUByte() == 99) + assert(byteBuffer:readUByte() == 128) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeByte(127) + byteBuffer:writeByte(-128) + assert(byteBuffer:readByte() == 127) + assert(byteBuffer:readByte() == -128) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeShort(32767) + byteBuffer:writeShort(0) + byteBuffer:writeShort(-32768) + assert(byteBuffer:readShort() == 32767) + assert(byteBuffer:readShort() == 0) + assert(byteBuffer:readShort() == -32768) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeInt(2147483647) + byteBuffer:writeInt(-999999) + byteBuffer:writeInt(0) + byteBuffer:writeInt(999999) + byteBuffer:writeInt(-2147483648) + assert(byteBuffer:readInt() == 2147483647) + assert(byteBuffer:readInt() == -999999) + assert(byteBuffer:readInt() == 0) + assert(byteBuffer:readInt() == 999999) + assert(byteBuffer:readInt() == -2147483648) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeLuaNumber(1234.5678) + byteBuffer:writeLuaNumber(0) + byteBuffer:writeLuaNumber(-2147483648) + assert(math.abs(byteBuffer:readLuaNumber() - 1234.5678) < 0.001) + assert(byteBuffer:readLuaNumber() == 0) + assert(byteBuffer:readLuaNumber() == -2147483648) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeLong(math.mininteger) + byteBuffer:writeLong(-9223372036854775807) + byteBuffer:writeLong(-9999999999999999) + byteBuffer:writeLong(-99999999) + byteBuffer:writeLong(0) + byteBuffer:writeLong(99999999) + byteBuffer:writeLong(9999999999999999) + byteBuffer:writeLong(9223372036854775807) + assert(byteBuffer:readLong() == math.mininteger) + assert(byteBuffer:readLong() == -9223372036854775807) + assert(byteBuffer:readLong() == -9999999999999999) + assert(byteBuffer:readLong() == -99999999) + assert(byteBuffer:readLong() == 0) + assert(byteBuffer:readLong() == 99999999) + assert(byteBuffer:readLong() == 9999999999999999) + assert(byteBuffer:readLong() == 9223372036854775807) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeFloat(0x0.000002P-126) + byteBuffer:writeFloat(0) + byteBuffer:writeFloat(1234.5678) + byteBuffer:writeFloat(0x1.fffffeP+127) + assert(byteBuffer:readFloat() == 0x0.000002P-126) + assert(byteBuffer:readFloat() == 0) + assert(math.abs(byteBuffer:readFloat() - 1234.5678) < 0.001) + assert(byteBuffer:readFloat() == 0x1.fffffeP+127) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + byteBuffer:writeDouble(0x0.0000000000001P-1022) + byteBuffer:writeDouble(0) + byteBuffer:writeDouble(1234.5678) + byteBuffer:writeDouble(0x1.fffffffffffffP+1023) + assert(byteBuffer:readDouble() == 0x0.0000000000001P-1022) + assert(byteBuffer:readDouble() == 0) + assert(math.abs(byteBuffer:readDouble() - 1234.5678) < 0.001) + assert(byteBuffer:readDouble() == 0x1.fffffffffffffP+1023) + byteBuffer:setWriteOffset(1) + byteBuffer:setReadOffset(1) + + local s = "你好 hello world" + byteBuffer:writeString(s) + assert(byteBuffer:readString() == s) + + byteBuffer:writeChar(s) + assert(byteBuffer:readChar() == "你") + byteBuffer:setWriteOffset(0) + byteBuffer:setReadOffset(0) + + + print("----------------------------------------------------") +end diff --git a/protocol/src/test/resources/speed.proto b/protocol/src/test/resources/speed.proto new file mode 100644 index 00000000..db78d39a --- /dev/null +++ b/protocol/src/test/resources/speed.proto @@ -0,0 +1,169 @@ +syntax = "proto3"; + +option java_package = "com.zfoo.protocol.packet"; +option java_outer_classname = "ProtobufObject"; + +// protoc -I=D:\zfoo\protocol\src\test\resources --java_out=D:\zfoo\protocol\src\test\java D:\zfoo\protocol\src\test\resources\speed.proto + +message ObjectB { + bool flag = 1; +} + +message ObjectA { + int32 a = 1; + map m = 2; + ObjectB objectB = 3; +} + +message ListInteger { + repeated int32 a = 1; +} + +message ListListInteger { + repeated ListInteger a = 1; +} + +message ListListListInteger { + repeated ListListInteger a = 1; +} + +message ListObjectA { + repeated ObjectA a = 1; +} + +message ListListObjectA { + repeated ListObjectA a = 1; +} + +message MapObjectA { + ObjectA key = 1; + ListInteger value = 2; +} + +message MapListListObjectA { + ListListObjectA key = 1; + ListListListInteger value = 2; +} + +message MapIntegerString { + map a = 1; +} + +message ListMapIntegerString { + repeated MapIntegerString a = 1; +} + +message MapListMapInteger { + ListMapIntegerString key = 1; + ListMapIntegerString value = 2; +} + +message ProtobufComplexObject { + // protobuf不支持单个byte,用int代替,增加了一点性能开销 + int32 a = 1; + int32 aa = 2; + bytes aaa = 3; + bytes aaaa = 4; + + // protobuf不支持单个short,用int代替,增加了一点性能开销 + int32 b = 5; + int32 bb = 6; + // protobuf不支持单个short,用bytes代替,减少了一点性能开销 + bytes bbb = 7; + bytes bbbb = 8; + + int32 c = 9; + int32 cc = 10; + repeated int32 ccc = 11; + repeated int32 cccc = 12; + + int64 d = 13; + int64 dd = 14; + repeated int64 ddd = 15; + repeated int64 dddd = 16; + + float e = 17; + float ee = 18; + repeated float eee = 19; + repeated float eeee = 20; + + double f = 21; + double ff = 22; + repeated double fff = 23; + repeated double ffff = 24; + + bool g = 25; + bool gg = 26; + repeated bool ggg = 27; + repeated bool gggg = 28; + + // protobuf不支持char,用string代替,增加了一点性能开销 + string h = 29; + string hh = 30; + repeated string hhh = 31; + repeated string hhhh = 32; + + string jj = 33; + repeated string jjj = 34; + + ObjectA kk = 35; + repeated ObjectA kkk = 36; + + repeated int32 l = 37; + // protobuf不支持嵌套repeated,用消息代替,减少了一点性能开销 + repeated ListListInteger ll = 38; + repeated ListObjectA lll = 39; + repeated string llll = 40; + repeated MapIntegerString lllll = 41; + + map m = 51; + map mm = 52; + repeated MapObjectA mmm = 53; // protobuf不支持map的key为对象,用数组代替,减少了很多性能开销 + repeated MapListListObjectA mmmm = 54; // protobuf不支持map的key为对象,用数组代替,减少了很多性能开销 + repeated MapListMapInteger mmmmm = 55; // protobuf不支持map的key为对象,用数组代替,不支持set,用list代替,减少了很多性能开销 + + + repeated int32 s = 61; // protobuf不支持set,用数组代替,减少了很多性能开销 + repeated ListListInteger ss = 62; // protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销 + repeated ListObjectA sss = 63; // protobuf不支持嵌套set和list,用对象代替,减少了很多性能开销 + repeated string ssss = 64; // protobuf不支持set,用数组代替,减少了很多性能开销 + repeated MapIntegerString sssss = 65; // protobuf不支持set和嵌套map,用对象代替,减少了很多性能开销 +} + + +message ProtobufNormalObject { + int32 a = 1; + bytes aaa = 3; + + int32 b = 5; + + int32 c = 9; + + int64 d = 13; + + float e = 17; + + double f = 21; + + bool g = 25; + + string jj = 33; + + ObjectA kk = 35; + + repeated int32 l = 37; + repeated int64 ll = 38; + repeated ObjectA lll = 39; + repeated string llll = 40; + + map m = 51; + map mm = 52; + + repeated int32 s = 61; + repeated string ssss = 64; +} + +message ProtobufSimpleObject { + int32 c = 9; + bool g = 25; +} \ No newline at end of file diff --git a/scheduler/README.md b/scheduler/README.md new file mode 100644 index 00000000..7686fbc4 --- /dev/null +++ b/scheduler/README.md @@ -0,0 +1,35 @@ +### Ⅰ. 注意事项 + +- 每秒钟执行一次SchedulerManager.triggerPerSecond()方法,循环遍历可执行的scheduler +- triggerPerSecond在计算下一次triggerTimestamp会很耗时间,所以尽量避免配置一秒或者间隔时间很短的cron表达式 +- SchedulerManager的executor只有一条线程,所以使用者要避免做耗时和阻塞的运算,如果有这样的需求可以抛到其它线程池 +- 时间可以向前调,也可以向后调,都会出发trigger + +### Ⅱ. Cron Expression Example + +``` +30 * * * * ? 每半分钟触发任务 +30 10 * * * ? 每小时的10分30秒触发任务 +30 10 1 * * ? 每天1点10分30秒触发任务 +30 10 1 20 * ? 每月20号1点10分30秒触发任务 +30 10 1 20 10 ? * 每年10月20号1点10分30秒触发任务 +30 10 1 20 10 ? 2018 2018年10月20号1点10分30秒触发任务 +30 10 1 ? 10 * 2018 2018年10月每天20号1点10分30秒触发任务 +30 10 1 ? 10 SUN 2018 2018年10月每周日1点10分30秒触发任务 +15,30,45 * * * * ? 每分钟的15,30,45秒个触发一次 +15-45 * * * * ? 每分钟的15秒到45秒内,每秒都触发一次 +15/5 * * * * ? 每分钟的15秒开始触发,每隔5秒触发一次 +15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次 +0 0/3 * * * ? 每小时的第0分0秒开始,没三分钟触发一次 +0 15 10 ? * MON-FRI 星期一到星期五每天10点15分0秒触发一次 +0 15 10 L * ? 每个月的最后一天的10点15分0秒触发任务 +0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发任务 +0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发任务 +0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发任务 + +说明: +*(星号):代表任何时刻都接受癿意思 +,(逗号):代表分隔时段的意思 +-(减号):代表一段时间范围内 +/n(斜线):每隔n单位间隔 +``` diff --git a/scheduler/pom.xml b/scheduler/pom.xml new file mode 100644 index 00000000..06025de6 --- /dev/null +++ b/scheduler/pom.xml @@ -0,0 +1,243 @@ + + + 4.0.0 + + com.zfoo + scheduler + 3.0 + + jar + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + + com.zfoo + util + ${zfoo.util.version} + + + + io.netty + netty-all + ${netty.version} + + + + + org.javassist + javassist + ${javassist.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + diff --git a/scheduler/src/main/java/com/zfoo/scheduler/SchedulerContext.java b/scheduler/src/main/java/com/zfoo/scheduler/SchedulerContext.java new file mode 100644 index 00000000..2306b910 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/SchedulerContext.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler; + +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.scheduler.manager.ISchedulerManager; +import com.zfoo.scheduler.manager.SchedulerManager; +import com.zfoo.scheduler.schema.SchedulerRegisterProcessor; +import com.zfoo.util.ThreadUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; + +import java.lang.reflect.Field; +import java.util.concurrent.ScheduledExecutorService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SchedulerContext implements ApplicationListener, Ordered { + + private static final Logger logger = LoggerFactory.getLogger(SchedulerContext.class); + + private static SchedulerContext instance; + + private static boolean stop = false; + + private ApplicationContext applicationContext; + + + public static SchedulerContext getSchedulerContext() { + return instance; + } + + public static ApplicationContext getApplicationContext() { + return instance.applicationContext; + } + + + public static ISchedulerManager getSchedulerManager() { + return SchedulerManager.getInstance(); + } + + public static boolean isStop() { + return stop; + } + + + public synchronized static void shutdown() { + if (stop) { + return; + } + + stop = true; + + ISchedulerManager schedulerManager = getSchedulerManager(); + if (schedulerManager == null) { + return; + } + + try { + Field field = SchedulerManager.class.getDeclaredField("executor"); + ReflectionUtils.makeAccessible(field); + var executor = (ScheduledExecutorService) ReflectionUtils.getField(field, schedulerManager); + ThreadUtils.shutdown(executor); + } catch (Throwable e) { + logger.error("Scheduler thread pool failed shutdown.", e); + return; + } + + logger.info("Scheduler shutdown gracefully."); + } + + + @Override + public void onApplicationEvent(ApplicationContextEvent event) { + if (event instanceof ContextRefreshedEvent) { + if (instance != null) { + return; + } + // 初始化上下文 + SchedulerContext.instance = this; + instance.applicationContext = event.getApplicationContext(); + + var beanNames = applicationContext.getBeanDefinitionNames(); + var processor = applicationContext.getBean(SchedulerRegisterProcessor.class); + + for (var beanName : beanNames) { + processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName); + } + + } else if (event instanceof ContextClosedEvent) { + shutdown(); + } + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/manager/ISchedulerManager.java b/scheduler/src/main/java/com/zfoo/scheduler/manager/ISchedulerManager.java new file mode 100644 index 00000000..97237ef6 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/manager/ISchedulerManager.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.manager; + +import java.util.concurrent.TimeUnit; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface ISchedulerManager { + + /** + * 不断执行的周期循环任务 + */ + void scheduleAtFixedRate(Runnable runnable, long period, TimeUnit unit); + + /** + * 固定延迟执行的任务 + */ + void schedule(Runnable runnable, long delay, TimeUnit unit); + + + /** + * cron表达式执行的任务 + */ + void scheduleCron(Runnable runnable, String cron); + +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/manager/SchedulerManager.java b/scheduler/src/main/java/com/zfoo/scheduler/manager/SchedulerManager.java new file mode 100644 index 00000000..39d069e6 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/manager/SchedulerManager.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.manager; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.SchedulerContext; +import com.zfoo.scheduler.model.anno.Scheduler; +import com.zfoo.scheduler.model.vo.SchedulerDefinition; +import com.zfoo.scheduler.util.TimeUtils; +import io.netty.util.concurrent.FastThreadLocalThread; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Modifier; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SchedulerManager implements ISchedulerManager { + + private static final Logger logger = LoggerFactory.getLogger(SchedulerManager.class); + + private static final SchedulerManager INSTANCE = new SchedulerManager(); + + private static final List schedulerDefList = new CopyOnWriteArrayList<>(); + + /** + * 上一次trigger触发时间 + */ + private static long lastTriggerTimestamp = 0; + + + /** + * 在scheduler中,最小的triggerTimestamp + */ + private static long minSchedulerTriggerTimestamp = 0; + + + private static final long TRIGGER_MILLIS_INTERVAL = TimeUtils.MILLIS_PER_SECOND; + private static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new SchedulerThreadFactory()); + + + private static class SchedulerThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + // scheduler-p1-t1 = scheduler-pool-1-thread-1 + SchedulerThreadFactory() { + var s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); + namePrefix = "scheduler-p" + poolNumber.getAndIncrement() + "-t"; + } + + @Override + public Thread newThread(Runnable runnable) { + var t = new FastThreadLocalThread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0); + t.setDaemon(false); + t.setPriority(Thread.NORM_PRIORITY); + t.setUncaughtExceptionHandler((thread, e) -> { + logger.error(thread.toString(), e); + }); + return t; + } + } + + static { + executor.scheduleAtFixedRate(() -> { + try { + triggerPerSecond(); + } catch (Exception e) { + logger.error("scheduler triggers an error.", e); + } + }, TimeUtils.MILLIS_PER_SECOND, TRIGGER_MILLIS_INTERVAL, TimeUnit.MILLISECONDS); + } + + public static SchedulerManager getInstance() { + return INSTANCE; + } + + private SchedulerManager() { + } + + private static long minSchedulerTriggerTimestamp() { + var minSchedulerOptional = schedulerDefList.stream().min(Comparator.comparingLong(schedulerDef -> schedulerDef.getTriggerTimestamp())); + if (minSchedulerOptional.isPresent()) { + return minSchedulerOptional.get().getTriggerTimestamp(); + } else { + logger.error("schedulerDefList:[{}] has no minSchedulerTriggerTimestamp to return. ", JsonUtils.object2String(schedulerDefList)); + return 0; + } + } + + /** + * 每一秒执行一次,如果这个任务执行时间过长超过,比如10秒,执行完成后,不会再执行10次 + */ + private static void triggerPerSecond() { + var timestamp = TimeUtils.currentTimeMillis(); + + if (CollectionUtils.isEmpty(schedulerDefList)) { + return; + } + + /* + 有人向前调整过机器时间,重新计算scheduler里的triggerTimestamp + */ + // var diff = timestamp - lastTriggerTimestamp; + if (timestamp < lastTriggerTimestamp) { + for (SchedulerDefinition schedulerDef : schedulerDefList) { + var nextTriggerTimestamp = TimeUtils.getNextTimestampByCronExpression(schedulerDef.getCronExpression(), timestamp); + schedulerDef.setTriggerTimestamp(nextTriggerTimestamp); + } + minSchedulerTriggerTimestamp = minSchedulerTriggerTimestamp(); + } + + // diff > 0, 没有人调整时间或者有人向后调整过机器时间,可以忽略,因为向后调整时间时间戳一定会大于triggerTimestamp,所以一定会触发 + lastTriggerTimestamp = timestamp; + + // 如果minSchedulerTriggerTimestamp大于timestamp,说明没有可执行的scheduler + if (timestamp < minSchedulerTriggerTimestamp) { + return; + } + + for (var schedulerDef : schedulerDefList) { + if (timestamp >= schedulerDef.getTriggerTimestamp()) { + // 到达触发时间,则执行runnable方法 + schedulerDef.getScheduler().invoke(); + // 重新设置下一次的触发时间戳 + var nextTriggerTimestamp = TimeUtils.getNextTimestampByCronExpression(schedulerDef.getCronExpression(), timestamp); + schedulerDef.setTriggerTimestamp(nextTriggerTimestamp); + } + } + minSchedulerTriggerTimestamp = minSchedulerTriggerTimestamp(); + } + + public void registerScheduler(Object bean) { + try { + var methods = ReflectionUtils.getMethodsByAnnoInPOJOClass(bean.getClass(), Scheduler.class); + for (var method : methods) { + var scheduler = method.getAnnotation(Scheduler.class); + + var paramClazzs = method.getParameterTypes(); + if (paramClazzs.length >= 1) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] can not have any parameters", bean.getClass(), method.getName())); + } + + var methodName = method.getName(); + + if (!Modifier.isPublic(method.getModifiers())) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must use 'public' as modifier!", bean.getClass().getName(), methodName)); + } + + if (Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] can not use 'static' as modifier!", bean.getClass().getName(), methodName)); + } + + if (!methodName.startsWith("cron")) { + throw new IllegalArgumentException(StringUtils.format("[class:{}] [method:{}] must start with 'cron' as method name!" + , bean.getClass().getName(), methodName)); + } + + var schedulerDef = SchedulerDefinition.valueOf(scheduler.cron(), bean, method); + schedulerDefList.add(schedulerDef); + minSchedulerTriggerTimestamp = minSchedulerTriggerTimestamp(); + } + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + + @Override + public void scheduleAtFixedRate(Runnable runnable, long period, TimeUnit unit) { + if (SchedulerContext.isStop()) { + return; + } + + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } catch (Exception e) { + logger.error("scheduleAtFixedRate未知exception异常", e); + } catch (Throwable t) { + logger.error("scheduleAtFixedRate未知error异常", t); + } + } + }, 0, period, unit); + } + + @Override + public void schedule(Runnable runnable, long delay, TimeUnit unit) { + if (SchedulerContext.isStop()) { + return; + } + + executor.schedule(new Runnable() { + @Override + public void run() { + try { + runnable.run(); + } catch (Exception e) { + logger.error("schedule未知exception异常", e); + } catch (Throwable t) { + logger.error("schedule未知error异常", t); + } + } + }, delay, unit); + } + + @Override + public void scheduleCron(Runnable runnable, String cron) { + if (SchedulerContext.isStop()) { + return; + } + + schedulerDefList.add(SchedulerDefinition.valueOf(cron, runnable)); + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/model/anno/Scheduler.java b/scheduler/src/main/java/com/zfoo/scheduler/model/anno/Scheduler.java new file mode 100644 index 00000000..4e61defd --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/model/anno/Scheduler.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.model.anno; + +import java.lang.annotation.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface Scheduler { + + String cron(); + +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/model/vo/EnhanceUtils.java b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/EnhanceUtils.java new file mode 100644 index 00000000..5772917e --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/EnhanceUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.model.vo; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.schema.NamespaceHandler; +import com.zfoo.util.security.IdUtils; +import javassist.*; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class EnhanceUtils { + + static { + // 适配Tomcat,因为Tomcat不是用的默认的类加载器,而Javaassist用的是默认的加载器 + var classArray = new Class[]{ + IScheduler.class + }; + + var classPool = ClassPool.getDefault(); + + for (var clazz : classArray) { + if (classPool.find(clazz.getCanonicalName()) == null) { + ClassClassPath classPath = new ClassClassPath(clazz); + classPool.insertClassPath(classPath); + } + } + } + + public static IScheduler createScheduler(ReflectScheduler reflectScheduler) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + var classPool = ClassPool.getDefault(); + + Object bean = reflectScheduler.getBean(); + Method method = reflectScheduler.getMethod(); + + // 定义类名称 + CtClass enhanceClazz = classPool.makeClass(EnhanceUtils.class.getCanonicalName() + StringUtils.capitalize(NamespaceHandler.SCHEDULER) + IdUtils.getLocalIntId()); + enhanceClazz.addInterface(classPool.get(IScheduler.class.getCanonicalName())); + + // 定义类中的一个成员 + CtField field = new CtField(classPool.get(bean.getClass().getCanonicalName()), "bean", enhanceClazz); + field.setModifiers(Modifier.PRIVATE); + enhanceClazz.addField(field); + + // 定义类的构造器 + CtConstructor constructor = new CtConstructor(classPool.get(new String[]{bean.getClass().getCanonicalName()}), enhanceClazz); + constructor.setBody("{this.bean=$1;}"); + constructor.setModifiers(Modifier.PUBLIC); + enhanceClazz.addConstructor(constructor); + + // 定义类实现的接口方法 + CtMethod invokeMethod = new CtMethod(classPool.get(void.class.getCanonicalName()), "invoke", null, enhanceClazz); + invokeMethod.setModifiers(Modifier.PUBLIC + Modifier.FINAL); + String invokeMethodBody = "{this.bean." + method.getName() + "();}"; + invokeMethod.setBody(invokeMethodBody); + enhanceClazz.addMethod(invokeMethod); + + // 释放缓存 + enhanceClazz.detach(); + + Class resultClazz = enhanceClazz.toClass(IScheduler.class); + Constructor resultConstructor = resultClazz.getConstructor(bean.getClass()); + return (IScheduler) resultConstructor.newInstance(bean); + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/model/vo/IScheduler.java b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/IScheduler.java new file mode 100644 index 00000000..6361288b --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/IScheduler.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.model.vo; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IScheduler { + + + void invoke(); + +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/model/vo/ReflectScheduler.java b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/ReflectScheduler.java new file mode 100644 index 00000000..a2da28e3 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/ReflectScheduler.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.model.vo; + +import com.zfoo.protocol.util.ReflectionUtils; + +import java.lang.reflect.Method; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ReflectScheduler implements IScheduler { + + private Object bean; + + private Method method; + + public static ReflectScheduler valueOf(Object bean, Method method) { + var scheduler = new ReflectScheduler(); + scheduler.bean = bean; + scheduler.method = method; + return scheduler; + } + + @Override + public void invoke() { + ReflectionUtils.invokeMethod(bean, method); + } + + public Object getBean() { + return bean; + } + + public Method getMethod() { + return method; + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/model/vo/RunnableScheduler.java b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/RunnableScheduler.java new file mode 100644 index 00000000..2eaf76da --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/RunnableScheduler.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.model.vo; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RunnableScheduler implements IScheduler { + + private Runnable runnable; + + public static RunnableScheduler valueOf(Runnable runnable) { + var scheduler = new RunnableScheduler(); + scheduler.runnable = runnable; + return scheduler; + } + + @Override + public void invoke() { + runnable.run(); + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/model/vo/SchedulerDefinition.java b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/SchedulerDefinition.java new file mode 100644 index 00000000..9013c9e8 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/model/vo/SchedulerDefinition.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.model.vo; + +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.scheduler.util.TimeUtils; +import javassist.CannotCompileException; +import javassist.NotFoundException; +import org.springframework.scheduling.support.CronExpression; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SchedulerDefinition { + + private CronExpression cronExpression; + + private IScheduler scheduler; + + /** + * 触发时间戳,只要当前时间戳大于这个触发事件戳都视为可以触发 + */ + private long triggerTimestamp; + + public static SchedulerDefinition valueOf(String cron, Object bean, Method method) throws NoSuchMethodException, IllegalAccessException, InstantiationException, CannotCompileException, NotFoundException, InvocationTargetException { + var schedulerDef = new SchedulerDefinition(); + var cronExpression = CronExpression.parse(cron); + schedulerDef.cronExpression = cronExpression; + // 字节码增强,避免反射 + schedulerDef.scheduler = EnhanceUtils.createScheduler(ReflectScheduler.valueOf(bean, method)); + schedulerDef.triggerTimestamp = TimeUtils.getNextTimestampByCronExpression(cronExpression, TimeUtils.currentTimeMillis()); + ReflectionUtils.makeAccessible(method); + return schedulerDef; + } + + public static SchedulerDefinition valueOf(String cron, Runnable runnable) { + var schedulerDef = new SchedulerDefinition(); + var cronExpression = CronExpression.parse(cron); + schedulerDef.cronExpression = cronExpression; + schedulerDef.scheduler = RunnableScheduler.valueOf(runnable); + schedulerDef.triggerTimestamp = TimeUtils.getNextTimestampByCronExpression(cronExpression, TimeUtils.currentTimeMillis()); + return schedulerDef; + } + + public CronExpression getCronExpression() { + return cronExpression; + } + + public void setCronExpression(CronExpression cronExpression) { + this.cronExpression = cronExpression; + } + + public IScheduler getScheduler() { + return scheduler; + } + + public void setScheduler(IScheduler scheduler) { + this.scheduler = scheduler; + } + + public long getTriggerTimestamp() { + return triggerTimestamp; + } + + public void setTriggerTimestamp(long triggerTimestamp) { + this.triggerTimestamp = triggerTimestamp; + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/schema/NamespaceHandler.java b/scheduler/src/main/java/com/zfoo/scheduler/schema/NamespaceHandler.java new file mode 100644 index 00000000..8a125e41 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/schema/NamespaceHandler.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.schema; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NamespaceHandler extends NamespaceHandlerSupport { + + public static final String SCHEDULER = "scheduler"; + + @Override + public void init() { + registerBeanDefinitionParser(SCHEDULER, new SchedulerDefinitionParser()); + } +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerDefinitionParser.java b/scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerDefinitionParser.java new file mode 100644 index 00000000..169ffc94 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerDefinitionParser.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.schema; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.scheduler.SchedulerContext; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.w3c.dom.Element; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SchedulerDefinitionParser implements BeanDefinitionParser { + + private final String SCHEDULER_ID = "id"; + + @Override + public AbstractBeanDefinition parse(Element element, ParserContext parserContext) { + Class clazz; + String name; + BeanDefinitionBuilder builder; + + // 注册SchedulerSpringContext + clazz = SchedulerContext.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册SchedulerRegisterProcessor + clazz = SchedulerRegisterProcessor.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注册SchedulerManager + String schedulerId = element.getAttribute(SCHEDULER_ID); + return builder.getBeanDefinition(); + } + +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerRegisterProcessor.java b/scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerRegisterProcessor.java new file mode 100644 index 00000000..1e594e9d --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/schema/SchedulerRegisterProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.schema; + +import com.zfoo.scheduler.SchedulerContext; +import com.zfoo.scheduler.manager.SchedulerManager; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class SchedulerRegisterProcessor implements BeanPostProcessor { + + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (SchedulerContext.getSchedulerContext() == null) { + return bean; + } + SchedulerManager schedulerManager = (SchedulerManager) SchedulerContext.getSchedulerManager(); + schedulerManager.registerScheduler(bean); + return bean; + } + +} diff --git a/scheduler/src/main/java/com/zfoo/scheduler/util/TimeUtils.java b/scheduler/src/main/java/com/zfoo/scheduler/util/TimeUtils.java new file mode 100644 index 00000000..44a06d04 --- /dev/null +++ b/scheduler/src/main/java/com/zfoo/scheduler/util/TimeUtils.java @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.util; + +import com.zfoo.scheduler.SchedulerContext; +import com.zfoo.scheduler.manager.SchedulerManager; +import io.netty.util.concurrent.FastThreadLocal; +import org.springframework.scheduling.support.CronExpression; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.*; +import java.time.temporal.TemporalAdjusters; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class TimeUtils { + + private static long timestamp = System.currentTimeMillis(); + + // 一秒钟对应的纳秒数 + public static final long NANO_PER_SECOND = 1_000_000_000; + // 一秒钟对应的毫秒数 + public static final long MILLIS_PER_SECOND = 1 * 1000; + // 一分钟对应的毫秒数 + public static final long MILLIS_PER_MINUTE = 1 * 60 * MILLIS_PER_SECOND; + // 一个小时对应的毫秒数 + public static final long MILLIS_PER_HOUR = 1 * 60 * MILLIS_PER_MINUTE; + // 一天对应的毫秒数 + public static final long MILLIS_PER_DAY = 1 * 24 * MILLIS_PER_HOUR; + // 一周对应的毫秒数 + public static final long MILLIS_PER_WEEK = 1 * 7 * MILLIS_PER_HOUR; + // 默认的时区 + public static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault(); + // 默认的时区Id + public static final ZoneId DEFAULT_ZONE_ID = TimeZone.getDefault().toZoneId(); + + + // 统一的时间格式模板 + public static final String DATE_FORMAT_TEMPLATE = "yyyy-MM-dd HH:mm:ss"; + // 简单的时间格式模板 + public static final String SIMPLE_DATE_FORMAT_TEMPLATE = "yyyyMMddHHmmss"; + // 统一的时间格式模板 + public static final String DATE_FORMAT_TEMPLATE_FOR_DAY = "yyyy-MM-dd"; + + private static final FastThreadLocal DATE_FORMAT = new FastThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(DATE_FORMAT_TEMPLATE); + } + }; + + private static final FastThreadLocal SIMPLE_DATE_FORMAT = new FastThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(SIMPLE_DATE_FORMAT_TEMPLATE); + } + }; + + private static final FastThreadLocal DATE_FORMAT_FOR_DAY = new FastThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(DATE_FORMAT_TEMPLATE_FOR_DAY); + } + }; + + static { + SchedulerContext.getSchedulerManager(); + SchedulerManager.getInstance(); + } + + /** + * 获取精确的时间戳 + */ + public static long currentTimeMillis() { + timestamp = System.currentTimeMillis(); + return timestamp; + } + + /** + * 获取最多只有一秒延迟的粗略时间戳,适用于对时间精度要求不高的场景,最多只有一秒误差 + *

+ * 比System.currentTimeMillis()的性能高10倍 + */ + public static long now() { + return timestamp; + } + + // --------------------------------------日期格式-------------------------------------- + + /** + * 把日期字符串转换成Date对象,统一的日期模板要遵循DATE_FORMAT_TEMPLATE="yyyy-MM-dd HH:mm:ss" + * + * @param dateString 日期字符串,如:2018-02-12 10:12:50 + * @return Date + * @throws ParseException 解析异常 + */ + public static Date stringToDate(String dateString) throws ParseException { + return DATE_FORMAT.get().parse(dateString); + } + + /** + * 把long类型的时间戳转换成字符串格式的时间 + * + * @param time 时间戳 + * @return yyyy-MM-dd HH:mm:ss + */ + public static String timeToString(long time) { + return dateToString(new Date(time)); + } + + public static String dateToString(Date date) { + return DATE_FORMAT.get().format(date); + } + + public static String simpleDateString() { + return SIMPLE_DATE_FORMAT.get().format(new Date(now())); + } + + public static Date dayStringToDate(String dateString) throws ParseException { + return DATE_FORMAT_FOR_DAY.get().parse(dateString); + } + // --------------------------------------日期判断-------------------------------------- + + /** + *

Checks if two date objects are on the same day ignoring time.

+ * + *

28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true. + * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false. + *

+ * + * @param date1 the first date, not altered, not null + * @param date2 the second date, not altered, not null + * @return true if they represent the same day + * @throws IllegalArgumentException if either date is null + */ + public static boolean isSameDay(Date date1, Date date2) { + if (date1 == null || date2 == null) { + throw new IllegalArgumentException("The date must not be null"); + } + var cal1 = Calendar.getInstance(); + cal1.setTime(date1); + var cal2 = Calendar.getInstance(); + cal2.setTime(date2); + return isSameDay(cal1, cal2); + } + + public static boolean isSameDay(long time1, long time2) { + var cal1 = Calendar.getInstance(); + cal1.setTimeInMillis(time1); + var cal2 = Calendar.getInstance(); + cal2.setTimeInMillis(time2); + return isSameDay(cal1, cal2); + } + + /** + *

Checks if two calendar objects are on the same day ignoring time.

+ * + *

28 Mar 2002 13:45 and 28 Mar 2002 06:01 would return true. + * 28 Mar 2002 13:45 and 12 Mar 2002 13:45 would return false. + *

+ * + * @param cal1 the first calendar, not altered, not null + * @param cal2 the second calendar, not altered, not null + * @return true if they represent the same day + * @throws IllegalArgumentException if either calendar is null + */ + public static boolean isSameDay(Calendar cal1, Calendar cal2) { + if (cal1 == null || cal2 == null) { + throw new IllegalArgumentException("The date must not be null"); + } + return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) && + cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR); + } + + public static String dateFormatForDayString(long time) { + return DATE_FORMAT_FOR_DAY.get().format(new Date(time)); + } + + /** + * 判断两个日期是否是同一周,设置周一为一周的第一天 + *

+ * 2004-12-25”是星期六,也就是说它是2004年中第52周的星期六,那么“2004-12-26”到底是2004年的第几周哪,java中经测试取得的它的Week值是1, + * 那么也就是说它被看作2005年的第一周了,这个处理是比较好的。可以用来判断“2004-12-26”和“2005-1-1”是同一周。 + */ + public static boolean isSameWeek(long time1, long time2) { + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + cal1.setTimeInMillis(time1); + cal2.setTimeInMillis(time2); + cal1.setFirstDayOfWeek(Calendar.MONDAY); + cal2.setFirstDayOfWeek(Calendar.MONDAY); + + int yearDiff = cal1.get(Calendar.YEAR) - cal2.get(Calendar.YEAR); + if (yearDiff == 0 && cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) { + // yearDiff==0,说明是同一年 + if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) { + return true; + } + } else if (yearDiff == 1 && cal2.get(Calendar.MONTH) == 11) { + //yearDiff==1,说明cal比cal2大一年;java的一月用"0"标识,那么12月用"11" + if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) { + return true; + } + } else if (yearDiff == -1 && cal1.get(Calendar.MONTH) == 11) { + //yearDiff==-1,说明cal比cal2小一年 + if (cal1.get(Calendar.WEEK_OF_YEAR) == cal2.get(Calendar.WEEK_OF_YEAR)) { + return true; + } + } + return false; + } + + public static boolean isSameMonth(long time1, long time2) { + Calendar cal1 = Calendar.getInstance(); + Calendar cal2 = Calendar.getInstance(); + cal1.setTimeInMillis(time1); + cal2.setTimeInMillis(time2); + return cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) + && cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH); + } + + // --------------------------------------获取相关时间戳-------------------------------------- + + + /** + * 获取给定时间戳对应的日期的0点时间戳 + */ + public static long getZeroTimeOfDay(long time) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis(); + } + + + /** + * 获取给定时间戳对应的日期的最后时刻时间戳 + */ + public static long getLastTimeOfDay(long time) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + cal.set(Calendar.HOUR_OF_DAY, 23); + cal.set(Calendar.MINUTE, 59); + cal.set(Calendar.SECOND, 59); + cal.set(Calendar.MILLISECOND, 999); + return cal.getTimeInMillis(); + } + + // 获取给定时间戳对应的日期的昨天这个时刻的时间戳 + public static long getYesterdayTimeOfDay(long time) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(time); + cal.add(Calendar.DATE, -1); + return cal.getTimeInMillis(); + } + + // 获取给定时间戳对应小时的起始时间戳 + public static long getStartTimeOfHour(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var monday = localDateTime.withMinute(0).withSecond(0).withNano(0); + return TimeUtils.localDateTimeToTimestamp(monday); + } + + // 获取给定时间戳对应周的第一天的起始时间戳 + public static long getStartTimeOfWeek(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var monday = localDateTime.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0); + return TimeUtils.localDateTimeToTimestamp(monday); + } + + // 获取给定时间戳对应月的第一天的起始时间戳 + public static long getStartTimeOfMonth(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var oneOfMonth = localDateTime.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0); + return TimeUtils.localDateTimeToTimestamp(oneOfMonth); + } + + // 获取给定时间戳对应月的最后一天的结束时间戳 + public static long getEndTimeOfMonth(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var oneOfMonth = localDateTime.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0); + return TimeUtils.localDateTimeToTimestamp(oneOfMonth); + } + + //获取上一个月月初的时间 + public static long getLastMonthStart(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var lastMonth = localDateTime.minusMonths(1); + LocalDateTime with = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0); + return TimeUtils.localDateTimeToTimestamp(with); + } + + //获取上一个月月末的时间 + public static long getLastMonthEnd(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var lastMonth = localDateTime.minusMonths(1); + LocalDateTime with = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0); + return TimeUtils.localDateTimeToTimestamp(with); + } + + //获取下一个月月初的时间 + public static long getNextMonthStart(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var lastMonth = localDateTime.plusMonths(1); + LocalDateTime with = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0).withNano(0); + return TimeUtils.localDateTimeToTimestamp(with); + } + + // 获取下一个月月末的时间 + public static long getNextMonthEnd(long time) { + var localDateTime = TimeUtils.timestampToLocalDateTime(time); + var lastMonth = localDateTime.plusMonths(1); + LocalDateTime with = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59).withNano(0); + return TimeUtils.localDateTimeToTimestamp(with); + } + + /** + * LocalDateTime转毫秒时间戳 + * + * @param localDateTime LocalDateTime + * @return 时间戳 + */ + public static Long localDateTimeToTimestamp(LocalDateTime localDateTime) { + var zoneId = ZoneId.systemDefault(); + var instant = localDateTime.atZone(zoneId).toInstant(); + return instant.toEpochMilli(); + } + + /** + * 时间戳转LocalDateTime + * + * @param timestamp 时间戳 + * @return LocalDateTime + */ + public static LocalDateTime timestampToLocalDateTime(long timestamp) { + var instant = Instant.ofEpochMilli(timestamp); + var zone = ZoneId.systemDefault(); + return LocalDateTime.ofInstant(instant, zone); + } + + // --------------------------------------cron表达式-------------------------------------- + public static long getNextTimestampByCronExpression(CronExpression expression, long currentTimestamp) { + var next = expression.next(ZonedDateTime.ofInstant(Instant.ofEpochMilli(currentTimestamp), DEFAULT_ZONE_ID)); + + if (next == null) { + return Long.MAX_VALUE; + } + + return next.toInstant().toEpochMilli(); + } + +} diff --git a/scheduler/src/main/resources/META-INF/spring.handlers b/scheduler/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..d1a4dc45 --- /dev/null +++ b/scheduler/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/scheduler=com.zfoo.scheduler.schema.NamespaceHandler \ No newline at end of file diff --git a/scheduler/src/main/resources/META-INF/spring.schemas b/scheduler/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..39dd3128 --- /dev/null +++ b/scheduler/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/scheduler-1.0.xsd=scheduler-1.0.xsd diff --git a/scheduler/src/main/resources/scheduler-1.0.xsd b/scheduler/src/main/resources/scheduler-1.0.xsd new file mode 100644 index 00000000..6709561a --- /dev/null +++ b/scheduler/src/main/resources/scheduler-1.0.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/scheduler/src/test/java/com/zfoo/scheduler/ApplicationTest.java b/scheduler/src/test/java/com/zfoo/scheduler/ApplicationTest.java new file mode 100644 index 00000000..1e968201 --- /dev/null +++ b/scheduler/src/test/java/com/zfoo/scheduler/ApplicationTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler; + +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * cron(译为克龙)代表100万年,是英文单词中最大的时间单位。 + * google(译为古戈尔)代表10的100次方,足够穷尽宇宙万物 + * + * @author jaysunxiao + * @version 3.0 + */ + +@Ignore +public class ApplicationTest { + + @Test + public void startSchedulerTest() { + // 加载配置文件,配置文件中必须引入scheduler + var context = new ClassPathXmlApplicationContext("application.xml"); + + ThreadUtils.sleep(Long.MAX_VALUE); + } + +} diff --git a/scheduler/src/test/java/com/zfoo/scheduler/SchedulerController.java b/scheduler/src/test/java/com/zfoo/scheduler/SchedulerController.java new file mode 100644 index 00000000..adc9cc49 --- /dev/null +++ b/scheduler/src/test/java/com/zfoo/scheduler/SchedulerController.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler; + +import com.zfoo.scheduler.model.anno.Scheduler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class SchedulerController { + + private static final Logger logger = LoggerFactory.getLogger(SchedulerController.class); + + @Scheduler(cron = "0/5 * * * * ?") + public void cronScheduler1() { + logger.info("scheduler1 每5秒时间调度任务"); + } + + @Scheduler(cron = "0,10,20,40 * * * * ?") + public void cronScheduler2() { + logger.info("scheduler2 每分钟的10秒,20秒,40秒调度任务"); + } + +} diff --git a/scheduler/src/test/java/com/zfoo/scheduler/util/TimeUtilsTest.java b/scheduler/src/test/java/com/zfoo/scheduler/util/TimeUtilsTest.java new file mode 100644 index 00000000..302a55a4 --- /dev/null +++ b/scheduler/src/test/java/com/zfoo/scheduler/util/TimeUtilsTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.scheduler.util; + +import com.zfoo.util.ThreadUtils; +import org.junit.Ignore; +import org.junit.Test; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAdjusters; +import java.util.Date; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class TimeUtilsTest { + + @Test + public void testLocalDate() { + // 取当前日期: + LocalDate today = LocalDate.now(); // -> 2014-12-24 + // 根据年月日取日期: + LocalDate crischristmas = LocalDate.of(2014, 12, 25); // -> 2014-12-25 + // 根据字符串取: + LocalDate endOfFeb = LocalDate.parse("2014-02-28"); // 严格按照ISO yyyy-MM-dd验证,02写成2都不行,当然也有一个重载方法允许自己定义格式 + LocalDate.parse("2014-02-26"); // 无效日期无法通过:DateTimeParseException: Invalid date + // 取本月第1天: + LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth()); // 2017-03-01 + // 取本月第2天: + LocalDate secondDayOfThisMonth = today.withDayOfMonth(2); // 2017-03-02 + // 取本月最后一天,再也不用计算是28,29,30还是31: + LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth()); // 2017-12-31 + // 取下一天: + LocalDate firstDayOf2015 = lastDayOfThisMonth.plusDays(1); // 变成了2018-01-01 + // 取2017年1月第一个周一,用Calendar要死掉很多脑细胞: + LocalDate firstMondayOf2015 = LocalDate.parse("2017-01-01").with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); // 2017-01-02 + } + + @Test + public void testLocalTime() { + System.out.println(Long.MAX_VALUE); + } + + @Test + public void testLocalDateTime() { + LocalDate date = LocalDate.of(2018, 12, 4); + LocalTime time = LocalTime.of(0, 0, 0); + LocalDateTime localDateTime = LocalDateTime.of(date, time); + System.out.println(localDateTime); + //将localDateTime转换成时间戳 + System.out.println("localDateTime:" + java.sql.Timestamp.valueOf(localDateTime).getTime()); + } + + + /** + * 计算周五的活动下次开启的时间 + */ + @Test + public void test() { + LocalDate date = LocalDate.of(2018, 12, 6); + LocalTime time = LocalTime.of(0, 0, 0); + + int days = DayOfWeek.FRIDAY.getValue() - date.getDayOfWeek().getValue(); + LocalDate localDate = (days <= 0) ? date.plusDays(DayOfWeek.SUNDAY.getValue() + days) : date.plusDays(days); + System.out.println(localDate); + + LocalDateTime localDateTime = LocalDateTime.of(date, time); + System.out.println(localDateTime); + System.out.println("date:" + new Date(java.sql.Timestamp.valueOf(localDateTime).getTime())); + } + + @Ignore + @Test + public void nowTest() { + for (int i = 0; i < 100; i++) { + System.out.println(TimeUtils.now()); + ThreadUtils.sleep(1000); + } + } + +} diff --git a/scheduler/src/test/resources/application.xml b/scheduler/src/test/resources/application.xml new file mode 100644 index 00000000..f24bd324 --- /dev/null +++ b/scheduler/src/test/resources/application.xml @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/scheduler/src/test/resources/logback-test.xml b/scheduler/src/test/resources/logback-test.xml new file mode 100644 index 00000000..2302634a --- /dev/null +++ b/scheduler/src/test/resources/logback-test.xml @@ -0,0 +1,44 @@ + + + + + com.zfoo.scheduler + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + + diff --git a/storage/pom.xml b/storage/pom.xml new file mode 100644 index 00000000..0341a9f9 --- /dev/null +++ b/storage/pom.xml @@ -0,0 +1,256 @@ + + + 4.0.0 + + com.zfoo + storage + 3.0 + + jar + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + + + com.zfoo + util + ${zfoo.util.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + + org.apache.poi + poi + ${poi.version} + + + commons-codec + commons-codec + + + + + commons-codec + commons-codec + ${commons-codec.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + ch.qos.logback + logback-core + ${logback.version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + slf4j-api + org.slf4j + + + + + + + junit + junit + ${junit.version} + test + + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + diff --git a/storage/src/main/java/com/zfoo/storage/StorageContext.java b/storage/src/main/java/com/zfoo/storage/StorageContext.java new file mode 100644 index 00000000..b0fdb87d --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/StorageContext.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.interpreter.IResourceReader; +import com.zfoo.storage.manager.IStorageManager; +import com.zfoo.storage.schema.ResInjectionProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.support.ConversionServiceFactoryBean; +import org.springframework.core.Ordered; +import org.springframework.core.convert.ConversionService; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StorageContext implements ApplicationListener, Ordered { + + private static StorageContext instance; + + private ApplicationContext applicationContext; + + private IResourceReader resourceReader; + + private IStorageManager storageManager; + + private ConversionService conversionService; + + public static StorageContext getStorageContext() { + return instance; + } + + public static ApplicationContext getApplicationContext() { + return instance.applicationContext; + } + + public static StorageContext getInstance() { + return instance; + } + + public static ConversionService getConversionService() { + return instance.conversionService; + } + + public static IResourceReader getResourceReader() { + return instance.resourceReader; + } + + public static IStorageManager getStorageManager() { + return instance.storageManager; + } + + public static void injectResource() { + var applicationContext = instance.applicationContext; + var beanNames = applicationContext.getBeanDefinitionNames(); + var processor = applicationContext.getBean(ResInjectionProcessor.class); + + for (var beanName : beanNames) { + processor.postProcessAfterInitialization(applicationContext.getBean(beanName), beanName); + } + } + + @Override + public void onApplicationEvent(ApplicationContextEvent event) { + + if (event instanceof ContextRefreshedEvent) { + if (instance != null) { + return; + } + // 初始化上下文 + StorageContext.instance = this; + instance.applicationContext = event.getApplicationContext(); + instance.conversionService = (ConversionService) applicationContext.getBean(StringUtils.uncapitalize(ConversionServiceFactoryBean.class.getName())); + instance.resourceReader = applicationContext.getBean(IResourceReader.class); + instance.storageManager = applicationContext.getBean(IStorageManager.class); + + // 初始化,并读取配置表 + instance.storageManager.initBefore(); + + // 注入配置表资源 + injectResource(); + + // 移除没有被引用的不必要资源,为了节省服务器内存 + instance.storageManager.initAfter(); + } else if (event instanceof ContextClosedEvent) { + + } + } + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } +} diff --git a/storage/src/main/java/com/zfoo/storage/interpreter/ExcelResourceReader.java b/storage/src/main/java/com/zfoo/storage/interpreter/ExcelResourceReader.java new file mode 100644 index 00000000..67e1aef8 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/interpreter/ExcelResourceReader.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.interpreter; + +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.StorageContext; +import com.zfoo.storage.model.anno.Id; +import com.zfoo.storage.util.CellUtils; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.springframework.core.convert.TypeDescriptor; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ExcelResourceReader implements IResourceReader { + + private static final TypeDescriptor TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class); + + @Override + public List read(InputStream inputStream, Class clazz) { + var wb = createWorkbook(inputStream, clazz); + var result = new ArrayList(); + + // 默认取到第一个sheet页 + var sheet = wb.getSheetAt(0); + var fieldInfos = getFieldInfos(sheet, clazz); + + var iterator = sheet.iterator(); + // 行数定位到有效数据行,默认是第四行为有效数据行 + iterator.next(); + iterator.next(); + iterator.next(); + + // 从ROW_SERVER这行开始读取数据 + while (iterator.hasNext()) { + var row = iterator.next(); + var instance = ReflectionUtils.newInstance(clazz); + + var idCell = row.getCell(0); + if (idCell == null || StringUtils.isBlank(CellUtils.getCellStringValue(idCell))) { + continue; + } + + for (var fieldInfo : fieldInfos) { + var cell = row.getCell(fieldInfo.index); + if (cell != null) { + var content = CellUtils.getCellStringValue(cell); + if (!StringUtils.isEmpty(content)) { + inject(instance, fieldInfo.field, content); + } + } + + // 如果读的是id列的单元格,则判断当前id是否为空 + if (fieldInfo.field.isAnnotationPresent(Id.class)) { + if (cell == null || StringUtils.isEmpty(CellUtils.getCellStringValue(cell))) { + throw new RuntimeException(StringUtils.format("静态资源[resource:{}]存在id未配置的项", clazz.getSimpleName())); + } + } + } + + result.add(instance); + } + return result; + } + + private void inject(Object instance, Field field, String content) { + try { + var targetType = new TypeDescriptor(field); + var value = StorageContext.getConversionService().convert(content, TYPE_DESCRIPTOR, targetType); + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, instance, value); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("无法将Excel资源[class:{}]中的[content:{}]转换为属性[field:{}]" + , instance.getClass().getSimpleName(), content, field.getName()), e); + } + } + + + // 只读取代码里写的字段 + private Collection getFieldInfos(Sheet sheet, Class clazz) { + var fieldRow = getFieldRow(sheet); + if (fieldRow == null) { + throw new RuntimeException(StringUtils.format("无法获取资源[class:{}]的Excel文件的属性控制列", clazz.getSimpleName())); + } + + var cellFieldMap = new HashMap(); + for (var i = 0; i < fieldRow.getLastCellNum(); i++) { + var cell = fieldRow.getCell(i); + if (Objects.isNull(cell)) { + continue; + } + + var name = CellUtils.getCellStringValue(cell); + if (StringUtils.isEmpty(name)) { + continue; + } + var previousValue = cellFieldMap.put(name, i); + if (Objects.nonNull(previousValue)) { + throw new RuntimeException(StringUtils.format("资源[class:{}]的Excel文件出现重复的属性控制列[field:{}]" + , clazz.getSimpleName(), name)); + } + } + + var fieldList = Arrays.stream(clazz.getDeclaredFields()) + .filter(it -> !Modifier.isTransient(it.getModifiers())) + .filter(it -> !Modifier.isStatic(it.getModifiers())) + .collect(Collectors.toList()); + + for (var field : fieldList) { + if (!cellFieldMap.containsKey(field.getName())) { + throw new RuntimeException(StringUtils.format("资源类[class:{}]的声明属性[filed:{}]无法获取,请检查配置表的格式", clazz, field.getName())); + } + } + + return fieldList.stream().map(it -> new FieldInfo(cellFieldMap.get(it.getName()), it)).collect(Collectors.toList()); + + } + + // 获取配置表的有效列名称,默认第一行就是字段名称 + private Row getFieldRow(Sheet sheet) { + var iterator = sheet.iterator(); + var row = iterator.next(); + return row; + } + + + private Workbook createWorkbook(InputStream input, Class clazz) { + try { + return WorkbookFactory.create(input); + } catch (IOException e) { + throw new RuntimeException(StringUtils.format("静态资源[{}]异常,无法读取文件", clazz.getSimpleName())); + } + } + + + private static class FieldInfo { + public final int index; + public final Field field; + + public FieldInfo(int index, Field field) { + this.index = index; + this.field = field; + } + } +} diff --git a/storage/src/main/java/com/zfoo/storage/interpreter/IResourceReader.java b/storage/src/main/java/com/zfoo/storage/interpreter/IResourceReader.java new file mode 100644 index 00000000..cbc165ea --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/interpreter/IResourceReader.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.interpreter; + +import java.io.InputStream; +import java.util.List; + +/** + * 解释器模式设计的文本解析器 + *

+ * interpreter [in'ter·pret·er || ɪn'tɜrprɪtə(r) /-'tɜːp-] + * n. 直译程序, 翻译员, 解释者 + * + * @author jaysunxiao + * @version 3.0 + */ +public interface IResourceReader { + + List read(InputStream inputStream, Class clazz); + +} diff --git a/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java b/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java new file mode 100644 index 00000000..1aededc1 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/manager/IStorageManager.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.manager; + +import com.zfoo.storage.model.vo.Storage; +import org.springframework.lang.Nullable; + +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public interface IStorageManager { + + /** + * 配置表初始化之前,先读取所有的excel + */ + void initBefore(); + + /** + * 程序加载过后,移除没有用到的配置表 + */ + void initAfter(); + + @Nullable + Storage getStorage(Class clazz); + + Set> allStorageClassSet(); + + Map, Storage> getStorageMap(); + + void updateStorage(Class clazz, Storage storage); + +} diff --git a/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java b/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java new file mode 100644 index 00000000..ec43652e --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/manager/StorageManager.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.manager; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.model.config.StorageConfig; +import com.zfoo.storage.model.vo.ResourceDef; +import com.zfoo.storage.model.vo.Storage; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.util.ResourceUtils; + +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StorageManager implements IStorageManager { + + // ANT通配符有三种, ? :匹配任何单字符; * :匹配0或者任意数量的字符; ** :匹配0或者更多的目录 + // 1. /project/*.a 匹配项目根路径下所有在project路径下的.a文件 + // 2. /project/p?ttern 匹配项目根路径下 /project/pattern 和 /app/pXttern,但是不包括/app/pttern + // 3. /**/example 匹配项目根路径下 /project/example, /project/foow/example, 和 /example + // 4. /project/**/dir/file.* 匹配项目根路径下/project/dir/file.jsp, /project/foow/dir/file.html,/project/foow/bar/dir/file.pdf + // 5. /**/*.jsp 匹配项目根路径下任何的.jsp 文件 + // classpath:和classpath*: 的区别,前者只会第一个加载到的类,后者会加载所有的类,包括jar文件下的类 + private static final String SUFFIX_PATTERN = "**/*.class"; + + private StorageConfig storageConfig; + + /** + * 在当前项目被依赖注入,被使用的Storage + */ + private Map, Storage> storageMap = new HashMap<>(); + + /** + * 全部的Storage定义 + */ + private Set> allStorageClassSet = new HashSet<>(); + + public StorageConfig getStorageConfig() { + return storageConfig; + } + + public void setStorageConfig(StorageConfig storageConfig) { + this.storageConfig = storageConfig; + } + + @Override + public void initBefore() { + var resourceDefinitionMap = new HashMap, ResourceDef>(); + var clazzNameSet = scanResourceAnno(storageConfig.getScanPackage()); + for (var clazzName : clazzNameSet) { + Class resourceClazz; + try { + resourceClazz = Class.forName(clazzName); + } catch (ClassNotFoundException e) { + throw new RuntimeException(StringUtils.format("无法获取资源类[{}]", clazzName)); + } + + var resourceFile = scanResourceFile(resourceClazz); + ResourceDef resourceDef = new ResourceDef(resourceClazz, resourceFile); + if (resourceDefinitionMap.containsKey(resourceClazz)) { + throw new RuntimeException(StringUtils.format("类的资源定义[{}]已经存在[{}]", resourceClazz, resourceDef)); + } + resourceDefinitionMap.put(resourceClazz, resourceDef); + } + + try { + for (var definition : resourceDefinitionMap.values()) { + var clazz = definition.getClazz(); + Storage storage = new Storage<>(); + storage.init(definition.getResource().getInputStream(), definition.getClazz()); + storageMap.putIfAbsent(clazz, storage); + allStorageClassSet.add(clazz); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void initAfter() { + var unusableStorageClassList = storageMap.entrySet().stream() + .filter(it -> !it.getValue().isUsable()) + .map(it -> it.getKey()) + .collect(Collectors.toList()); + + unusableStorageClassList.forEach(it -> { + storageMap.remove(it); + }); + } + + @Override + public Storage getStorage(Class clazz) { + return storageMap.get(clazz); + } + + @Override + public Set> allStorageClassSet() { + return allStorageClassSet; + } + + @Override + public Map, Storage> getStorageMap() { + return storageMap; + } + + + @Override + public void updateStorage(Class clazz, Storage storage) { + storageMap.put(clazz, storage); + } + + private Set scanResourceAnno(String scanLocation) { + var resourcePatternResolver = new PathMatchingResourcePatternResolver(); + var metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + + try { + var packageSearchPath = ResourceUtils.CLASSPATH_URL_PREFIX + scanLocation.replace(StringUtils.PERIOD, StringUtils.SLASH) + StringUtils.SLASH + SUFFIX_PATTERN; + var resources = resourcePatternResolver.getResources(packageSearchPath); + var result = new HashSet(); + var name = com.zfoo.storage.model.anno.Resource.class.getName(); + for (var resource : resources) { + if (resource.isReadable()) { + var metadataReader = metadataReaderFactory.getMetadataReader(resource); + var annoMeta = metadataReader.getAnnotationMetadata(); + if (annoMeta.hasAnnotation(name)) { + ClassMetadata clazzMeta = metadataReader.getClassMetadata(); + result.add(clazzMeta.getClassName()); + } + } + } + return result; + } catch (IOException e) { + throw new RuntimeException("无法读取资源信息:" + e); + } + } + + private Resource scanResourceFile(Class clazz) { + var resourcePatternResolver = new PathMatchingResourcePatternResolver(); + var metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); + + try { + var resourceList = new ArrayList(); + + var packageSearchPath = StringUtils.format("{}/**/{}.{}", storageConfig.getResourceLocation(), clazz.getSimpleName(), storageConfig.getResourceSuffix()); + packageSearchPath = packageSearchPath.replaceAll("//", "/"); + try { + resourceList.addAll(Arrays.asList(resourcePatternResolver.getResources(packageSearchPath))); + } catch (Exception e) { + + } + + // 通配符无法匹配根目录,所以如果找不到,再从根目录查找一遍 + if (CollectionUtils.isEmpty(resourceList)) { + packageSearchPath = StringUtils.format("{}/{}.{}", storageConfig.getResourceLocation(), clazz.getSimpleName(), storageConfig.getResourceSuffix()); + packageSearchPath = packageSearchPath.replaceAll("//", "/"); + resourceList.addAll(Arrays.asList(resourcePatternResolver.getResources(packageSearchPath))); + } + + if (CollectionUtils.isEmpty(resourceList)) { + throw new RuntimeException(StringUtils.format("无法找到配置文件[{}]", clazz.getSimpleName())); + } else if (resourceList.size() > 1) { + throw new RuntimeException(StringUtils.format("找到重复的配置文件[{}]", clazz.getSimpleName())); + } else { + return resourceList.get(0); + } + + } catch (IOException e) { + throw new RuntimeException(ExceptionUtils.getMessage(e)); + } + } +} diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/Id.java b/storage/src/main/java/com/zfoo/storage/model/anno/Id.java new file mode 100644 index 00000000..fd5f0e2f --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/anno/Id.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.anno; + + +import java.lang.annotation.*; + +/** + * 主键 + * + * @author jaysunxiao + * @version 3.0 + */ + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Id { +} diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/Index.java b/storage/src/main/java/com/zfoo/storage/model/anno/Index.java new file mode 100644 index 00000000..d1f56f82 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/anno/Index.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.anno; + +import java.lang.annotation.*; + +/** + * 索引,索引的名称使用字段属性的名称,用HaspMap实现 + * + * @author jaysunxiao + * @version 3.0 + */ + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface Index { + + boolean unique() default false; + +} diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java b/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java new file mode 100644 index 00000000..12cd734f --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/anno/ResInjection.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.anno; + +import java.lang.annotation.*; + +/** + * 静态数据的注入 + * + * @author jaysunxiao + * @version 3.0 + */ + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface ResInjection { + + String value() default ""; + + boolean unique() default false; + +} diff --git a/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java b/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java new file mode 100644 index 00000000..3f809a1b --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/anno/Resource.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.anno; + +import java.lang.annotation.*; + +/** + * 资源注解 + * + * @author jaysunxiao + * @version 3.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface Resource { +} diff --git a/storage/src/main/java/com/zfoo/storage/model/config/StorageConfig.java b/storage/src/main/java/com/zfoo/storage/model/config/StorageConfig.java new file mode 100644 index 00000000..362383ee --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/config/StorageConfig.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.config; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StorageConfig { + + private String id; + + private String scanPackage; + + private String resourceLocation; + + private String resourceSuffix; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getScanPackage() { + return scanPackage; + } + + public void setScanPackage(String scanPackage) { + this.scanPackage = scanPackage; + } + + public String getResourceLocation() { + return resourceLocation; + } + + public void setResourceLocation(String resourceLocation) { + this.resourceLocation = resourceLocation; + } + + public String getResourceSuffix() { + return resourceSuffix; + } + + public void setResourceSuffix(String resourceSuffix) { + this.resourceSuffix = resourceSuffix; + } +} diff --git a/storage/src/main/java/com/zfoo/storage/model/vo/IdDef.java b/storage/src/main/java/com/zfoo/storage/model/vo/IdDef.java new file mode 100644 index 00000000..f17bf0f1 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/vo/IdDef.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.vo; + +import com.zfoo.protocol.exception.RunException; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.storage.model.anno.Id; + +import java.lang.reflect.Field; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class IdDef { + + private Field field; + + public static IdDef valueOf(Class clazz) { + Field[] fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Id.class); + if (fields.length <= 0) { + throw new RunException("class[{}]没有主键Id注解", clazz.getName()); + } + if (fields.length > 1) { + throw new RunException("类[{}]的主键Id注解重复", clazz.getName()); + } + if (fields[0] == null) { + throw new RunException("不合法的Id资源映射对象:" + clazz.getName()); + } + Field idField = fields[0]; + ReflectionUtils.makeAccessible(idField); + IdDef idDef = new IdDef(); + idDef.setField(idField); + return idDef; + } + + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + } +} diff --git a/storage/src/main/java/com/zfoo/storage/model/vo/IndexDef.java b/storage/src/main/java/com/zfoo/storage/model/vo/IndexDef.java new file mode 100644 index 00000000..45eddab5 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/vo/IndexDef.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.vo; + +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.model.anno.Index; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * 简化索引的名称,使用字段的名称作为索引的名称 + * + * @author jaysunxiao + * @version 3.0 + */ +public class IndexDef { + + private boolean unique; + private Field field; + + public IndexDef(Field field) { + ReflectionUtils.makeAccessible(field); + this.field = field; + var index = field.getAnnotation(Index.class); + this.unique = index.unique(); + } + + public static Map createResourceIndexes(Class clazz) { + var fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(clazz, Index.class); + var indexes = new ArrayList(fields.length); + + + for (var field : fields) { + IndexDef indexDef = new IndexDef(field); + indexes.add(indexDef); + } + + var result = new HashMap(); + for (var index : indexes) { + var indexName = index.field.getName(); + if (result.put(indexName, index) != null) { + throw new RuntimeException(StringUtils.format("资源类[{}]索引名称重复,索引名[}|]", clazz.getName(), indexName)); + } + } + + return result; + } + + public boolean isUnique() { + return unique; + } + + public void setUnique(boolean unique) { + this.unique = unique; + } + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + } +} diff --git a/storage/src/main/java/com/zfoo/storage/model/vo/ResourceDef.java b/storage/src/main/java/com/zfoo/storage/model/vo/ResourceDef.java new file mode 100644 index 00000000..70c73e88 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/vo/ResourceDef.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.vo; + +import org.springframework.core.io.Resource; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ResourceDef { + + private final Class clazz; + private final Resource resource; + + public ResourceDef(Class clazz, Resource resource) { + this.clazz = clazz; + this.resource = resource; + } + + + public Class getClazz() { + return clazz; + } + + public Resource getResource() { + return resource; + } + +} diff --git a/storage/src/main/java/com/zfoo/storage/model/vo/Storage.java b/storage/src/main/java/com/zfoo/storage/model/vo/Storage.java new file mode 100644 index 00000000..a84b05ae --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/model/vo/Storage.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.model.vo; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.StorageContext; +import org.springframework.lang.Nullable; + +import java.io.InputStream; +import java.util.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Storage { + + private Class clazz; + + private final Map dataMap = new HashMap<>(); + private final Map>> indexMap = new HashMap<>(); + private final Map> uniqueIndexMap = new HashMap<>(); + + private IdDef idDef; + private Map indexDefMap; + + /** + * 是否被使用 + */ + private boolean usable = false; + + public Storage() { + } + + public void init(InputStream inputStream, Class resourceClazz) { + try { + this.clazz = (Class) resourceClazz; + var reader = StorageContext.getResourceReader(); + idDef = IdDef.valueOf(resourceClazz); + indexDefMap = IndexDef.createResourceIndexes(resourceClazz); + + var list = reader.read(inputStream, resourceClazz); + + dataMap.clear(); + indexMap.clear(); + uniqueIndexMap.clear(); + + for (var object : list) { + put((V) object); + } + } catch (Throwable e) { + throw new RuntimeException(e.getMessage(), e); + } finally { + IOUtils.closeIO(inputStream); + } + } + + public Collection getAll() { + return Collections.unmodifiableCollection(dataMap.values()); + } + + public boolean contain(K key) { + return dataMap.containsKey(key); + } + + public V get(K id) { + V result = dataMap.get(id); + AssertionUtils.notNull(result, "静态资源[resource:{}]种表示为[id:{}]的静态资源不存在", clazz.getSimpleName(), id); + return (V) result; + } + + public List getIndex(String indexName, Object key) { + var indexValues = indexMap.get(indexName); + AssertionUtils.notNull(indexValues, "静态资源[resource:{}]不存在为[indexName:{}]的索引", clazz.getSimpleName(), indexName); + var values = indexValues.get(key); + if (CollectionUtils.isEmpty(values)) { + return Collections.emptyList(); + } + return values; + } + + @Nullable + public V getUniqueIndex(String uniqueIndexName, Object key) { + var indexValueMap = uniqueIndexMap.get(uniqueIndexName); + AssertionUtils.notNull(indexValueMap, "静态资源[resource:{}]不存在为[uniqueIndexName:{}]的唯一索引", clazz.getSimpleName(), uniqueIndexName); + var value = indexValueMap.get(key); + return value; + } + + + private V put(V value) { + var key = (K) ReflectionUtils.getField(idDef.getField(), value); + + if (key == null) { + throw new RuntimeException("静态资源存在id未配置的项"); + } + + if (dataMap.containsKey(key)) { + throw new RuntimeException(StringUtils.format("静态资源[resource:{}]的[id:{}]重复", clazz.getSimpleName(), key)); + } + + // 添加资源 + var result = dataMap.put(key, value); + + // 添加索引 + for (var def : indexDefMap.values()) { + // 使用field的名称作为索引的名称 + var indexKey = def.getField().getName(); + var indexValue = ReflectionUtils.getField(def.getField(), value); + if (def.isUnique()) {// 唯一索引 + var index = uniqueIndexMap.computeIfAbsent(indexKey, k -> new HashMap<>()); + if (index.put(indexValue, value) != null) { + throw new RuntimeException(StringUtils.format("静态资源[class:{}]的唯一索引重复[index:{}][value:{}]", clazz.getName(), indexKey, indexValue)); + } + } else {// 不是唯一索引 + var index = indexMap.computeIfAbsent(indexKey, k -> new HashMap<>()); + var list = index.computeIfAbsent(indexValue, k -> new ArrayList()); + list.add(value); + } + } + return result; + } + + public int size() { + return dataMap.size(); + } + + public boolean isUsable() { + return usable; + } + + public void setUsable(boolean usable) { + this.usable = usable; + } +} diff --git a/storage/src/main/java/com/zfoo/storage/schema/NamespaceHandler.java b/storage/src/main/java/com/zfoo/storage/schema/NamespaceHandler.java new file mode 100644 index 00000000..0526bdf9 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/schema/NamespaceHandler.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.schema; + +import org.springframework.beans.factory.xml.NamespaceHandlerSupport; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NamespaceHandler extends NamespaceHandlerSupport { + + @Override + public void init() { + registerBeanDefinitionParser(StorageDefinitionParser.STORAGE, new StorageDefinitionParser()); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/schema/ResInjectionProcessor.java b/storage/src/main/java/com/zfoo/storage/schema/ResInjectionProcessor.java new file mode 100644 index 00000000..e9409f1a --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/schema/ResInjectionProcessor.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.schema; + +import com.zfoo.protocol.util.ReflectionUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.StorageContext; +import com.zfoo.storage.model.anno.Id; +import com.zfoo.storage.model.anno.ResInjection; +import com.zfoo.storage.model.vo.Storage; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.function.Consumer; + +/** + * Bean级生命周期接口和容器生命周期接口是个性和共性的辩证统一思想的体现, + * 前者解决Bean个性化处理的问题,而后者解决容器中某些Bean共性处理的问题 + * + * @author jaysunxiao + * @version 3.0 + */ +public class ResInjectionProcessor implements BeanPostProcessor { + + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (StorageContext.getStorageContext() == null) { + return bean; + } + ReflectionUtils.filterFieldsInClass(bean.getClass(), null, new Consumer() { + @Override + public void accept(Field field) { + ResInjection anno = field.getAnnotation(ResInjection.class); + if (anno != null) { + injectStorage(bean, field, anno); + } + } + }); + + return bean; + } + + private void injectStorage(Object bean, Field field, ResInjection anno) { + Type type = field.getGenericType(); + + if (!(type instanceof ParameterizedType)) { + throw new RuntimeException(StringUtils.format("[bean:{}]类型声明不正确,不是泛型类", bean.getClass().getSimpleName())); + } + + Type[] types = ((ParameterizedType) type).getActualTypeArguments(); + + // @ResInjection + // Storage resources; + Class keyClazz = (Class) types[0]; + + Class resourceClazz = (Class) types[1]; + + Storage storage = StorageContext.getStorageManager().getStorage(resourceClazz); + + if (storage == null) { + throw new RuntimeException(StringUtils.format("静态类资源[resource:{}]不存在", resourceClazz.getSimpleName())); + } + + Field[] idFields = ReflectionUtils.getFieldsByAnnoInPOJOClass(resourceClazz, Id.class); + if (idFields.length != 1) { + throw new RuntimeException(StringUtils.format("静态类资源[resource:{}]配置没有注解id", resourceClazz.getSimpleName())); + } + + if (!keyClazz.getSimpleName().toLowerCase().contains(idFields[0].getType().getSimpleName().toLowerCase())) { + throw new RuntimeException(StringUtils.format("静态类资源[resource:{}]配置注解[id:{}]类型和泛型类型[type:{}]不匹配" + , resourceClazz.getSimpleName(), idFields[0].getType().getSimpleName(), keyClazz.getSimpleName())); + } + + ReflectionUtils.makeAccessible(field); + ReflectionUtils.setField(field, bean, storage); + storage.setUsable(true); + } + +} diff --git a/storage/src/main/java/com/zfoo/storage/schema/StorageDefinitionParser.java b/storage/src/main/java/com/zfoo/storage/schema/StorageDefinitionParser.java new file mode 100644 index 00000000..755c8866 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/schema/StorageDefinitionParser.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.schema; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.StorageContext; +import com.zfoo.storage.interpreter.ExcelResourceReader; +import com.zfoo.storage.manager.StorageManager; +import com.zfoo.storage.model.config.StorageConfig; +import com.zfoo.storage.strategy.*; +import com.zfoo.util.DomUtils; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.context.support.ConversionServiceFactoryBean; +import org.w3c.dom.Element; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StorageDefinitionParser implements BeanDefinitionParser { + + // 配置标签名称 + public static final String STORAGE = "storage"; + + + @Override + public AbstractBeanDefinition parse(Element element, ParserContext parserContext) { + // 注册StorageConfig + parseStorageConfig(element, parserContext); + + // 注入StorageSpringContext,ExcelResourceReader,ResInjectionProcessor,ConversionService + registerBeanDefinition(parserContext); + + // 注入StorageManager + var clazz = StorageManager.class; + var name = StringUtils.uncapitalize(clazz.getName()); + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + builder.addPropertyReference("storageConfig", StorageConfig.class.getCanonicalName()); + parserContext.getRegistry().registerBeanDefinition(name, builder.getBeanDefinition()); + + return builder.getBeanDefinition(); + } + + private void parseStorageConfig(Element element, ParserContext parserContext) { + var clazz = StorageConfig.class; + var builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + + var scanElement = DomUtils.getFirstChildElementByTagName(element, "scan"); + if (scanElement == null) { + throw new RuntimeException("XML文件缺少[scan]元素定义"); + } + var resourceElement = DomUtils.getFirstChildElementByTagName(element, "resource"); + if (resourceElement == null) { + throw new RuntimeException("XML文件缺少[resource]元素定义"); + } + + resolvePlaceholder("id", "id", builder, element, parserContext); + resolvePlaceholder("package", "scanPackage", builder, scanElement, parserContext); + resolvePlaceholder("location", "resourceLocation", builder, resourceElement, parserContext); + resolvePlaceholder("suffix", "resourceSuffix", builder, resourceElement, parserContext); + + parserContext.getRegistry().registerBeanDefinition(clazz.getCanonicalName(), builder.getBeanDefinition()); + } + + private void registerBeanDefinition(ParserContext parserContext) { + var registry = parserContext.getRegistry(); + + Class clazz; + String name; + BeanDefinitionBuilder builder; + + // 注入StorageSpringContext + clazz = StorageContext.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + registry.registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注入ExcelResourceReader + clazz = ExcelResourceReader.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + registry.registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注入StaticInjectProcessor + clazz = ResInjectionProcessor.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + registry.registerBeanDefinition(name, builder.getBeanDefinition()); + + // 注入ConversionServiceFactoryBean + clazz = ConversionServiceFactoryBean.class; + name = StringUtils.uncapitalize(clazz.getName()); + builder = BeanDefinitionBuilder.rootBeanDefinition(clazz); + List converters = new ArrayList<>(); + converters.add(new JsonToArrayConverter()); + converters.add(new JsonToMapConverter()); + converters.add(new JsonToObjectConverter()); + converters.add(new StringToClassConverter()); + converters.add(new StringToDateConverter()); + converters.add(new StringToMapConverter()); + builder.addPropertyValue("converters", converters); + registry.registerBeanDefinition(name, builder.getBeanDefinition()); + } + + private void resolvePlaceholder(String attributeName, String fieldName, BeanDefinitionBuilder builder, Element element, ParserContext parserContext) { + var attributeValue = element.getAttribute(attributeName); + var environment = parserContext.getReaderContext().getEnvironment(); + var placeholder = environment.resolvePlaceholders(attributeValue); + builder.addPropertyValue(fieldName, placeholder); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/strategy/JsonToArrayConverter.java b/storage/src/main/java/com/zfoo/storage/strategy/JsonToArrayConverter.java new file mode 100644 index 00000000..7c92bd83 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/strategy/JsonToArrayConverter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.strategy; + +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalGenericConverter; + +import java.util.Collections; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsonToArrayConverter implements ConditionalGenericConverter { + + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + return sourceType.getType() == String.class && targetType.getType().isArray(); + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, Object[].class)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + // String content = (String) source; + // return targetType.isPrimitive() ? JsonUtil.string2Object(content, targetType.getObjectType()) + // : JsonUtil.string2Array(content, targetType.getType()); + Class clazz = null; + + String content = (String) source; + + String targetClazzName = targetType.getObjectType().getName(); + if (targetClazzName.contains(StringUtils.LEFT_SQUARE_BRACKET) || targetClazzName.contains(StringUtils.SEMICOLON)) { + String clazzPath = targetClazzName.substring(2, targetClazzName.length() - 1); + try { + clazz = Class.forName(clazzPath); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } else { + clazz = targetType.getObjectType(); + } + + return JsonUtils.string2Array(content, clazz); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/strategy/JsonToMapConverter.java b/storage/src/main/java/com/zfoo/storage/strategy/JsonToMapConverter.java new file mode 100644 index 00000000..28ef7f50 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/strategy/JsonToMapConverter.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.strategy; + +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalGenericConverter; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class JsonToMapConverter implements ConditionalGenericConverter { + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + return sourceType.getType() == String.class && Map.class.isAssignableFrom(targetType.getType()); + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, Map.class)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + String content = (String) source; + return JsonUtils.string2Object(content, targetType.getType()); + // return JsonUtil.string2Map(content, targetType.getMapKeyTypeDescriptor().getType() + // , targetType.getMapValueTypeDescriptor().getType()); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/strategy/JsonToObjectConverter.java b/storage/src/main/java/com/zfoo/storage/strategy/JsonToObjectConverter.java new file mode 100644 index 00000000..7980299b --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/strategy/JsonToObjectConverter.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.strategy; + +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.ReflectionUtils; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.core.convert.converter.ConditionalGenericConverter; + +import java.util.Collections; +import java.util.Set; + +/** + * 转换一个String到一个POJO对象,且这个对象不能继承如何接口 + * + * @author jaysunxiao + * @version 3.0 + */ +public class JsonToObjectConverter implements ConditionalGenericConverter { + + + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + if (sourceType.getType() != String.class) { + return false; + } + + if (targetType.getType().isPrimitive()) { + return false; + } + + if (Number.class.isAssignableFrom(targetType.getType())) { + return false; + } + + if (CharSequence.class.isAssignableFrom(targetType.getType())) { + return false; + } + + return ReflectionUtils.isPOJOClass(targetType.getType()); + } + + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(String.class, Object.class)); + } + + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + String content = (String) source; + return JsonUtils.string2Object(content, targetType.getType()); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/strategy/StringToClassConverter.java b/storage/src/main/java/com/zfoo/storage/strategy/StringToClassConverter.java new file mode 100644 index 00000000..c71741af --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/strategy/StringToClassConverter.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.strategy; + +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.StorageContext; +import org.springframework.core.convert.converter.Converter; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StringToClassConverter implements Converter> { + + + @Override + public Class convert(String source) { + if (!source.contains(".") && !source.startsWith("[")) { + source = "java.lang." + source; + } + + ClassLoader loader = null; + + StorageContext context = StorageContext.getInstance(); + + if (context != null) { + loader = StorageContext.getApplicationContext().getClassLoader(); + } else { + loader = Thread.currentThread().getContextClassLoader(); + } + + try { + return Class.forName(source, true, loader); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(StringUtils.format("无法将字符串[{}]转换为Class对象", source)); + } + + } +} diff --git a/storage/src/main/java/com/zfoo/storage/strategy/StringToDateConverter.java b/storage/src/main/java/com/zfoo/storage/strategy/StringToDateConverter.java new file mode 100644 index 00000000..3d0a3761 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/strategy/StringToDateConverter.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.strategy; + +import com.zfoo.protocol.util.StringUtils; +import org.springframework.core.convert.converter.Converter; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StringToDateConverter implements Converter { + + + @Override + public Date convert(String source) { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + try { + return df.parse(source); + } catch (ParseException e) { + throw new IllegalArgumentException(StringUtils.format("字符串[{}]不符合格式要求:[yyyy-MM-dd HH:mm:ss]", source)); + } + } +} diff --git a/storage/src/main/java/com/zfoo/storage/strategy/StringToMapConverter.java b/storage/src/main/java/com/zfoo/storage/strategy/StringToMapConverter.java new file mode 100644 index 00000000..1bc2a363 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/strategy/StringToMapConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.strategy; + +import com.zfoo.protocol.util.JsonUtils; +import org.springframework.core.convert.converter.Converter; + +import java.util.Map; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StringToMapConverter implements Converter> { + @Override + public Map convert(String source) { + return JsonUtils.string2Map(source, String.class, Object.class); + } +} diff --git a/storage/src/main/java/com/zfoo/storage/util/CellUtils.java b/storage/src/main/java/com/zfoo/storage/util/CellUtils.java new file mode 100644 index 00000000..90af9cb3 --- /dev/null +++ b/storage/src/main/java/com/zfoo/storage/util/CellUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.util; + +import com.zfoo.protocol.util.StringUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.FormulaError; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class CellUtils { + /** + * 获取单元格值 + * + * @return 值,类型可能为:Date、Double、Boolean、String + */ + public static Object getCellValue(Cell cell) { + return getCellValue(cell, cell.getCellType()); + } + + public static String getCellStringValue(Cell cell) { + return getCellValue(cell).toString().trim(); + } + + + /** + * 获取单元格值
+ * 如果单元格值为数字格式,则判断其格式中是否有小数部分,无则返回Long类型,否则返回Double类型 + */ + public static Object getCellValue(Cell cell, CellType cellType) { + Object value; + switch (cellType) { + case NUMERIC: + value = getNumericValue(cell); + break; + case BOOLEAN: + value = cell.getBooleanCellValue(); + break; + case FORMULA: + // 遇到公式时查找公式结果类型 + value = getCellValue(cell, cell.getCachedFormulaResultType()); + break; + case BLANK: + value = StringUtils.EMPTY; + break; + case ERROR: + final FormulaError error = FormulaError.forInt(cell.getErrorCellValue()); + value = (null == error) ? StringUtils.EMPTY : error.getString(); + break; + default: + value = cell.getStringCellValue(); + } + + return value; + } + + + // -------------------------------------------------------------------------------------------------------------- Private method start + + /** + * 获取数字类型的单元格值 + * + * @return 单元格值,可能为Long、Double、Date + */ + private static Object getNumericValue(Cell cell) { + var value = cell.getNumericCellValue(); + + var style = cell.getCellStyle(); + if (null == style) { + return value; + } + + // 判断是否为日期 + if (DateUtil.isCellDateFormatted(cell)) { + return cell.getDateCellValue(); + } + + var format = style.getDataFormatString(); + // 普通数字 + if (null != format && !format.contains(StringUtils.PERIOD)) { + var longValue = (long) value; + if (longValue == value) { + // 对于无小数部分的数字类型,转为Long + return longValue; + } + } + return value; + } + +} diff --git a/storage/src/main/resources/META-INF/spring.handlers b/storage/src/main/resources/META-INF/spring.handlers new file mode 100644 index 00000000..00ae5d99 --- /dev/null +++ b/storage/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/storage=com.zfoo.storage.schema.NamespaceHandler \ No newline at end of file diff --git a/storage/src/main/resources/META-INF/spring.schemas b/storage/src/main/resources/META-INF/spring.schemas new file mode 100644 index 00000000..58c90657 --- /dev/null +++ b/storage/src/main/resources/META-INF/spring.schemas @@ -0,0 +1 @@ +http\://www.zfoo.com/schema/storage-1.0.xsd=storage-1.0.xsd \ No newline at end of file diff --git a/storage/src/main/resources/storage-1.0.xsd b/storage/src/main/resources/storage-1.0.xsd new file mode 100644 index 00000000..764685e1 --- /dev/null +++ b/storage/src/main/resources/storage-1.0.xsd @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storage/src/test/java/com/zfoo/storage/ApplicationTest.java b/storage/src/test/java/com/zfoo/storage/ApplicationTest.java new file mode 100644 index 00000000..6f8d88ac --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/ApplicationTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage; + +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.storage.resource.StudentResource; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author jaysunxiao + * @version 3.0 + */ + +@Ignore +public class ApplicationTest { + + private static final Logger logger = LoggerFactory.getLogger(ApplicationTest.class); + + // storage教程 + @Test + public void startStorageTest() { + // 加载配置文件,配置文件中必须引入storage + // 配置文件中scan,需要映射Excel的类所在位置 + // 配置文件中resource,需要映射Excel的文件所在位置 + var context = new ClassPathXmlApplicationContext("application.xml"); + + // Excel的映射内容需要在被Spring管理的bean的方法上加上@ResInjection注解,即可自动注入Excel对应的对象 + // 参考StudentManager中的标准用法 + + var studentManager = context.getBean(StudentManager.class); + var studentResources = studentManager.studentResources; + // 类名称和Excel名称必须完全一致,Excel的列名称必须对应对象的属性名称 + for (StudentResource resource : studentResources.getAll()) { + logger.info(JsonUtils.object2String(resource)); + } + System.out.println(StringUtils.MULTIPLE_HYPHENS); + + // 通过id找到对应的行 + var id = 1000; + var valueById = studentResources.get(id); + logger.info(JsonUtils.object2String(valueById)); + System.out.println(StringUtils.MULTIPLE_HYPHENS); + + // 通过索引找对应的行 + var valuesByIndex = studentResources.getIndex("name", "james0"); + logger.info(JsonUtils.object2String(valuesByIndex)); + } +} diff --git a/storage/src/test/java/com/zfoo/storage/StudentManager.java b/storage/src/test/java/com/zfoo/storage/StudentManager.java new file mode 100644 index 00000000..a3f27229 --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/StudentManager.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage; + +import com.zfoo.storage.model.anno.ResInjection; +import com.zfoo.storage.model.vo.Storage; +import com.zfoo.storage.resource.StudentResource; +import org.springframework.stereotype.Component; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Component +public class StudentManager { + + @ResInjection + public Storage studentResources; + +} diff --git a/storage/src/test/java/com/zfoo/storage/conversion/ConversionTest.java b/storage/src/test/java/com/zfoo/storage/conversion/ConversionTest.java new file mode 100644 index 00000000..4548134c --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/conversion/ConversionTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.conversion; + +import com.zfoo.storage.strategy.JsonToArrayConverter; +import com.zfoo.storage.strategy.JsonToMapConverter; +import com.zfoo.storage.strategy.StringToClassConverter; +import com.zfoo.storage.strategy.StringToDateConverter; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.support.ConversionServiceFactoryBean; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.TypeDescriptor; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ConversionTest { + private static final ConversionServiceFactoryBean csfb = new ConversionServiceFactoryBean(); + private static final Set converters = new HashSet<>(); + + private static final StringToDateConverter std = new StringToDateConverter(); + private static final StringToClassConverter stcc = new StringToClassConverter(); + private static final JsonToMapConverter jtmc = new JsonToMapConverter(); + private static final JsonToArrayConverter jtac = new JsonToArrayConverter(); + + static { + converters.add(std); + converters.add(stcc); + converters.add(jtmc); + converters.add(jtac); + csfb.setConverters(converters); + csfb.afterPropertiesSet(); + } + + + @Test + public void string2Integer() { + ConversionService conversionService = csfb.getObject(); + Integer result = conversionService.convert("123", Integer.class); + Assert.assertEquals(123, result.intValue()); + } + + @Test + public void string2Class() { + ConversionService conversionService = csfb.getObject(); + // String to Class + Class clazz = (Class) conversionService.convert("com.zfoo.storage.model.vo.Storage", TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Class.class)); + + Assert.assertEquals("com.zfoo.storage.model.vo.Storage", clazz.getName()); + } + + @Test + public void string2Map() { + ConversionService conversionService = csfb.getObject(); + //Json to Map + String str = "{\"1\":1,\"2\":2,\"3\":3}"; + + Map map = new HashMap(); + + map = (Map) conversionService.convert(str, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(map.getClass())); + + Assert.assertEquals(3, map.size()); + } + + @Test + public void string2Array() { + ConversionService conversionService = csfb.getObject(); + String str = "[1,2,3]"; + + Integer[] array = (Integer[]) conversionService.convert(str, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer[].class)); + + Assert.assertEquals(3, array.length); + } + +} diff --git a/storage/src/test/java/com/zfoo/storage/excel/ExcelTest.java b/storage/src/test/java/com/zfoo/storage/excel/ExcelTest.java new file mode 100644 index 00000000..2e7220cd --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/excel/ExcelTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.excel; + +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Iterator; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class ExcelTest { + + @Test + public void createExcelTest() throws IOException { + //第一步创建workbook + var wb = WorkbookFactory.create(true); + + //第二步创建sheet + var sheet = wb.createSheet("测试"); + + //第三步创建行row:添加表头0行 + var row = sheet.createRow(0); + var style = wb.createCellStyle(); + //style.setAlignment(HSSFCellStyle.ALIGN_CENTER); //居中 + + + //第四步创建单元格 + var cell = row.createCell(0); //第一个单元格 + cell.setCellValue("姓名"); + cell.setCellStyle(style); + + cell = row.createCell(1); //第二个单元格 + cell.setCellValue("年龄"); + cell.setCellStyle(style); + + + //第五步插入数据 + for (int i = 0; i < 5; i++) { + //创建行 + row = sheet.createRow(i + 1); + //创建单元格并且添加数据 + row.createCell(0).setCellValue("aa" + i); + row.createCell(1).setCellValue(i); + } + + //第六步将生成excel文件保存到指定路径下 + FileOutputStream target = new FileOutputStream("target.xlsx"); + wb.write(target); + target.close(); + + System.out.println("Excel文件生成成功..."); + } + + @Test + public void readExcelTest() throws IOException { + Workbook wb = WorkbookFactory.create(new File("target.xlsx")); + + // 只读取第一个工作簿 + Sheet sheet = wb.getSheetAt(0); + + Iterator iterator = sheet.iterator(); + while (iterator.hasNext()) { + Row row = iterator.next(); + + StringBuilder builder = new StringBuilder(); + builder.append(row.getCell(0)); + builder.append(" - "); + builder.append(row.getCell(1)); + + System.out.println(builder.toString()); + } + + System.out.println("Excel文件读取成功"); + } + +} diff --git a/storage/src/test/java/com/zfoo/storage/pathmatch/PathMatchTest.java b/storage/src/test/java/com/zfoo/storage/pathmatch/PathMatchTest.java new file mode 100644 index 00000000..34950835 --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/pathmatch/PathMatchTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.pathmatch; + +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import java.io.IOException; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class PathMatchTest { + + @Test + public void resourceTest() throws IOException { + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources("classpath*:com/zfoo/**/*.class"); + for (Resource res : resources) { + System.out.println(res.getDescription()); + } + } + +} diff --git a/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java b/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java new file mode 100644 index 00000000..3a90c377 --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/resource/StudentResource.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.resource; + +import com.zfoo.storage.model.anno.Id; +import com.zfoo.storage.model.anno.Index; +import com.zfoo.storage.model.anno.Resource; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Resource +public class StudentResource { + + @Id + private int id; + + /** + * 索引,默认为可重复的索引 + */ + @Index + private String name; + + private int age; + private String[] courses; + private User[] users; + private User user; + + /** + * 不想映射的字段必须加上transient关键字,这样就不会从Excel中去找对应的列 + */ + private transient String notMapContent; + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public String[] getCourses() { + return courses; + } + + public User[] getUsers() { + return users; + } + + public User getUser() { + return user; + } + + public String getNotMapContent() { + return notMapContent; + } +} diff --git a/storage/src/test/java/com/zfoo/storage/resource/User.java b/storage/src/test/java/com/zfoo/storage/resource/User.java new file mode 100644 index 00000000..d648ba6d --- /dev/null +++ b/storage/src/test/java/com/zfoo/storage/resource/User.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.storage.resource; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class User { + + private String id; + private String name; + private String sex; + private int age; + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getSex() { + return sex; + } + + public int getAge() { + return age; + } +} \ No newline at end of file diff --git a/storage/src/test/resources/application.xml b/storage/src/test/resources/application.xml new file mode 100644 index 00000000..d5487a98 --- /dev/null +++ b/storage/src/test/resources/application.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/storage/src/test/resources/excel/StudentResource.xls b/storage/src/test/resources/excel/StudentResource.xls new file mode 100644 index 0000000000000000000000000000000000000000..9cdcfb0351843c08c8afc89f56a37f5d29aa2479 GIT binary patch literal 22016 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 al!;K~ZdAwJ{K@N%Tj8nV-{CK=f&T+3Zd<+p literal 0 HcmV?d00001 diff --git a/storage/src/test/resources/logback-test.xml b/storage/src/test/resources/logback-test.xml new file mode 100644 index 00000000..bc9d5e55 --- /dev/null +++ b/storage/src/test/resources/logback-test.xml @@ -0,0 +1,44 @@ + + + + + com.zfoo.storage + + + + + + + + ${PATTERN_CONSOLE} + UTF-8 + + + + + + + + + + + + + + + + + + + + + + diff --git a/util/pom.xml b/util/pom.xml new file mode 100644 index 00000000..95adf524 --- /dev/null +++ b/util/pom.xml @@ -0,0 +1,211 @@ + + + 4.0.0 + + com.zfoo + util + 3.0 + + jar + + + + + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + 3.0 + + + + 5.3.4 + 2.4.3 + + + + 1.15 + 2.8.0 + 4.4 + 3.12.0 + 1.4 + 1.2 + 2.14.0 + 4.5.13 + 4.4.14 + 30.1-jre + 3.9.1 + 2.8.6 + 5.0.3 + 2.8.8 + 3.2.0 + 5.5.9 + 5.7.0 + 1.28 + + + + 2.12.1 + 1.2.51 + + 4.1.2 + + 3.27.0-GA + 1.10.22 + + + 4.1.63.Final + + + 3.6.1 + 5.1.0 + + + 4.2.1 + 3.3.0 + + + 4.5.2 + + + 7.9.3 + 4.1.5 + 8.6.2 + + + 1.7.30 + 1.2.3 + + 4.13.1 + + + 11 + UTF-8 + 1.3.5 + + + 3.1.0 + 3.8.1 + 3.2.0 + 3.0.0-M5 + 3.2.0 + 3.2.4 + 2.8.1 + + + ${file.encoding} + ${file.encoding} + + + + + com.zfoo + protocol + ${zfoo.protocol.version} + + + + + org.springframework + spring-context + ${spring.version} + + + + + org.springframework + spring-expression + ${spring.version} + + + + + junit + junit + ${junit.version} + test + + + + + src/main/java + src/test/java + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + ${file.encoding} + + + + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + copy-resources + package + + copy-resources + + + ${file.encoding} + ${project.build.directory}/resource + + + src/main/resources/ + false + + + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + once + 10 + -Dfile.encoding=${file.encoding} + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + + + + diff --git a/util/src/main/java/com/zfoo/util/DomUtils.java b/util/src/main/java/com/zfoo/util/DomUtils.java new file mode 100644 index 00000000..943cffb5 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/DomUtils.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.zfoo.protocol.exception.ExceptionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.StringUtils; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * DOM(Document Object Model)文档对象模型 + *

+ * Convenience methods for working with the DOM API,in particular for working with DOM Nodes and DOM Elements. + *

+ * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class DomUtils { + + private static final XmlMapper MAPPER = XmlMapper.builder() + .defaultUseWrapper(false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true) // 当反序列化有未知属性则抛异常,true打开这个设置 + .build(); + + public static T string2Object(String xml, Class clazz) { + try { + return MAPPER.readValue(xml, clazz); + } catch (IOException e) { + throw new RuntimeException(StringUtils.format("将xml字符串[xml:{}]转换为对象[class:{}]异常[error:{}]", xml, clazz, ExceptionUtils.getStackTrace(e))); + } + } + + public static T file2Object(File xmlFile, Class clazz) { + try { + XMLInputFactory f = XMLInputFactory.newFactory(); + XMLStreamReader sr = f.createXMLStreamReader(new FileInputStream(xmlFile)); + return MAPPER.readValue(sr, clazz); + } catch (Exception e) { + throw new RuntimeException(StringUtils.format("将xml文件[xml:{}]转换为对象[class:{}]异常[error:{}]", xmlFile, clazz, ExceptionUtils.getStackTrace(e))); + } + } + + public static T inputStream2Object(InputStream xmlInputStream, Class clazz) { + try { + return MAPPER.readValue(xmlInputStream, clazz); + } catch (Exception e) { + throw new RuntimeException(StringUtils + .format("将xmlInputStream转换为对象[class:{}]异常[error:{}]", clazz, ExceptionUtils.getStackTrace(e))); + } + } + + /** + * 只返回第一层的孩子节点,不返回第一层孩子节点的孩子节点 + *

+ * Retrieves all child elements of the given DOM element + *

+ * + * @param element the DOM element to analyze + * @return a List of child {@code org.w3c.dom.Element} instances + */ + public static List getChildElements(Element element) { + AssertionUtils.notNull(element, "Element must not be null"); + NodeList nodeList = element.getChildNodes(); + List childEles = new ArrayList(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node instanceof Element) { + childEles.add((Element) node); + } + } + return childEles; + } + + /** + * Retrieves all child elements of the given DOM element that match any of the given element names. + * Only looks at the direct child level of the given element; do not go into further depth + * (in contrast to the DOM API's {@code getElementsByTagName} method). + * + * @param element the DOM element to analyze + * @param childElementNames the child element names to look for + * @return a List of child {@code org.w3c.dom.Element} instances + * @see org.w3c.dom.Element + * @see org.w3c.dom.Element#getElementsByTagName + */ + public static List getChildElementsByTagName(Element element, String... childElementNames) { + AssertionUtils.notNull(element, "Element must not be null"); + AssertionUtils.notNull(childElementNames, "Element names collection must not be null"); + List childEleNameList = Arrays.asList(childElementNames); + NodeList childNodes = element.getChildNodes(); + List elements = new ArrayList<>(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element && nodeNameMatch(node, childEleNameList)) { + elements.add((Element) node); + } + } + return elements; + } + + public static List getChildElementsByTagName(Element ele, String childElementName) { + return getChildElementsByTagName(ele, new String[]{childElementName}); + } + + /** + * Utility method that returns the first child element identified by its name. + * + * @param ele the DOM element to analyze + * @param childEleName the child element name to look for + * @return the {@code org.w3c.dom.Element} instance, or {@code null} if none found + */ + public static Element getFirstChildElementByTagName(Element ele, String childEleName) { + AssertionUtils.notNull(ele, "Element must not be null"); + AssertionUtils.notNull(childEleName, "Element name must not be null"); + NodeList nodeList = ele.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node instanceof Element && nodeNameMatch(node, childEleName)) { + return (Element) node; + } + } + return null; + } + + /* + Namespace-aware equals comparison. + */ + public static boolean nodeNameEquals(Node node, String desiredName) { + AssertionUtils.notNull(node, "Node must not be null"); + AssertionUtils.notNull(desiredName, "Desired name must not be null"); + return nodeNameMatch(node, desiredName); + } + + /* + Matches the given node's name and local name against the given desired names. + */ + private static boolean nodeNameMatch(Node node, Collection desiredNames) { + return (desiredNames.contains(node.getNodeName()) || desiredNames.contains(node.getLocalName())); + } + + /* + Matches the given node's name and local name against the given desired name. + */ + private static boolean nodeNameMatch(Node node, String desiredName) { + return (desiredName.equals(node.getNodeName()) || desiredName.equals(node.getLocalName())); + } + +} diff --git a/util/src/main/java/com/zfoo/util/EnumUtils.java b/util/src/main/java/com/zfoo/util/EnumUtils.java new file mode 100644 index 00000000..10d70cd7 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/EnumUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.zfoo.protocol.util.AssertionUtils; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class EnumUtils { + + public static > boolean isInEnums(String targetEnumName, E[] sourceEnums) { + AssertionUtils.notNull(targetEnumName, sourceEnums); + return Arrays.stream(sourceEnums).anyMatch(sourceEnum -> sourceEnum.name().equals(targetEnumName)); + } + + + public static Set enumerationToSet(Enumeration enumeration) { + var set = new HashSet(); + if (enumeration != null) { + while (enumeration.hasMoreElements()) { + set.add(enumeration.nextElement()); + } + } + return set; + } + +} diff --git a/util/src/main/java/com/zfoo/util/ThreadUtils.java b/util/src/main/java/com/zfoo/util/ThreadUtils.java new file mode 100644 index 00000000..2cebbdee --- /dev/null +++ b/util/src/main/java/com/zfoo/util/ThreadUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class ThreadUtils { + + private static final int WAIT_TIME = 10; + private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS; + + public static void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void shutdown(ExecutorService executor) { + try { + if (!executor.isTerminated()) { + + executor.shutdown(); + + if (!executor.awaitTermination(WAIT_TIME, TIME_UNIT)) { + executor.shutdownNow(); + } + + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void shutdownForkJoinPool() { + try { + ForkJoinPool.commonPool().shutdown(); + + if (ForkJoinPool.commonPool().awaitTermination(WAIT_TIME, TimeUnit.SECONDS)) { + ForkJoinPool.commonPool().shutdownNow(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * 使用kill退出的方式,不能调用这个停止方法 + */ + public static void shutdownApplication() { + new Thread(() -> { + System.exit(0); + }).start(); + } + + +} diff --git a/util/src/main/java/com/zfoo/util/captcha/ArithmeticCaptcha.java b/util/src/main/java/com/zfoo/util/captcha/ArithmeticCaptcha.java new file mode 100644 index 00000000..ab59a7e2 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/ArithmeticCaptcha.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha; + + +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.util.captcha.model.AbstractCaptcha; +import com.zfoo.util.math.RandomUtils; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.Operation; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * png格式验证码 + * + * @author jaysunxiao + * @version 3.0 + */ +public class ArithmeticCaptcha extends AbstractCaptcha { + + /** + * 计算公式 + */ + private String arithmeticString; + + @Override + public void buildCaptcha() { + var sb = new StringBuilder(); + for (var i = 1; i <= length; i++) { + sb.append(RandomUtils.randomInt(10)); + + if (i == length) { + break; + } + + var operation = RandomUtils.randomEle(List.of(Operation.ADD, Operation.SUBTRACT, Operation.MULTIPLY)); + switch (operation) { + case ADD: + sb.append("+"); + break; + case SUBTRACT: + sb.append("-"); + break; + case MULTIPLY: + sb.append("x"); + break; + default: + } + } + + ExpressionParser parser = new SpelExpressionParser(); + Expression exp = parser.parseExpression(sb.toString().replaceAll("x", "*")); + captcha = exp.getValue(String.class); + + sb.append("=?"); + arithmeticString = sb.toString(); + } + + + /** + * 生成验证码 + */ + @Override + public void drawImage(OutputStream out) { + var captchaChars = arithmeticString.toCharArray(); + BufferedImage bi = null; + Graphics2D g2d = null; + try { + bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bi.getGraphics(); + // 填充背景 + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, width, height); + // 抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // 画干扰圆 + drawOval(2, g2d); + // 画字符串 + g2d.setFont(getCaptchaFont().getFont()); + FontMetrics fontMetrics = g2d.getFontMetrics(); + int fW = width / captchaChars.length; // 每一个字符所占的宽度 + int fSp = (fW - (int) fontMetrics.getStringBounds("8", g2d).getWidth()) / 2; // 字符的左右边距 + for (int i = 0; i < captchaChars.length; i++) { + g2d.setColor(color()); + int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(captchaChars[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 + g2d.drawString(String.valueOf(captchaChars[i]), i * fW + fSp + 3, fY - 3); + } + ImageIO.write(bi, "png", out); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(out); + if (g2d != null) { + g2d.dispose(); + } + if (bi != null) { + bi.getGraphics().dispose(); + } + } + } + + @Override + public String toBase64() { + return toBase64("data:image/png;base64,"); + } + + + public String getArithmeticString() { + return arithmeticString; + } + + public void setArithmeticString(String arithmeticString) { + this.arithmeticString = arithmeticString; + } + +} \ No newline at end of file diff --git a/util/src/main/java/com/zfoo/util/captcha/GifCaptcha.java b/util/src/main/java/com/zfoo/util/captcha/GifCaptcha.java new file mode 100644 index 00000000..5a7fbbec --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/GifCaptcha.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha; + + +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.util.captcha.gif.GifEncoder; +import com.zfoo.util.captcha.model.AbstractCaptcha; +import com.zfoo.util.math.RandomUtils; + +import java.awt.*; +import java.awt.geom.CubicCurve2D; +import java.awt.image.BufferedImage; +import java.io.OutputStream; + +/** + * Gif验证码类 + * + * @author jaysunxiao + * @version 3.0 + */ +public class GifCaptcha extends AbstractCaptcha { + + @Override + public void drawImage(OutputStream out) { + try { + char[] captchaChars = captcha.toCharArray(); // 获取验证码数组 + // 随机生成每个文字的颜色 + Color fontColor[] = new Color[length]; + for (int i = 0; i < length; i++) { + fontColor[i] = color(); + } + // 随机生成贝塞尔曲线参数 + int x1 = 5, y1 = RandomUtils.randomInt(5, height / 2); + int x2 = width - 5, y2 = RandomUtils.randomInt(height / 2, height - 5); + int ctrlx = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly = RandomUtils.randomInt(5, height - 5); + if (RandomUtils.randomInt(2) == 0) { + int ty = y1; + y1 = y2; + y2 = ty; + } + int ctrlx1 = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly1 = RandomUtils.randomInt(5, height - 5); + int[][] besselXY = new int[][]{{x1, y1}, {ctrlx, ctrly}, {ctrlx1, ctrly1}, {x2, y2}}; + // 开始画gif每一帧 + GifEncoder gifEncoder = new GifEncoder(); + gifEncoder.setQuality(180); + gifEncoder.setDelay(100); + gifEncoder.setRepeat(0); + gifEncoder.start(out); + for (int i = 0; i < length; i++) { + BufferedImage frame = drawImageFrame(fontColor, captchaChars, i, besselXY); + gifEncoder.addFrame(frame); + frame.flush(); + } + gifEncoder.finish(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(out); + } + } + + @Override + public String toBase64() { + return toBase64("data:image/gif;base64,"); + } + + /** + * 画随机码图 + * + * @param fontColor 随机字体颜色 + * @param strs 字符数组 + * @param flag 透明度 + * @param besselXY 干扰线参数 + * @return BufferedImage + */ + private BufferedImage drawImageFrame(Color[] fontColor, char[] strs, int flag, int[][] besselXY) { + BufferedImage bi = null; + Graphics2D g2d = null; + try { + bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bi.getGraphics(); + // 填充背景颜色 + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, width, height); + // 抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // 画干扰圆圈 + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f * RandomUtils.randomInt(10))); // 设置透明度 + drawOval(2, g2d); + // 画干扰线 + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); // 设置透明度 + g2d.setStroke(new BasicStroke(1.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + g2d.setColor(fontColor[0]); + CubicCurve2D shape = new CubicCurve2D.Double(besselXY[0][0], besselXY[0][1], besselXY[1][0], besselXY[1][1], besselXY[2][0], besselXY[2][1], besselXY[3][0], besselXY[3][1]); + g2d.draw(shape); + // 画验证码 + g2d.setFont(getCaptchaFont().getFont()); + FontMetrics fontMetrics = g2d.getFontMetrics(); + int fW = width / strs.length; // 每一个字符所占的宽度 + int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距 + for (int i = 0; i < strs.length; i++) { + // 设置透明度 + AlphaComposite ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); + g2d.setComposite(ac3); + g2d.setColor(fontColor[i]); + int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(strs[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 + g2d.drawString(String.valueOf(strs[i]), i * fW + fSp + 3, fY - 3); + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + if (g2d != null) { + g2d.dispose(); + } + if (bi != null) { + bi.getGraphics().dispose(); + } + } + return bi; + } + + /** + * 获取透明度,从0到1,自动计算步长 + * + * @param i + * @param j + * @return 透明度 + */ + private float getAlpha(int i, int j) { + int num = i + j; + float r = (float) 1 / (length - 1); + float s = length * r; + return num >= length ? (num * r - s) : num * r; + } + +} diff --git a/util/src/main/java/com/zfoo/util/captcha/PngCaptcha.java b/util/src/main/java/com/zfoo/util/captcha/PngCaptcha.java new file mode 100644 index 00000000..a7c498ce --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/PngCaptcha.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha; + +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.util.captcha.model.AbstractCaptcha; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.OutputStream; + +/** + * png格式验证码 + * + * @author jaysunxiao + * @version 3.0 + */ +public class PngCaptcha extends AbstractCaptcha { + + @Override + public void drawImage(OutputStream out) { + var captchaChars = captcha.toCharArray(); + BufferedImage bi = null; + Graphics2D g2d = null; + try { + bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + g2d = (Graphics2D) bi.getGraphics(); + + // 填充背景 + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, width, height); + // 抗锯齿 + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // 画干扰圆 + drawOval(2, g2d); + // 画干扰线 + g2d.setStroke(new BasicStroke(2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + drawBesselLine(1, g2d); + // 画字符串 + g2d.setFont(getCaptchaFont().getFont()); + FontMetrics fontMetrics = g2d.getFontMetrics(); + int fW = width / captchaChars.length; // 每一个字符所占的宽度 + int fSp = (fW - (int) fontMetrics.getStringBounds("W", g2d).getWidth()) / 2; // 字符的左右边距 + for (int i = 0; i < captchaChars.length; i++) { + g2d.setColor(color()); + int fY = height - ((height - (int) fontMetrics.getStringBounds(String.valueOf(captchaChars[i]), g2d).getHeight()) >> 1); // 文字的纵坐标 + g2d.drawString(String.valueOf(captchaChars[i]), i * fW + fSp + 3, fY - 3); + } + ImageIO.write(bi, "png", out); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(out); + if (g2d != null) { + g2d.dispose(); + } + if (bi != null) { + bi.getGraphics().dispose(); + } + } + } + + @Override + public String toBase64() { + return toBase64("data:image/png;base64,"); + } + +} \ No newline at end of file diff --git a/util/src/main/java/com/zfoo/util/captcha/gif/Encoder.java b/util/src/main/java/com/zfoo/util/captcha/gif/Encoder.java new file mode 100644 index 00000000..439e3433 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/gif/Encoder.java @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha.gif; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Encoder { + static final int BITS = 12; + static final int HSIZE = 5003; // 80% 占用率 + private static final int EOF = -1; + int n_bits; // number of bits/code + int maxbits = BITS; // user settable max # bits/code + int maxcode; // maximum code, given n_bits + int maxmaxcode = 1 << BITS; // should NEVER generate this code + int[] htab = new int[HSIZE]; + int[] codetab = new int[HSIZE]; + int hsize = HSIZE; // for dynamic table sizing + int free_ent = 0; // first unused entry + // block compression parameters -- after all codes are used up, + // and compression rate changes, start over. + boolean clear_flg = false; + int g_init_bits; + int ClearCode; + int EOFCode; + int cur_accum = 0; + int cur_bits = 0; + + // Algorithm: use open addressing double hashing (no chaining) on the + // prefix code / next character combination. We do a variant of Knuth's + // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + // secondary probe. Here, the modular division first probe is gives way + // to a faster exclusive-or manipulation. Also do block compression with + // an adaptive reset, whereby the code table is cleared when the compression + // ratio decreases, but after the table fills. The variable-length output + // codes are re-sized at this point, and a special CLEAR code is generated + // for the decompressor. Late addition: construct the table according to + // file size for noticeable speed improvement on small files. Please direct + // questions about this implementation to ames!jaw. + int masks[] = + { + 0x0000, + 0x0001, + 0x0003, + 0x0007, + 0x000F, + 0x001F, + 0x003F, + 0x007F, + 0x00FF, + 0x01FF, + 0x03FF, + 0x07FF, + 0x0FFF, + 0x1FFF, + 0x3FFF, + 0x7FFF, + 0xFFFF}; + // Number of characters so far in this 'packet' + int a_count; + // Define the storage for the packet accumulator + byte[] accum = new byte[256]; + + // output + // + // Output the given code. + // Inputs: + // code: A n_bits-bit integer. If == -1, then EOF. This assumes + // that n_bits =< wordsize - 1. + // Outputs: + // Outputs code to the file. + // Assumptions: + // Chars are 8 bits long. + // Algorithm: + // Maintain a BITS character long buffer (so that 8 codes will + // fit in it exactly). Use the VAX insv instruction to insert each + // code in turn. When the buffer fills up empty it and start over. + // 图片的宽高 + private int imgW, imgH; + private byte[] pixAry; + private int initCodeSize; // 验证码位数 + private int remaining; // 剩余数量 + private int curPixel; // 像素 + + //---------------------------------------------------------------------------- + + /** + * @param width 宽度 + * @param height 高度 + * @param pixels 像素 + * @param color_depth 颜色 + */ + Encoder(int width, int height, byte[] pixels, int color_depth) { + imgW = width; + imgH = height; + pixAry = pixels; + initCodeSize = Math.max(2, color_depth); + } + + // Add a character to the end of the current packet, and if it is 254 + // characters, flush the packet to disk. + + /** + * @param c 字节 + * @param outs 输出流 + * @throws IOException IO异常 + */ + void char_out(byte c, OutputStream outs) throws IOException { + accum[a_count++] = c; + if (a_count >= 254) { + flush_char(outs); + } + } + + // Clear out the hash table + + // table clear for block compress + + /** + * @param outs 输出流 + * @throws IOException IO异常 + */ + void cl_block(OutputStream outs) throws IOException { + cl_hash(hsize); + free_ent = ClearCode + 2; + clear_flg = true; + + output(ClearCode, outs); + } + + // reset code table + + /** + * @param hsize int + */ + void cl_hash(int hsize) { + for (int i = 0; i < hsize; ++i) { + htab[i] = -1; + } + } + + /** + * @param init_bits int + * @param outs 输出流 + * @throws IOException IO异常 + */ + void compress(int init_bits, OutputStream outs) throws IOException { + int fcode; + int i /* = 0 */; + int c; + int ent; + int disp; + int hsize_reg; + int hshift; + + // Set up the globals: g_init_bits - initial number of bits + g_init_bits = init_bits; + + // Set up the necessary values + clear_flg = false; + n_bits = g_init_bits; + maxcode = MAXCODE(n_bits); + + ClearCode = 1 << (init_bits - 1); + EOFCode = ClearCode + 1; + free_ent = ClearCode + 2; + + a_count = 0; // clear packet + + ent = nextPixel(); + + hshift = 0; + for (fcode = hsize; fcode < 65536; fcode *= 2) { + ++hshift; + } + hshift = 8 - hshift; // set hash code range bound + + hsize_reg = hsize; + cl_hash(hsize_reg); // clear hash table + + output(ClearCode, outs); + + outer_loop: + while ((c = nextPixel()) != EOF) { + fcode = (c << maxbits) + ent; + i = (c << hshift) ^ ent; // xor hashing + + if (htab[i] == fcode) { + ent = codetab[i]; + continue; + } else if (htab[i] >= 0) // non-empty slot + { + disp = hsize_reg - i; // secondary hash (after G. Knott) + if (i == 0) { + disp = 1; + } + do { + if ((i -= disp) < 0) { + i += hsize_reg; + } + + if (htab[i] == fcode) { + ent = codetab[i]; + continue outer_loop; + } + } while (htab[i] >= 0); + } + output(ent, outs); + ent = c; + if (free_ent < maxmaxcode) { + codetab[i] = free_ent++; // code -> hashtable + htab[i] = fcode; + } else { + cl_block(outs); + } + } + // Put out the final code. + output(ent, outs); + output(EOFCode, outs); + } + + //---------------------------------------------------------------------------- + + /** + * @param os 输出流 + * @throws IOException IO异常 + */ + void encode(OutputStream os) throws IOException { + os.write(initCodeSize); // write "initial code size" byte + + remaining = imgW * imgH; // reset navigation variables + curPixel = 0; + + compress(initCodeSize + 1, os); // compress and write the pixel data + + os.write(0); // write block terminator + } + + // Flush the packet to disk, and reset the accumulator + + /** + * @param outs 输出流 + * @throws IOException IO异常 + */ + void flush_char(OutputStream outs) throws IOException { + if (a_count > 0) { + outs.write(a_count); + outs.write(accum, 0, a_count); + a_count = 0; + } + } + + /** + * @param n_bits int + * @return int + */ + final int MAXCODE(int n_bits) { + return (1 << n_bits) - 1; + } + + //---------------------------------------------------------------------------- + // Return the next pixel from the image + //---------------------------------------------------------------------------- + + /** + * @return int + */ + private int nextPixel() { + if (remaining == 0) { + return EOF; + } + + --remaining; + + byte pix = pixAry[curPixel++]; + + return pix & 0xff; + } + + /** + * @param code int + * @param outs 输出流 + * @throws IOException IO异常 + */ + void output(int code, OutputStream outs) throws IOException { + cur_accum &= masks[cur_bits]; + + if (cur_bits > 0) { + cur_accum |= (code << cur_bits); + } else { + cur_accum = code; + } + + cur_bits += n_bits; + + while (cur_bits >= 8) { + char_out((byte) (cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + + // If the next entry is going to be too big for the code size, + // then increase it, if possible. + if (free_ent > maxcode || clear_flg) { + if (clear_flg) { + maxcode = MAXCODE(n_bits = g_init_bits); + clear_flg = false; + } else { + ++n_bits; + if (n_bits == maxbits) { + maxcode = maxmaxcode; + } else { + maxcode = MAXCODE(n_bits); + } + } + } + + if (code == EOFCode) { + // At EOF, write the rest of the buffer. + while (cur_bits > 0) { + char_out((byte) (cur_accum & 0xff), outs); + cur_accum >>= 8; + cur_bits -= 8; + } + + flush_char(outs); + } + } +} diff --git a/util/src/main/java/com/zfoo/util/captcha/gif/GifEncoder.java b/util/src/main/java/com/zfoo/util/captcha/gif/GifEncoder.java new file mode 100644 index 00000000..547f09c6 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/gif/GifEncoder.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha.gif; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.*; + +public class GifEncoder { + protected int width; // image size + protected int height; + protected Color transparent = null; // transparent color if given + protected int transIndex; // transparent index in color table + protected int repeat = -1; // no repeat + protected int delay = 0; // frame delay (hundredths) + protected boolean started = false; // ready to output frames + protected OutputStream out; + protected BufferedImage image; // current frame + protected byte[] pixels; // BGR byte array from frame + protected byte[] indexedPixels; // converted frame indexed to palette + protected int colorDepth; // number of bit planes + protected byte[] colorTab; // RGB palette + protected boolean[] usedEntry = new boolean[256]; // active palette entries + protected int palSize = 7; // color table size (bits-1) + protected int dispose = -1; // disposal code (-1 = use default) + protected boolean closeStream = false; // close stream when finished + protected boolean firstFrame = true; + protected boolean sizeSet = false; // if false, get size from first frame + protected int sample = 10; // default sample interval for quantizer + + /** + * Sets the delay time between each frame, or changes it + * for subsequent frames (applies to last frame added). + * + * @param ms int delay time in milliseconds + */ + public void setDelay(int ms) { + delay = Math.round(ms / 10.0f); + } + + /** + * Sets the GIF frame disposal code for the last added frame + * and any subsequent frames. Default is 0 if no transparent + * color has been set, otherwise 2. + * + * @param code int disposal code. + */ + public void setDispose(int code) { + if (code >= 0) { + dispose = code; + } + } + + /** + * Sets the number of times the set of GIF frames + * should be played. Default is 1; 0 means play + * indefinitely. Must be invoked before the first + * image is added. + * + * @param iter int number of iterations. + */ + public void setRepeat(int iter) { + if (iter >= 0) { + repeat = iter; + } + } + + /** + * Sets the transparent color for the last added frame + * and any subsequent frames. + * Since all colors are subject to modification + * in the quantization process, the color in the final + * palette for each frame closest to the given color + * becomes the transparent color for that frame. + * May be set to null to indicate no transparent color. + * + * @param c Color to be treated as transparent on display. + */ + public void setTransparent(Color c) { + transparent = c; + } + + /** + * Adds next GIF frame. The frame is not written immediately, but is + * actually deferred until the next frame is received so that timing + * data can be inserted. Invoking finish() flushes all + * frames. If setSize was not invoked, the size of the + * first image is used for all subsequent frames. + * + * @param im BufferedImage containing frame to write. + * @return true if successful. + */ + public boolean addFrame(BufferedImage im) { + if ((im == null) || !started) { + return false; + } + boolean ok = true; + try { + if (!sizeSet) { + // use first frame's size + setSize(im.getWidth(), im.getHeight()); + } + image = im; + getImagePixels(); // convert to correct format if necessary + analyzePixels(); // build color table & map pixels + if (firstFrame) { + writeLSD(); // logical screen descriptior + writePalette(); // global color table + if (repeat >= 0) { + // use NS app extension to indicate reps + writeNetscapeExt(); + } + } + writeGraphicCtrlExt(); // write graphic control extension + writeImageDesc(); // image descriptor + if (!firstFrame) { + writePalette(); // local color table + } + writePixels(); // encode and write pixel data + firstFrame = false; + } catch (IOException e) { + ok = false; + } + + return ok; + } + + //added by alvaro + public boolean outFlush() { + boolean ok = true; + try { + out.flush(); + return ok; + } catch (IOException e) { + ok = false; + } + + return ok; + } + + public byte[] getFrameByteArray() { + return ((ByteArrayOutputStream) out).toByteArray(); + } + + /** + * Flushes any pending data and closes output file. + * If writing to an OutputStream, the stream is not + * closed. + * + * @return boolean + */ + public boolean finish() { + if (!started) { + return false; + } + boolean ok = true; + started = false; + try { + out.write(0x3b); // gif trailer + out.flush(); + if (closeStream) { + out.close(); + } + } catch (IOException e) { + ok = false; + } + + return ok; + } + + public void reset() { + // reset for subsequent use + transIndex = 0; + out = null; + image = null; + pixels = null; + indexedPixels = null; + colorTab = null; + closeStream = false; + firstFrame = true; + } + + /** + * Sets frame rate in frames per second. Equivalent to + * setDelay(1000/fps). + * + * @param fps float frame rate (frames per second) + */ + public void setFrameRate(float fps) { + if (fps != 0f) { + delay = Math.round(100f / fps); + } + } + + /** + * Sets quality of color quantization (conversion of images + * to the maximum 256 colors allowed by the GIF specification). + * Lower values (minimum = 1) produce better colors, but slow + * processing significantly. 10 is the default, and produces + * good color mapping at reasonable speeds. Values greater + * than 20 do not yield significant improvements in speed. + * + * @param quality int greater than 0. + */ + public void setQuality(int quality) { + if (quality < 1) { + quality = 1; + } + sample = quality; + } + + /** + * Sets the GIF frame size. The default size is the + * size of the first frame added if this method is + * not invoked. + * + * @param w int frame width. + * @param h int frame width. + */ + public void setSize(int w, int h) { + if (started && !firstFrame) { + return; + } + width = w; + height = h; + if (width < 1) { + width = 320; + } + if (height < 1) { + height = 240; + } + sizeSet = true; + } + + /** + * Initiates GIF file creation on the given stream. The stream + * is not closed automatically. + * + * @param os OutputStream on which GIF images are written. + * @return false if initial write failed. + */ + public boolean start(OutputStream os) { + if (os == null) { + return false; + } + boolean ok = true; + closeStream = false; + out = os; + try { + writeString("GIF89a"); // header + } catch (IOException e) { + ok = false; + } + return started = ok; + } + + /** + * Initiates writing of a GIF file with the specified name. + * + * @param file String containing output file name. + * @return false if open or initial write failed. + */ + public boolean start(String file) { + boolean ok = true; + try { + out = new BufferedOutputStream(new FileOutputStream(file)); + ok = start(out); + closeStream = true; + } catch (IOException e) { + ok = false; + } + return started = ok; + } + + /** + * Analyzes image colors and creates color map. + */ + protected void analyzePixels() { + int len = pixels.length; + int nPix = len / 3; + indexedPixels = new byte[nPix]; + Quant nq = new Quant(pixels, len, sample); + // initialize quantizer + colorTab = nq.process(); // create reduced palette + // convert map from BGR to RGB + for (int i = 0; i < colorTab.length; i += 3) { + byte temp = colorTab[i]; + colorTab[i] = colorTab[i + 2]; + colorTab[i + 2] = temp; + usedEntry[i / 3] = false; + } + // map image pixels to new palette + int k = 0; + for (int i = 0; i < nPix; i++) { + int index = + nq.map(pixels[k++] & 0xff, + pixels[k++] & 0xff, + pixels[k++] & 0xff); + usedEntry[index] = true; + indexedPixels[i] = (byte) index; + } + pixels = null; + colorDepth = 8; + palSize = 7; + // get closest match to transparent color if specified + if (transparent != null) { + transIndex = findClosest(transparent); + } + } + + /** + * Returns index of palette color closest to c + * + * @param c color + * @return int + */ + protected int findClosest(Color c) { + if (colorTab == null) { + return -1; + } + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + int minpos = 0; + int dmin = 256 * 256 * 256; + int len = colorTab.length; + for (int i = 0; i < len; ) { + int dr = r - (colorTab[i++] & 0xff); + int dg = g - (colorTab[i++] & 0xff); + int db = b - (colorTab[i] & 0xff); + int d = dr * dr + dg * dg + db * db; + int index = i / 3; + if (usedEntry[index] && (d < dmin)) { + dmin = d; + minpos = index; + } + i++; + } + return minpos; + } + + /** + * Extracts image pixels into byte array "pixels" + */ + protected void getImagePixels() { + int w = image.getWidth(); + int h = image.getHeight(); + int type = image.getType(); + if ((w != width) + || (h != height) + || (type != BufferedImage.TYPE_3BYTE_BGR)) { + // create new image with right size/format + BufferedImage temp = + new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + Graphics2D g = temp.createGraphics(); + g.drawImage(image, 0, 0, null); + image = temp; + } + pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + } + + /** + * Writes Graphic Control Extension + * + * @throws IOException IO异常 + */ + protected void writeGraphicCtrlExt() throws IOException { + out.write(0x21); // extension introducer + out.write(0xf9); // GCE label + out.write(4); // data block size + int transp, disp; + if (transparent == null) { + transp = 0; + disp = 0; // dispose = no action + } else { + transp = 1; + disp = 2; // force clear if using transparent color + } + if (dispose >= 0) { + disp = dispose & 7; // user override + } + disp <<= 2; + + // packed fields + out.write(0 | // 1:3 reserved + disp | // 4:6 disposal + 0 | // 7 user input - 0 = none + transp); // 8 transparency flag + + writeShort(delay); // delay x 1/100 sec + out.write(transIndex); // transparent color index + out.write(0); // block terminator + } + + /** + * Writes Image Descriptor + * + * @throws IOException IO异常 + */ + protected void writeImageDesc() throws IOException { + out.write(0x2c); // image separator + writeShort(0); // image position x,y = 0,0 + writeShort(0); + writeShort(width); // image size + writeShort(height); + // packed fields + if (firstFrame) { + // no LCT - GCT is used for first (or only) frame + out.write(0); + } else { + // specify normal LCT + out.write(0x80 | // 1 local color table 1=yes + 0 | // 2 interlace - 0=no + 0 | // 3 sorted - 0=no + 0 | // 4-5 reserved + palSize); // 6-8 size of color table + } + } + + /** + * Writes Logical Screen Descriptor + * + * @throws IOException IO异常 + */ + protected void writeLSD() throws IOException { + // logical screen size + writeShort(width); + writeShort(height); + // packed fields + out.write((0x80 | // 1 : global color table flag = 1 (gct used) + 0x70 | // 2-4 : color resolution = 7 + 0x00 | // 5 : gct sort flag = 0 + palSize)); // 6-8 : gct size + + out.write(0); // background color index + out.write(0); // pixel aspect ratio - assume 1:1 + } + + /** + * Writes Netscape application extension to define + * repeat count. + * + * @throws IOException IO异常 + */ + protected void writeNetscapeExt() throws IOException { + out.write(0x21); // extension introducer + out.write(0xff); // app extension label + out.write(11); // block size + writeString("NETSCAPE" + "2.0"); // app id + auth code + out.write(3); // sub-block size + out.write(1); // loop sub-block id + writeShort(repeat); // loop count (extra iterations, 0=repeat forever) + out.write(0); // block terminator + } + + /** + * Writes color table + * + * @throws IOException IO异常 + */ + protected void writePalette() throws IOException { + out.write(colorTab, 0, colorTab.length); + int n = (3 * 256) - colorTab.length; + for (int i = 0; i < n; i++) { + out.write(0); + } + } + + /** + * Encodes and writes pixel data + * + * @throws IOException IO异常 + */ + protected void writePixels() throws IOException { + Encoder encoder = new Encoder(width, height, indexedPixels, colorDepth); + encoder.encode(out); + } + + /** + * Write 16-bit value to output stream, LSB first + * + * @param value int + * @throws IOException IO异常 + */ + protected void writeShort(int value) throws IOException { + out.write(value & 0xff); + out.write((value >> 8) & 0xff); + } + + /** + * Writes string to output stream + * + * @param s string + * @throws IOException IO异常 + */ + protected void writeString(String s) throws IOException { + for (int i = 0; i < s.length(); i++) { + out.write((byte) s.charAt(i)); + } + } +} diff --git a/util/src/main/java/com/zfoo/util/captcha/gif/Quant.java b/util/src/main/java/com/zfoo/util/captcha/gif/Quant.java new file mode 100644 index 00000000..af04d79c --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/gif/Quant.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha.gif; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Quant { + protected static final int netsize = 256; /* number of colours used */ + + /* four primes near 500 - assume no image has a length so large */ + /* that it is divisible by all four primes */ + protected static final int prime1 = 499; + protected static final int prime2 = 491; + protected static final int prime3 = 487; + protected static final int prime4 = 503; + + protected static final int minpicturebytes = (3 * prime4); + /* minimum size for input image */ + + /* Program Skeleton + ---------------- + [select samplefac in range 1..30] + [read image from input file] + pic = (unsigned char*) malloc(3*width*height); + initnet(pic,3*width*height,samplefac); + learn(); + unbiasnet(); + [write output image header, using writecolourmap(f)] + inxbuild(); + write output image using inxsearch(b,g,r) */ + + /* Network Definitions + ------------------- */ + + protected static final int maxnetpos = (netsize - 1); + protected static final int netbiasshift = 4; /* bias for colour values */ + protected static final int ncycles = 100; /* no. of learning cycles */ + + /* defs for freq and bias */ + protected static final int intbiasshift = 16; /* bias for fractions */ + protected static final int intbias = (((int) 1) << intbiasshift); + protected static final int gammashift = 10; /* gamma = 1024 */ + protected static final int gamma = (((int) 1) << gammashift); + protected static final int betashift = 10; + protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */ + protected static final int betagamma = + (intbias << (gammashift - betashift)); + + /* defs for decreasing radius factor */ + protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */ + protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ + protected static final int radiusbias = (((int) 1) << radiusbiasshift); + protected static final int initradius = (initrad * radiusbias); /* and decreases by a */ + protected static final int radiusdec = 30; /* factor of 1/30 each cycle */ + + /* defs for decreasing alpha factor */ + protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */ + protected static final int initalpha = (((int) 1) << alphabiasshift); + /* radbias and alpharadbias used for radpower calculation */ + protected static final int radbiasshift = 8; + protected static final int radbias = (((int) 1) << radbiasshift); + protected static final int alpharadbshift = (alphabiasshift + radbiasshift); + protected static final int alpharadbias = (((int) 1) << alpharadbshift); + protected int alphadec; /* biased by 10 bits */ + + /* Types and Global Variables + -------------------------- */ + protected byte[] thepicture; /* the input image itself */ + protected int lengthcount; /* lengthcount = H*W*3 */ + + protected int samplefac; /* sampling factor 1..30 */ + + // typedef int pixel[4]; /* BGRc */ + protected int[][] network; /* the network itself - [netsize][4] */ + + protected int[] netindex = new int[256]; + /* for network lookup - really 256 */ + + protected int[] bias = new int[netsize]; + /* bias and freq arrays for learning */ + protected int[] freq = new int[netsize]; + protected int[] radpower = new int[initrad]; + /* radpower for precomputation */ + + /* Initialise network in range (0,0,0) to (255,255,255) and set parameters + ----------------------------------------------------------------------- */ + public Quant(byte[] thepic, int len, int sample) { + + int i; + int[] p; + + thepicture = thepic; + lengthcount = len; + samplefac = sample; + + network = new int[netsize][]; + for (i = 0; i < netsize; i++) { + network[i] = new int[4]; + p = network[i]; + p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; + freq[i] = intbias / netsize; /* 1/netsize */ + bias[i] = 0; + } + } + + public byte[] colorMap() { + byte[] map = new byte[3 * netsize]; + int[] index = new int[netsize]; + for (int i = 0; i < netsize; i++) { + index[network[i][3]] = i; + } + int k = 0; + for (int i = 0; i < netsize; i++) { + int j = index[i]; + map[k++] = (byte) (network[j][0]); + map[k++] = (byte) (network[j][1]); + map[k++] = (byte) (network[j][2]); + } + return map; + } + + /* Insertion sort of network and building of netindex[0..255] (to do after unbias) + ------------------------------------------------------------------------------- */ + public void inxbuild() { + + int i, j, smallpos, smallval; + int[] p; + int[] q; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + for (i = 0; i < netsize; i++) { + p = network[i]; + smallpos = i; + smallval = p[1]; /* index on g */ + /* find smallest in i..netsize-1 */ + for (j = i + 1; j < netsize; j++) { + q = network[j]; + if (q[1] < smallval) { /* index on g */ + smallpos = j; + smallval = q[1]; /* index on g */ + } + } + q = network[smallpos]; + /* swap p (i) and q (smallpos) entries */ + if (i != smallpos) { + j = q[0]; + q[0] = p[0]; + p[0] = j; + j = q[1]; + q[1] = p[1]; + p[1] = j; + j = q[2]; + q[2] = p[2]; + p[2] = j; + j = q[3]; + q[3] = p[3]; + p[3] = j; + } + /* smallval entry is now in position i */ + if (smallval != previouscol) { + netindex[previouscol] = (startpos + i) >> 1; + for (j = previouscol + 1; j < smallval; j++) { + netindex[j] = i; + } + previouscol = smallval; + startpos = i; + } + } + netindex[previouscol] = (startpos + maxnetpos) >> 1; + for (j = previouscol + 1; j < 256; j++) { + netindex[j] = maxnetpos; /* really 256 */ + } + } + + /* Main Learning Loop + ------------------ */ + public void learn() { + + int i, j, b, g, r; + int radius, rad, alpha, step, delta, samplepixels; + byte[] p; + int pix, lim; + + if (lengthcount < minpicturebytes) { + samplefac = 1; + } + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + pix = 0; + lim = lengthcount; + samplepixels = lengthcount / (3 * samplefac); + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if (rad <= 1) { + rad = 0; + } + for (i = 0; i < rad; i++) { + radpower[i] = + alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); + } + + //fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); + + if (lengthcount < minpicturebytes) { + step = 3; + } else if ((lengthcount % prime1) != 0) { + step = 3 * prime1; + } else { + if ((lengthcount % prime2) != 0) { + step = 3 * prime2; + } else { + if ((lengthcount % prime3) != 0) { + step = 3 * prime3; + } else { + step = 3 * prime4; + } + } + } + + i = 0; + while (i < samplepixels) { + b = (p[pix + 0] & 0xff) << netbiasshift; + g = (p[pix + 1] & 0xff) << netbiasshift; + r = (p[pix + 2] & 0xff) << netbiasshift; + j = contest(b, g, r); + + altersingle(alpha, j, b, g, r); + if (rad != 0) { + alterneigh(rad, j, b, g, r); /* alter neighbours */ + } + + pix += step; + if (pix >= lim) { + pix -= lengthcount; + } + + i++; + if (delta == 0) { + delta = 1; + } + if (i % delta == 0) { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if (rad <= 1) { + rad = 0; + } + for (j = 0; j < rad; j++) { + radpower[j] = + alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); + } + } + } + //fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha); + } + + /* Search for BGR values 0..255 (after net is unbiased) and return colour index + ---------------------------------------------------------------------------- */ + public int map(int b, int g, int r) { + + int i, j, dist, a, bestd; + int[] p; + int best; + + bestd = 1000; /* biggest possible dist is 256*3 */ + best = -1; + i = netindex[g]; /* index on g */ + j = i - 1; /* start at netindex[g] and work outwards */ + + while ((i < netsize) || (j >= 0)) { + if (i < netsize) { + p = network[i]; + dist = p[1] - g; /* inx key */ + if (dist >= bestd) { + i = netsize; /* stop iter */ + } else { + i++; + if (dist < 0) { + dist = -dist; + } + a = p[0] - b; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + if (j >= 0) { + p = network[j]; + dist = g - p[1]; /* inx key - reverse dif */ + if (dist >= bestd) { + j = -1; /* stop iter */ + } else { + j--; + if (dist < 0) { + dist = -dist; + } + a = p[0] - b; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + a = p[2] - r; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + bestd = dist; + best = p[3]; + } + } + } + } + } + return (best); + } + + public byte[] process() { + learn(); + unbiasnet(); + inxbuild(); + return colorMap(); + } + + /* Unbias network to give byte values 0..255 and record position i to prepare for sort + ----------------------------------------------------------------------------------- */ + public void unbiasnet() { + + int i, j; + + for (i = 0; i < netsize; i++) { + network[i][0] >>= netbiasshift; + network[i][1] >>= netbiasshift; + network[i][2] >>= netbiasshift; + network[i][3] = i; /* record colour no */ + } + } + + /* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] + --------------------------------------------------------------------------------- */ + protected void alterneigh(int rad, int i, int b, int g, int r) { + + int j, k, lo, hi, a, m; + int[] p; + + lo = i - rad; + if (lo < -1) { + lo = -1; + } + hi = i + rad; + if (hi > netsize) { + hi = netsize; + } + + j = i + 1; + k = i - 1; + m = 1; + while ((j < hi) || (k > lo)) { + a = radpower[m++]; + if (j < hi) { + p = network[j++]; + try { + p[0] -= (a * (p[0] - b)) / alpharadbias; + p[1] -= (a * (p[1] - g)) / alpharadbias; + p[2] -= (a * (p[2] - r)) / alpharadbias; + } catch (Exception e) { + } // prevents 1.3 miscompilation + } + if (k > lo) { + p = network[k--]; + try { + p[0] -= (a * (p[0] - b)) / alpharadbias; + p[1] -= (a * (p[1] - g)) / alpharadbias; + p[2] -= (a * (p[2] - r)) / alpharadbias; + } catch (Exception e) { + } + } + } + } + + /* Move neuron i towards biased (b,g,r) by factor alpha + ---------------------------------------------------- */ + protected void altersingle(int alpha, int i, int b, int g, int r) { + + /* alter hit neuron */ + int[] n = network[i]; + n[0] -= (alpha * (n[0] - b)) / initalpha; + n[1] -= (alpha * (n[1] - g)) / initalpha; + n[2] -= (alpha * (n[2] - r)) / initalpha; + } + + /* Search for biased BGR values + ---------------------------- */ + protected int contest(int b, int g, int r) { + + /* finds closest neuron (min dist) and updates freq */ + /* finds best neuron (min dist-bias) and returns position */ + /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ + /* bias[i] = gamma*((1/netsize)-freq[i]) */ + + int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + int[] n; + + bestd = ~(((int) 1) << 31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + + for (i = 0; i < netsize; i++) { + n = network[i]; + dist = n[0] - b; + if (dist < 0) { + dist = -dist; + } + a = n[1] - g; + if (a < 0) { + a = -a; + } + dist += a; + a = n[2] - r; + if (a < 0) { + a = -a; + } + dist += a; + if (dist < bestd) { + bestd = dist; + bestpos = i; + } + biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); + if (biasdist < bestbiasd) { + bestbiasd = biasdist; + bestbiaspos = i; + } + betafreq = (freq[i] >> betashift); + freq[i] -= betafreq; + bias[i] += (betafreq << gammashift); + } + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + return (bestbiaspos); + } +} \ No newline at end of file diff --git a/util/src/main/java/com/zfoo/util/captcha/model/AbstractCaptcha.java b/util/src/main/java/com/zfoo/util/captcha/model/AbstractCaptcha.java new file mode 100644 index 00000000..546558d3 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/model/AbstractCaptcha.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha.model; + +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.math.RandomUtils; + +import java.awt.*; +import java.awt.geom.CubicCurve2D; +import java.awt.geom.QuadCurve2D; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.util.Base64; + +/** + * 验证码抽象类 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AbstractCaptcha { + + /** + * 常用颜色 + */ + private static final int[][] COLOR = new int[][] + { + {0, 135, 255}, + {51, 153, 51}, + {255, 102, 102}, + {255, 153, 0}, + {153, 102, 0}, + {153, 102, 153}, + {51, 153, 153}, + {102, 102, 255}, + {0, 102, 204}, + {204, 51, 51}, + {0, 153, 204}, + {0, 51, 102} + }; + /** + * 验证码随机字符长度 + */ + protected int length = 5; + /** + * 验证码显示宽度 + */ + protected int width = 130; + /** + * 验证码显示高度 + */ + protected int height = 48; + /** + * 当前验证码 + */ + protected String captcha; + private CaptchaCharEnum captchaChar = CaptchaCharEnum.ARAB_ENGLISH; + private CaptchaFontEnum captchaFont = CaptchaFontEnum.actionj; + + public void buildCaptcha() { + switch (captchaChar) { + case ARAB_NUMBER: + captcha = RandomUtils.randomString(StringUtils.ARAB_NUMBER, length); + break; + case ENGLISH_CHAR: + captcha = RandomUtils.randomString(StringUtils.ENGLISH_CHAR, length); + break; + case ARAB_ENGLISH: + captcha = RandomUtils.randomString(length); + break; + default: + } + } + + + /** + * 给定范围获得随机颜色 + * + * @param fc 0-255 + * @param bc 0-255 + * @return 随机颜色 + */ + protected Color color(int fc, int bc) { + if (fc > 255) { + fc = 255; + } + if (bc > 255) { + bc = 255; + } + int r = fc + RandomUtils.randomInt(bc - fc); + int g = fc + RandomUtils.randomInt(bc - fc); + int b = fc + RandomUtils.randomInt(bc - fc); + return new Color(r, g, b); + } + + /** + * 获取随机常用颜色 + * + * @return 随机颜色 + */ + protected Color color() { + int[] color = COLOR[RandomUtils.randomInt(COLOR.length)]; + return new Color(color[0], color[1], color[2]); + } + + /** + * 验证码输出,抽象方法,由子类实现 + */ + public abstract void drawImage(OutputStream os); + + /** + * 输出base64编码 + * + * @return base64编码字符串 + */ + public abstract String toBase64(); + + /** + * 输出base64编码 + * + * @param type 编码头 + * @return base64编码字符串 + */ + public String toBase64(String type) { + var outputStream = new ByteArrayOutputStream(); + drawImage(outputStream); + var base64 = type + Base64.getEncoder().encodeToString(outputStream.toByteArray()); + IOUtils.closeIO(outputStream); + return base64; + } + + /** + * 获取当前的验证码 + */ + public String captcha() { + return captcha; + } + + + /** + * 随机画干扰线 + * + * @param num 数量 + * @param g Graphics2D + */ + public void drawLine(int num, Graphics2D g) { + drawLine(num, null, g); + } + + /** + * 随机画干扰线 + * + * @param num 数量 + * @param color 颜色 + * @param g Graphics2D + */ + public void drawLine(int num, Color color, Graphics2D g) { + for (int i = 0; i < num; i++) { + g.setColor(color == null ? color() : color); + int x1 = RandomUtils.randomInt(-10, width - 10); + int y1 = RandomUtils.randomInt(5, height - 5); + int x2 = RandomUtils.randomInt(10, width + 10); + int y2 = RandomUtils.randomInt(2, height - 2); + g.drawLine(x1, y1, x2, y2); + } + } + + /** + * 随机画干扰圆 + * + * @param num 数量 + * @param g Graphics2D + */ + public void drawOval(int num, Graphics2D g) { + drawOval(num, null, g); + } + + /** + * 随机画干扰圆 + * + * @param num 数量 + * @param color 颜色 + * @param g Graphics2D + */ + public void drawOval(int num, Color color, Graphics2D g) { + for (int i = 0; i < num; i++) { + g.setColor(color == null ? color() : color); + int w = 5 + RandomUtils.randomInt(10); + g.drawOval(RandomUtils.randomInt(width - 25), RandomUtils.randomInt(height - 15), w, w); + } + } + + /** + * 随机画贝塞尔曲线 + * + * @param num 数量 + * @param g Graphics2D + */ + public void drawBesselLine(int num, Graphics2D g) { + drawBesselLine(num, null, g); + } + + /** + * 随机画贝塞尔曲线 + * + * @param num 数量 + * @param color 颜色 + * @param g Graphics2D + */ + public void drawBesselLine(int num, Color color, Graphics2D g) { + for (var i = 0; i < num; i++) { + g.setColor(color == null ? color() : color); + int x1 = 5, y1 = RandomUtils.randomInt(5, height / 2); + int x2 = width - 5, y2 = RandomUtils.randomInt(height / 2, height - 5); + int ctrlx = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly = RandomUtils.randomInt(5, height - 5); + if (RandomUtils.randomInt(2) == 0) { + int ty = y1; + y1 = y2; + y2 = ty; + } + if (RandomUtils.randomInt(2) == 0) { // 二阶贝塞尔曲线 + QuadCurve2D shape = new QuadCurve2D.Double(); + shape.setCurve(x1, y1, ctrlx, ctrly, x2, y2); + g.draw(shape); + } else { // 三阶贝塞尔曲线 + int ctrlx1 = RandomUtils.randomInt(width / 4, width / 4 * 3), ctrly1 = RandomUtils.randomInt(5, height - 5); + CubicCurve2D shape = new CubicCurve2D.Double(x1, y1, ctrlx, ctrly, ctrlx1, ctrly1, x2, y2); + g.draw(shape); + } + } + } + + + public CaptchaCharEnum getCaptchaChar() { + return captchaChar; + } + + public void setCaptchaChar(CaptchaCharEnum captchaChar) { + this.captchaChar = captchaChar; + } + + public CaptchaFontEnum getCaptchaFont() { + return captchaFont; + } + + public void setCaptchaFont(CaptchaFontEnum captchaFont) { + this.captchaFont = captchaFont; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public int getHeight() { + return height; + } + + public void setHeight(int height) { + this.height = height; + } +} \ No newline at end of file diff --git a/util/src/main/java/com/zfoo/util/captcha/model/CaptchaCharEnum.java b/util/src/main/java/com/zfoo/util/captcha/model/CaptchaCharEnum.java new file mode 100644 index 00000000..edb05f59 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/model/CaptchaCharEnum.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha.model; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public enum CaptchaCharEnum { + + /** + * 纯数字 + */ + ARAB_NUMBER, + + /** + * 纯英文字母 + */ + ENGLISH_CHAR, + + /** + * 数字和英文结合 + */ + ARAB_ENGLISH, + + ; + +} diff --git a/util/src/main/java/com/zfoo/util/captcha/model/CaptchaFontEnum.java b/util/src/main/java/com/zfoo/util/captcha/model/CaptchaFontEnum.java new file mode 100644 index 00000000..4d95eb10 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/captcha/model/CaptchaFontEnum.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha.model; + +import com.zfoo.protocol.util.ClassUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.awt.*; +import java.io.InputStream; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public enum CaptchaFontEnum { + + /** + * 默认字体 + */ + actionj, + + epilog, + + fresnel, + + headache, + + lexo, + + prefix, + + progbot, + + ransom, + + robot, + + scandal, + + ; + + private static final Font DEFAULT_FONT = new Font("Arial", Font.BOLD, 32); + + private Font font; + + CaptchaFontEnum() { + InputStream inputStream = null; + try { + inputStream = ClassUtils.getFileFromClassPath(StringUtils.format("captcha/{}.ttf", this.name())); + font = Font.createFont(Font.TRUETYPE_FONT, inputStream).deriveFont(Font.BOLD, 32f); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(inputStream); + } + } + + public Font getFont() { + return font; + } +} diff --git a/util/src/main/java/com/zfoo/util/math/Combinatorics.java b/util/src/main/java/com/zfoo/util/math/Combinatorics.java new file mode 100644 index 00000000..6888d6de --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/Combinatorics.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class Combinatorics {// |kɒmbinəˋtɒ:riks|n.组合学 + + + //输出数组a的startIndex~endIndex(包括端点)的全排列 + public void permutation(int[] a, int startIndex, int endIndex) { + + if (startIndex == endIndex) { + for (int i = 0; i <= endIndex; i++) { + System.out.print(a[i]); + } + System.out.println(); + } + for (int i = startIndex; i <= endIndex; i++) { + swap(a, i, startIndex); + permutation(a, startIndex + 1, endIndex); + swap(a, i, startIndex); + } + } + + //输出数组a的全排列 + public void permutation(int[] a) { + permutation(a, 0, a.length - 1); + } + + + //public void Combination(int[] a,int[] b,int) + + //交换数组中的两个元素 + public void swap(int[] a, int xIndex, int yIndex) { + int temp = a[xIndex]; + a[xIndex] = a[yIndex]; + a[yIndex] = temp; + } + + +} diff --git a/util/src/main/java/com/zfoo/util/math/ConsistentHash.java b/util/src/main/java/com/zfoo/util/math/ConsistentHash.java new file mode 100644 index 00000000..a3f95a26 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/ConsistentHash.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +import com.zfoo.protocol.model.Pair; + +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.TreeMap; + +/** + * 带虚拟节点的一致性Hash算法,参考:http://www.zsythink.net/archives/1182 + * + * @author jaysunxiao + * @version 3.0 + */ + +public class ConsistentHash { + + // 真实结点列表,考虑到服务器上线、下线的场景,即添加、删除的场景会比较频繁,这里使用LinkedList会更好 + private List> realNodes = new LinkedList<>(); + + // 虚拟节点,key表示虚拟节点的hash值,value表示虚拟节点的名称 + private TreeMap> virtualNodeTreeMap = new TreeMap<>(); + + // 虚拟节点的数目,数量越大约均匀,经验值150 + private int virtualNodes = 0; + + public ConsistentHash(List> realNodes, int virtualNodes) { + // 先把原始的服务器添加到真实结点列表中 + this.realNodes.addAll(realNodes); + this.virtualNodes = virtualNodes; + + // 初始化 + // 再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高 + for (var realNode : realNodes) { + addNode(realNode); + } + } + + public void addNode(Pair realNode) { + for (var i = 0; i < this.virtualNodes; i++) { + var virtualNode = realNode.getKey().toString() + "&&VN" + i; + var hash = HashUtils.fnvHash(virtualNode); + virtualNodeTreeMap.put(hash, realNode); + } + } + + + // 得到应当路由到的结点 + public Pair getRealNode(Object key) { + // 得到该key的hash值 + var hash = HashUtils.fnvHash(key); + // 第一个Key就是顺时针过去离node最近的那个结点 + var entry = virtualNodeTreeMap.ceilingEntry(hash); + if (Objects.isNull(entry)) { + // 如果没有比该key的hash值大的,则从第一个node开始 + return virtualNodeTreeMap.firstEntry().getValue(); + } + return entry.getValue(); + } + + +} diff --git a/util/src/main/java/com/zfoo/util/math/HashUtils.java b/util/src/main/java/com/zfoo/util/math/HashUtils.java new file mode 100644 index 00000000..d7986eac --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/HashUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +/** + * 改进的hash算法虽然发布比较均匀,但是没有Java自带的hash算法速度快,所以需要知道自己需要什么 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class HashUtils { + + + private static final int P = 16777619; + private static final int INIT_HASH = (int) 2166136261L; + + /** + * 改进的32位FNV算法1 + * + * @param data 数组 + * @return hash结果 + */ + public static int fnvHash(byte[] data) { + var hash = INIT_HASH; + for (byte b : data) { + hash = (hash ^ b) * P; + } + hash += hash << 13; + hash ^= hash >> 7; + hash += hash << 3; + hash ^= hash >> 17; + hash += hash << 5; + return Math.abs(hash); + } + + /** + * 改进的32位FNV算法1 + * + * @param object 计算hash的对象,会调用toString方法 + * @return hash结果 + */ + public static int fnvHash(Object object) { + var hash = object.toString().chars().reduce(INIT_HASH, (left, right) -> (left ^ right) * P); + hash += hash << 13; + hash ^= hash >> 7; + hash += hash << 3; + hash ^= hash >> 17; + hash += hash << 5; + return Math.abs(hash); + } + +} diff --git a/util/src/main/java/com/zfoo/util/math/NumberUtils.java b/util/src/main/java/com/zfoo/util/math/NumberUtils.java new file mode 100644 index 00000000..f5c1bcd8 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/NumberUtils.java @@ -0,0 +1,998 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Collection; + +/** + * 数字工具类
+ * BigDecimal(double val)构造方法的结果有一定的不可预知性,例如: + * + *
+ * new BigDecimal(0.1)
+ * 
+ *

+ * 表示的不是0.1而是0.1000000000000000055511151231257827021181583404541015625 + *

+ * 这是因为0.1无法准确的表示为double。因此应该使用new BigDecimal(String)。 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class NumberUtils { + + /** + * 默认除法运算精度 + */ + private static final int DEFAULT_DIV_SCALE = 10; + + + /** + * 提供精确的加法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被加值 + * @return 和 + */ + public static BigDecimal add(Number... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + Number value = values[0]; + BigDecimal result = new BigDecimal(null == value ? "0" : value.toString()); + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.add(new BigDecimal(value.toString())); + } + } + return result; + } + + /** + * 提供精确的加法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被加值 + * @return 求和 + */ + public static BigDecimal add(String... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + String value = values[0]; + BigDecimal result = new BigDecimal(null == value ? "0" : value); + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.add(new BigDecimal(value)); + } + } + return result; + } + + /** + * 提供精确的加法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被加值 + * @return 求和 + */ + public static BigDecimal add(BigDecimal... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + BigDecimal value = values[0]; + BigDecimal result = null == value ? BigDecimal.ZERO : value; + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.add(value); + } + } + return result; + } + + + /** + * 提供精确的减法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被减值 + * @return 差 + */ + public static BigDecimal sub(Number... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + Number value = values[0]; + BigDecimal result = new BigDecimal(null == value ? "0" : value.toString()); + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.subtract(new BigDecimal(value.toString())); + } + } + return result; + } + + /** + * 提供精确的减法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被减值 + * @return 差 + */ + public static BigDecimal sub(String... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + String value = values[0]; + BigDecimal result = new BigDecimal(null == value ? "0" : value); + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.subtract(new BigDecimal(value)); + } + } + return result; + } + + /** + * 提供精确的减法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被减值 + * @return 差 + */ + public static BigDecimal sub(BigDecimal... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + BigDecimal value = values[0]; + BigDecimal result = null == value ? BigDecimal.ZERO : value; + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.subtract(value); + } + } + return result; + } + + + /** + * 提供精确的乘法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被乘值 + * @return 积 + */ + public static BigDecimal mul(Number... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + Number value = values[0]; + BigDecimal result = new BigDecimal(null == value ? "0" : value.toString()); + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.multiply(new BigDecimal(value.toString())); + } + } + return result; + } + + /** + * 提供精确的乘法运算 + * + * @param a 被乘数 + * @param b 乘数 + * @return 积 + */ + public static BigDecimal mul(String a, String b) { + return mul(new BigDecimal(a), new BigDecimal(b)); + } + + /** + * 提供精确的乘法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被乘值 + * @return 积 + */ + public static BigDecimal mul(String... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + String value = values[0]; + BigDecimal result = new BigDecimal(null == value ? "0" : value); + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.multiply(new BigDecimal(value)); + } + } + return result; + } + + /** + * 提供精确的乘法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被乘值 + * @return 积 + */ + public static BigDecimal mul(BigDecimal... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + BigDecimal value = values[0]; + BigDecimal result = null == value ? BigDecimal.ZERO : value; + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.multiply(value); + } + } + return result; + } + + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入 + * + * @param a 被除数 + * @param b 除数 + * @return 两个参数的商 + */ + public static BigDecimal div(Number a, Number b) { + return div(a, b, DEFAULT_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况的时候,精确到小数点后10位,后面的四舍五入 + * + * @param a 被除数 + * @param b 除数 + * @return 两个参数的商 + */ + public static BigDecimal div(String a, String b) { + return div(a, b, DEFAULT_DIV_SCALE); + } + + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度,后面的四舍五入 + * + * @param a 被除数 + * @param b 除数 + * @param scale 精确度,如果为负值,取绝对值 + * @return 两个参数的商 + */ + public static BigDecimal div(Number a, Number b, int scale) { + return div(a, b, scale, RoundingMode.HALF_UP); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度,后面的四舍五入 + * + * @param a 被除数 + * @param b 除数 + * @param scale 精确度,如果为负值,取绝对值 + * @return 两个参数的商 + */ + public static BigDecimal div(String a, String b, int scale) { + return div(a, b, scale, RoundingMode.HALF_UP); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度 + * + * @param a 被除数 + * @param b 除数 + * @param scale 精确度,如果为负值,取绝对值 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 两个参数的商 + */ + public static BigDecimal div(Number a, Number b, int scale, RoundingMode roundingMode) { + return div(a.toString(), b.toString(), scale, roundingMode); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度 + * + * @param a 被除数 + * @param b 除数 + * @param scale 精确度,如果为负值,取绝对值 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 两个参数的商 + */ + public static BigDecimal div(String a, String b, int scale, RoundingMode roundingMode) { + return div(new BigDecimal(a), new BigDecimal(b), scale, roundingMode); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,由scale指定精确度 + * + * @param a 被除数 + * @param b 除数 + * @param scale 精确度,如果为负值,取绝对值 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 两个参数的商 + */ + public static BigDecimal div(BigDecimal a, BigDecimal b, int scale, RoundingMode roundingMode) { + AssertionUtils.notNull(b, "Divisor must be not null !"); + if (null == a) { + return BigDecimal.ZERO; + } + if (scale < 0) { + scale = -scale; + } + return a.divide(b, scale, roundingMode); + } + + /** + * 提供精确的乘法运算
+ * 如果传入多个值为null或者空,则返回0 + * + * @param values 多个被乘值 + * @return 积 + */ + public static BigDecimal div(BigDecimal... values) { + if (CollectionUtils.isEmpty(values)) { + return BigDecimal.ZERO; + } + + BigDecimal value = values[0]; + BigDecimal result = null == value ? BigDecimal.ZERO : value; + for (int i = 1; i < values.length; i++) { + value = values[i]; + if (null != value) { + result = result.divide(value); + } + } + return result; + } + + // ------------------------------------------------------------------------------------------- round + + /** + * 保留固定位数小数
+ * 采用四舍五入策略 {@link RoundingMode#HALF_UP}
+ * 例如保留2位小数:123.456789 =》 123.46 + * + * @param v 值 + * @param scale 保留小数位数 + * @return 新值 + */ + public static BigDecimal round(double v, int scale) { + return round(v, scale, RoundingMode.HALF_UP); + } + + + /** + * 保留固定位数小数
+ * 采用四舍五入策略 {@link RoundingMode#HALF_UP}
+ * 例如保留2位小数:123.456789 =》 123.46 + * + * @param numberStr 数字值的字符串表现形式 + * @param scale 保留小数位数 + * @return 新值 + */ + public static BigDecimal round(String numberStr, int scale) { + return round(numberStr, scale, RoundingMode.HALF_UP); + } + + /** + * 保留固定位数小数
+ * 采用四舍五入策略 {@link RoundingMode#HALF_UP}
+ * 例如保留2位小数:123.456789 =》 123.46 + * + * @param number 数字值 + * @param scale 保留小数位数 + * @return 新值 + */ + public static BigDecimal round(BigDecimal number, int scale) { + return round(number, scale, RoundingMode.HALF_UP); + } + + /** + * 保留固定位数小数
+ * 例如保留四位小数:123.456789 =》 123.4567 + * + * @param v 值 + * @param scale 保留小数位数 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 新值 + */ + public static BigDecimal round(double v, int scale, RoundingMode roundingMode) { + return round(Double.toString(v), scale, roundingMode); + } + + /** + * 保留固定位数小数
+ * 例如保留四位小数:123.456789 =》 123.4567 + * + * @param numberStr 数字值的字符串表现形式 + * @param scale 保留小数位数,如果传入小于0,则默认0 + * @param roundingMode 保留小数的模式 {@link RoundingMode},如果传入null则默认四舍五入 + * @return 新值 + */ + public static BigDecimal round(String numberStr, int scale, RoundingMode roundingMode) { + AssertionUtils.isTrue(!StringUtils.isBlank(numberStr)); + if (scale < 0) { + scale = 0; + } + return round(toBigDecimal(numberStr), scale, roundingMode); + } + + /** + * 保留固定位数小数
+ * 例如保留四位小数:123.456789 =》 123.4567 + * + * @param number 数字值 + * @param scale 保留小数位数,如果传入小于0,则默认0 + * @param roundingMode 保留小数的模式 {@link RoundingMode},如果传入null则默认四舍五入 + * @return 新值 + */ + public static BigDecimal round(BigDecimal number, int scale, RoundingMode roundingMode) { + if (null == number) { + number = BigDecimal.ZERO; + } + if (scale < 0) { + scale = 0; + } + if (null == roundingMode) { + roundingMode = RoundingMode.HALF_UP; + } + + return number.setScale(scale, roundingMode); + } + + + /** + * 四舍六入五成双计算法 + *

+ * 四舍六入五成双是一种比较精确比较科学的计数保留法,是一种数字修约规则。 + *

+ * + *
+     * 算法规则:
+     * 四舍六入五考虑,
+     * 五后非零就进一,
+     * 五后皆零看奇偶,
+     * 五前为偶应舍去,
+     * 五前为奇要进一。
+     * 
+ * + * @param number 需要科学计算的数据 + * @param scale 保留的小数位 + * @return 结果 + */ + public static BigDecimal roundHalfEven(Number number, int scale) { + return roundHalfEven(toBigDecimal(number), scale); + } + + /** + * 四舍六入五成双计算法 + *

+ * 四舍六入五成双是一种比较精确比较科学的计数保留法,是一种数字修约规则。 + *

+ * + *
+     * 算法规则:
+     * 四舍六入五考虑,
+     * 五后非零就进一,
+     * 五后皆零看奇偶,
+     * 五前为偶应舍去,
+     * 五前为奇要进一。
+     * 
+ * + * @param value 需要科学计算的数据 + * @param scale 保留的小数位 + * @return 结果 + */ + public static BigDecimal roundHalfEven(BigDecimal value, int scale) { + return round(value, scale, RoundingMode.HALF_EVEN); + } + + /** + * 保留固定小数位数,舍去多余位数 + * + * @param number 需要科学计算的数据 + * @param scale 保留的小数位 + * @return 结果 + */ + public static BigDecimal roundDown(Number number, int scale) { + return roundDown(toBigDecimal(number), scale); + } + + /** + * 保留固定小数位数,舍去多余位数 + * + * @param value 需要科学计算的数据 + * @param scale 保留的小数位 + * @return 结果 + */ + public static BigDecimal roundDown(BigDecimal value, int scale) { + return round(value, scale, RoundingMode.DOWN); + } + + // ------------------------------------------------------------------------------------------- decimalFormat + + /** + * 格式化double
+ * 对 {@link DecimalFormat} 做封装
+ * + * @param pattern 格式 格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。
+ *
    + *
  • 0 =》 取一位整数
  • + *
  • 0.00 =》 取一位整数和两位小数
  • + *
  • 00.000 =》 取两位整数和三位小数
  • + *
  • # =》 取所有整数部分
  • + *
  • #.##% =》 以百分比方式计数,并取两位小数
  • + *
  • #.#####E0 =》 显示为科学计数法,并取五位小数
  • + *
  • ,### =》 每三位以逗号进行分隔,例如:299,792,458
  • + *
  • 光速大小为每秒,###米 =》 将格式嵌入文本
  • + *
+ * @param value 值 + * @return 格式化后的值 + */ + public static String decimalFormat(String pattern, double value) { + return new DecimalFormat(pattern).format(value); + } + + /** + * 格式化double
+ * 对 {@link DecimalFormat} 做封装
+ * + * @param pattern 格式 格式中主要以 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充,# 表示只要有可能就把数字拉上这个位置。
+ *
    + *
  • 0 =》 取一位整数
  • + *
  • 0.00 =》 取一位整数和两位小数
  • + *
  • 00.000 =》 取两位整数和三位小数
  • + *
  • # =》 取所有整数部分
  • + *
  • #.##% =》 以百分比方式计数,并取两位小数
  • + *
  • #.#####E0 =》 显示为科学计数法,并取五位小数
  • + *
  • ,### =》 每三位以逗号进行分隔,例如:299,792,458
  • + *
  • 光速大小为每秒,###米 =》 将格式嵌入文本
  • + *
+ * @param value 值 + * @return 格式化后的值 + */ + public static String decimalFormat(String pattern, long value) { + return new DecimalFormat(pattern).format(value); + } + + /** + * 格式化金额输出,每三位用逗号分隔 + * + * @param value 金额 + * @return 格式化后的值 + */ + public static String decimalFormatMoney(double value) { + return decimalFormat(",##0.00", value); + } + + /** + * 格式化百分比,小数采用四舍五入方式 + * + * @param number 值 + * @param scale 保留小数位数 + * @return 百分比 + */ + public static String formatPercent(double number, int scale) { + final NumberFormat format = NumberFormat.getPercentInstance(); + format.setMaximumFractionDigits(scale); + return format.format(number); + } + + // ------------------------------------------------------------------------------------------- isXXX + + /** + * 判断String是否是整数
+ * 支持8、10、16进制 + * + * @param s String + * @return 是否为整数 + */ + public static boolean isInteger(String s) { + try { + Integer.parseInt(s); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * 判断字符串是否是Long类型
+ * 支持8、10、16进制 + * + * @param s String + * @return 是否为{@link Long}类型 + */ + public static boolean isLong(String s) { + try { + Long.parseLong(s); + } catch (NumberFormatException e) { + return false; + } + return true; + } + + /** + * 判断字符串是否是浮点数 + * + * @param s String + * @return 是否为{@link Double}类型 + */ + public static boolean isDouble(String s) { + try { + Double.parseDouble(s); + if (s.contains(".")) { + return true; + } + return false; + } catch (NumberFormatException e) { + return false; + } + } + + /** + * 判断字符串是否全部都为数字 + *
+     * StringUtils.isNumeric(null)   = false
+     * StringUtils.isNumeric("")     = false
+     * StringUtils.isNumeric("  ")   = false
+     * StringUtils.isNumeric("123")  = true
+     * StringUtils.isNumeric("\u0967\u0968\u0969")  = true
+     * StringUtils.isNumeric("12 3") = false
+     * StringUtils.isNumeric("ab2c") = false
+     * StringUtils.isNumeric("12-3") = false
+     * StringUtils.isNumeric("12.3") = false
+     * StringUtils.isNumeric("-123") = false
+     * StringUtils.isNumeric("+123") = false
+     * 
+ * + * @param cs the CharSequence to check, may be null + * @return {@code true} if only contains digits, and is non-null + */ + public static boolean isNumeric(final CharSequence cs) { + if (StringUtils.isEmpty(cs)) { + return false; + } + return cs.chars().allMatch(it -> Character.isDigit(it)); + } + + /** + * 是否是质数(素数)
+ * 质数表的质数又称素数。指整数在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。 + * + * @param n 数字 + * @return 是否是质数 + */ + public static boolean isPrimes(int n) { + AssertionUtils.isTrue(n > 1, "The number must be > 1"); + for (int i = 2; i <= Math.sqrt(n); i++) { + if (n % i == 0) { + return false; + } + } + return true; + } + + + // ------------------------------------------------------------------------------------------- range + + /** + * 从0开始给定范围内的整数列表,步进为1 + * + * @param stop 结束(包含) + * @return 整数列表 + */ + public static int[] range(int stop) { + return range(0, stop); + } + + /** + * 给定范围内的整数列表,步进为1 + * + * @param start 开始(包含) + * @param stop 结束(包含) + * @return 整数列表 + */ + public static int[] range(int start, int stop) { + return range(start, stop, 1); + } + + /** + * 给定范围内的整数列表 + * + * @param start 开始(包含) + * @param stop 结束(包含) + * @param step 步进 + * @return 整数列表 + */ + public static int[] range(int start, int stop, int step) { + if (start < stop) { + step = Math.abs(step); + } else if (start > stop) { + step = -Math.abs(step); + } else {// start == end + return new int[]{start}; + } + + int size = Math.abs((stop - start) / step) + 1; + int[] values = new int[size]; + int index = 0; + for (int i = start; (step > 0) ? i <= stop : i >= stop; i += step) { + values[index] = i; + index++; + } + return values; + } + + /** + * 将给定范围内的整数添加到已有集合中,步进为1 + * + * @param start 开始(包含) + * @param stop 结束(包含) + * @param values 集合 + * @return 集合 + */ + public static Collection appendRange(int start, int stop, Collection values) { + return appendRange(start, stop, 1, values); + } + + /** + * 将给定范围内的整数添加到已有集合中 + * + * @param start 开始(包含) + * @param stop 结束(包含) + * @param step 步进 + * @param values 集合 + * @return 集合 + */ + public static Collection appendRange(int start, int stop, int step, Collection values) { + if (start < stop) { + step = Math.abs(step); + } else if (start > stop) { + step = -Math.abs(step); + } else {// start == end + values.add(start); + return values; + } + + for (int i = start; (step > 0) ? i <= stop : i >= stop; i += step) { + values.add(i); + } + return values; + } + + // ------------------------------------------------------------------------------------------- others + + /** + * 计算阶乘 + *

+ * n! = n * (n-1) * ... * end + *

+ * + * @param start 阶乘起始 + * @param end 阶乘结束 + * @return 结果 + */ + public static long factorial(long start, long end) { + if (start < end) { + return 0L; + } + if (start == end) { + return 1L; + } + return start * factorial(start - 1, end); + } + + /** + * 计算阶乘 + *

+ * n! = n * (n-1) * ... * 2 * 1 + *

+ * + * @param n 阶乘起始 + * @return 结果 + */ + public static long factorial(long n) { + return factorial(n, 1); + } + + /** + * 平方根算法
+ * 推荐使用 {@link Math#sqrt(double)} + * + * @param x 值 + * @return 平方根 + */ + public static long sqrt(long x) { + long y = 0; + long b = (~Long.MAX_VALUE) >>> 1; + while (b > 0) { + if (x >= y + b) { + x -= y + b; + y >>= 1; + y += b; + } else { + y >>= 1; + } + b >>= 2; + } + return y; + } + + /** + * 最大公约数 + * + * @param m 第一个值 + * @param n 第二个值 + * @return 最大公约数 + */ + public static int divisor(int m, int n) { + while (m % n != 0) { + int temp = m % n; + m = n; + n = temp; + } + return n; + } + + /** + * 最小公倍数 + * + * @param m 第一个值 + * @param n 第二个值 + * @return 最小公倍数 + */ + public static int multiple(int m, int n) { + return m * n / divisor(m, n); + } + + /** + * 获得数字对应的二进制字符串 + * + * @param number 数字 + * @return 二进制字符串 + */ + public static String getBinaryStr(Number number) { + if (number instanceof Long) { + return Long.toBinaryString((Long) number); + } else if (number instanceof Integer) { + return Integer.toBinaryString((Integer) number); + } else { + return Long.toBinaryString(number.longValue()); + } + } + + /** + * 二进制转int + * + * @param binaryStr 二进制字符串 + * @return int + */ + public static int binaryToInt(String binaryStr) { + return Integer.parseInt(binaryStr, 2); + } + + /** + * 二进制转long + * + * @param binaryStr 二进制字符串 + * @return long + */ + public static long binaryToLong(String binaryStr) { + return Long.parseLong(binaryStr, 2); + } + + // ------------------------------------------------------------------------------------------- compare + + + /** + * 数字转{@link BigDecimal} + * + * @param number 数字 + * @return {@link BigDecimal} + */ + public static BigDecimal toBigDecimal(Number number) { + if (null == number) { + return BigDecimal.ZERO; + } + return toBigDecimal(number.toString()); + } + + /** + * 数字转{@link BigDecimal} + * + * @param number 数字 + * @return {@link BigDecimal} + */ + public static BigDecimal toBigDecimal(String number) { + return (null == number) ? BigDecimal.ZERO : new BigDecimal(number); + } + + + /** + * 判断两个数字是否相邻,例如1和2相邻,1和3不相邻
+ * 判断方法为做差取绝对值判断是否为1 + * + * @param number1 数字1 + * @param number2 数字2 + * @return 是否相邻 + */ + public static boolean isBeside(long number1, long number2) { + return Math.abs(number1 - number2) == 1; + } + + /** + * 判断两个数字是否相邻,例如1和2相邻,1和3不相邻
+ * 判断方法为做差取绝对值判断是否为1 + * + * @param number1 数字1 + * @param number2 数字2 + * @return 是否相邻 + */ + public static boolean isBeside(int number1, int number2) { + return Math.abs(number1 - number2) == 1; + } + + + /** + * 提供精确的幂运算 + * + * @param number 底数 + * @param n 指数 + * @return 幂的积 + */ + public static BigDecimal pow(Number number, int n) { + return pow(toBigDecimal(number), n); + } + + /** + * 提供精确的幂运算 + * + * @param number 底数 + * @param n 指数 + * @return 幂的积 + */ + public static BigDecimal pow(BigDecimal number, int n) { + return number.pow(n); + } + + +} diff --git a/util/src/main/java/com/zfoo/util/math/RandomSelector.java b/util/src/main/java/com/zfoo/util/math/RandomSelector.java new file mode 100644 index 00000000..62fdf9b6 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/RandomSelector.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +import org.springframework.lang.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class RandomSelector { + + private int cursor = 0; + + private TreeMap elementMap = new TreeMap<>(); + + public void addElement(@Nullable T value, int weight) { + if (value == null || weight <= 0) { + return; + } + + cursor += weight; + elementMap.put(cursor, value); + } + + public void clear() { + elementMap.clear(); + cursor = 0; + } + + public int size() { + return elementMap.size(); + } + + public T select() { + if (cursor <= 0) { + throw new IllegalStateException("全部的权重是0"); + } + if (elementMap.isEmpty()) { + throw new IllegalStateException("选择的元素为空"); + } + + var randomInt = RandomUtils.randomInt(cursor) + 1; + return elementMap.ceilingEntry(randomInt).getValue(); + } + + public List select(int count) { + List resultList = new ArrayList<>(); + for (int i = 0; i < count; i++) { + resultList.add(select()); + } + return resultList; + } + +} diff --git a/util/src/main/java/com/zfoo/util/math/RandomUtils.java b/util/src/main/java/com/zfoo/util/math/RandomUtils.java new file mode 100644 index 00000000..781a453c --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/RandomUtils.java @@ -0,0 +1,421 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +import com.zfoo.protocol.util.StringUtils; + +import java.awt.*; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.List; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class RandomUtils { + + + /** + * 用于随机选的字符和数字 + */ + public static final String BASE_CHAR_NUMBER = StringUtils.ENGLISH_CHAR + StringUtils.ARAB_NUMBER; + + /** + * 使用ThreadLocalRandom产生随机数,能够解决多个线程发生的竞争争夺。 + */ + public static ThreadLocalRandom getRandom() { + return ThreadLocalRandom.current(); + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + */ + public static SecureRandom getSecureRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取随机数产生器 + * + * @param isSecure 是否为强随机数生成器 (RNG) + * @return {@link Random} + */ + public static Random getRandom(boolean isSecure) { + return isSecure ? getSecureRandom() : getRandom(); + } + + /** + * 获得随机Boolean值 + * + * @return true or false + */ + public static boolean randomBoolean() { + return 0 == randomInt(2); + } + + /** + * 获得指定范围内的随机数 + * + * @param min 最小数(包含) + * @param max 最大数(不包含) + * @return 随机数 + */ + public static int randomInt(int min, int max) { + return getRandom().nextInt(min, max); + } + + /** + * 获得随机数[-2^32, 2^32) + * + * @return 随机数 + */ + public static int randomInt() { + return getRandom().nextInt(); + } + + /** + * 获得指定范围内的随机数 [0,limit) + * + * @param limit 限制随机数的范围,不包括这个数 + * @return 随机数 + */ + public static int randomInt(int limit) { + return getRandom().nextInt(limit); + } + + /** + * 获得指定范围内的随机数[min, max) + * + * @param min 最小数(包含) + * @param max 最大数(不包含) + * @return 随机数 + */ + public static long randomLong(long min, long max) { + return getRandom().nextLong(min, max); + } + + /** + * 获得随机数 + * + * @return 随机数 + */ + public static long randomLong() { + return getRandom().nextLong(); + } + + /** + * 获得指定范围内的随机数 [0,limit) + * + * @param limit 限制随机数的范围,不包括这个数 + * @return 随机数 + */ + public static long randomLong(long limit) { + return getRandom().nextLong(limit); + } + + /** + * 获得指定范围内的随机数 + * + * @param min 最小数(包含) + * @param max 最大数(不包含) + * @return 随机数 + */ + public static double randomDouble(double min, double max) { + return getRandom().nextDouble(min, max); + } + + /** + * 获得指定范围内的随机数 + * + * @param min 最小数(包含) + * @param max 最大数(不包含) + * @param scale 保留小数位数 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 随机数 + */ + public static double randomDouble(double min, double max, int scale, RoundingMode roundingMode) { + return NumberUtils.round(randomDouble(min, max), scale, roundingMode).doubleValue(); + } + + /** + * 获得随机数[0, 1) + * + * @return 随机数 + */ + public static double randomDouble() { + return getRandom().nextDouble(); + } + + /** + * 获得指定范围内的随机数 + * + * @param scale 保留小数位数 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 随机数 + */ + public static double randomDouble(int scale, RoundingMode roundingMode) { + return NumberUtils.round(randomDouble(), scale, roundingMode).doubleValue(); + } + + /** + * 获得指定范围内的随机数 [0,limit) + * + * @param limit 限制随机数的范围,不包括这个数 + * @return 随机数 + */ + public static double randomDouble(double limit) { + return getRandom().nextDouble(limit); + } + + /** + * 获得指定范围内的随机数 + * + * @param limit 限制随机数的范围,不包括这个数 + * @param scale 保留小数位数 + * @param roundingMode 保留小数的模式 {@link RoundingMode} + * @return 随机数 + */ + public static double randomDouble(double limit, int scale, RoundingMode roundingMode) { + return NumberUtils.round(randomDouble(limit), scale, roundingMode).doubleValue(); + } + + /** + * 获得指定范围内的随机数[0, 1) + * + * @return 随机数 + */ + public static BigDecimal randomBigDecimal() { + return NumberUtils.toBigDecimal(getRandom().nextDouble()); + } + + /** + * 获得指定范围内的随机数 [0,limit) + * + * @param limit 最大数(不包含) + * @return 随机数 + */ + public static BigDecimal randomBigDecimal(BigDecimal limit) { + return NumberUtils.toBigDecimal(getRandom().nextDouble(limit.doubleValue())); + } + + /** + * 获得指定范围内的随机数 + * + * @param min 最小数(包含) + * @param max 最大数(不包含) + * @return 随机数 + */ + public static BigDecimal randomBigDecimal(BigDecimal min, BigDecimal max) { + return NumberUtils.toBigDecimal(getRandom().nextDouble(min.doubleValue(), max.doubleValue())); + } + + /** + * 随机bytes + * + * @param length 长度 + * @return bytes + */ + public static byte[] randomBytes(int length) { + byte[] bytes = new byte[length]; + getRandom().nextBytes(bytes); + return bytes; + } + + /** + * 随机获得列表中的元素 + * + * @param 元素类型 + * @param list 列表 + * @return 随机元素 + */ + public static T randomEle(List list) { + return randomEle(list, list.size()); + } + + /** + * 随机获得列表中的元素 + * + * @param 元素类型 + * @param list 列表 + * @param limit 限制列表的前N项 + * @return 随机元素 + */ + public static T randomEle(List list, int limit) { + return list.get(randomInt(limit)); + } + + /** + * 随机获得数组中的元素 + * + * @param 元素类型 + * @param array 列表 + * @return 随机元素 + */ + public static T randomEle(T[] array) { + return randomEle(array, array.length); + } + + /** + * 随机获得数组中的元素 + * + * @param 元素类型 + * @param array 列表 + * @param limit 限制列表的前N项 + * @return 随机元素 + */ + public static T randomEle(T[] array, int limit) { + return array[randomInt(limit)]; + } + + /** + * 随机获得列表中的一定量元素 + * + * @param 元素类型 + * @param list 列表 + * @param count 随机取出的个数 + * @return 随机元素 + */ + public static List randomEles(List list, int count) { + final List result = new ArrayList(count); + int limit = list.size(); + while (result.size() < count) { + result.add(randomEle(list, limit)); + } + + return result; + } + + /** + * 随机获得列表中的一定量的不重复元素,返回Set + * + * @param 元素类型 + * @param collection 列表 + * @param count 随机取出的个数 + * @return 随机元素 + * @throws IllegalArgumentException 需要的长度大于给定集合非重复总数 + */ + public static Set randomEleSet(Collection collection, int count) { + ArrayList source = new ArrayList<>(new HashSet<>(collection)); + if (count > source.size()) { + throw new IllegalArgumentException("Count is larger than collection distinct size !"); + } + + final HashSet result = new HashSet(count); + int limit = collection.size(); + while (result.size() < count) { + result.add(randomEle(source, limit)); + } + + return result; + } + + /** + * 获得一个随机的字符串(只包含数字和字符) + * + * @param length 字符串的长度 + * @return 随机字符串 + */ + public static String randomString(int length) { + return randomString(BASE_CHAR_NUMBER, length); + } + + /** + * 获得一个随机的字符串(只包含数字和大写字符) + * + * @param length 字符串的长度 + * @return 随机字符串 + */ + public static String randomStringUpper(int length) { + return randomString(BASE_CHAR_NUMBER, length).toUpperCase(); + } + + /** + * 获得一个只包含数字的字符串 + * + * @param length 字符串的长度 + * @return 随机字符串 + */ + public static String randomNumbers(int length) { + return randomString(StringUtils.ARAB_NUMBER, length); + } + + /** + * 获得一个随机的字符串 + * + * @param baseString 随机字符选取的样本 + * @param length 字符串的长度 + * @return 随机字符串 + */ + public static String randomString(String baseString, int length) { + final StringBuilder sb = new StringBuilder(); + + if (length < 1) { + length = 1; + } + int baseLength = baseString.length(); + for (int i = 0; i < length; i++) { + int number = getRandom().nextInt(baseLength); + sb.append(baseString.charAt(number)); + } + return sb.toString(); + } + + /** + * 随机数字,数字为0~9单个数字 + * + * @return 随机数字字符 + */ + public static int randomNumber() { + return randomChar(StringUtils.ARAB_NUMBER); + } + + /** + * 随机字母或数字,小写 + * + * @return 随机字符 + */ + public static char randomChar() { + return randomChar(BASE_CHAR_NUMBER); + } + + /** + * 随机字符 + * + * @param baseString 随机字符选取的样本 + * @return 随机字符 + */ + public static char randomChar(String baseString) { + return baseString.charAt(getRandom().nextInt(baseString.length())); + } + + /** + * 生成随机颜色 + * + * @return 随机颜色 + */ + public static Color randomColor() { + final Random random = getRandom(); + return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); + } + +} diff --git a/util/src/main/java/com/zfoo/util/math/dfa/WordTree.java b/util/src/main/java/com/zfoo/util/math/dfa/WordTree.java new file mode 100644 index 00000000..874532b9 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/dfa/WordTree.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math.dfa; + + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.util.*; + +/** + * DFA(Deterministic Finite Automaton 确定有穷自动机) + * DFA单词树(以下简称单词树),常用于在某大段文字中快速查找某几个关键词是否存在。
+ * 单词树使用group区分不同的关键字集合,不同的分组可以共享树枝,避免重复建树。
+ * 单词树使用树状结构表示一组单词。
+ * 例如:红领巾,红河构建树后为:
+ *
红 + *
/ \ + *
领 河 + *
/ + *
巾 + * 其中每个节点都是一个WordTree对象,查找时从上向下查找。
+ */ +public class WordTree extends HashMap { + + + /** + * 敏感词字符末尾标识,用于标识单词末尾字符 + */ + private Set endCharacterSet = new HashSet<>(); + + + /** + * 默认构造 + */ + public WordTree() { + } + + + /** + * 增加一组单词 + * + * @param words 单词集合 + */ + public void addWords(Collection words) { + var wordSet = new HashSet<>(words); + for (var word : wordSet) { + addWord(word); + } + } + + /** + * 添加单词,使用默认类型 + * + * @param word 单词 + */ + public void addWord(String word) { + WordTree parent = null; + WordTree current = this; + WordTree child; + char currentChar = 0; + int length = word.length(); + for (var i = 0; i < length; i++) { + currentChar = word.charAt(i); + // 只处理合法字符 + if (!StringUtils.isStopChar(currentChar)) { + child = current.get(currentChar); + if (child == null) { + // 无子类,新建一个子节点后存放下一个字符 + child = new WordTree(); + current.put(currentChar, child); + } + parent = current; + current = child; + } + } + if (null != parent && parent != this) { + parent.setEnd(currentChar); + } + } + + //------------------------------------------------------------------------------- match + + /** + * 指定文本是否包含树中的词 + * + * @param text 被检查的文本 + * @return 是否包含 + */ + public boolean isMatch(String text) { + if (StringUtils.isBlank(text)) { + return false; + } + var matchAll = matchAll(text, 1); + return CollectionUtils.isNotEmpty(matchAll); + } + + + //------------------------------------------------------------------------------- match all + + /** + * 找出所有匹配的关键字 + * + * @param text 被检查的文本 + * @return 匹配的词列表 + */ + public List matchAll(String text) { + return matchAll(text, -1); + } + + /** + * 找出所有匹配的关键字 + * + * @param text 被检查的文本 + * @param limit 限制匹配个数 + * @return 匹配的词列表 + */ + public List matchAll(String text, int limit) { + return matchAll(text, limit, false, false); + } + + /** + * 找出所有匹配的关键字
+ * 密集匹配原则:假如关键词有 ab,b,文本是abab,将匹配 [ab,b,ab]
+ * 贪婪匹配(最长匹配)原则:假如关键字a,ab,最长匹配将匹配[a, ab] + * + * @param text 被检查的文本 + * @param limit 限制匹配个数 + * @param isDensityMatch 是否使用密集匹配原则 + * @param isGreedMatch 是否使用贪婪匹配(最长匹配)原则 + * @return 匹配的词列表 + */ + public List matchAll(String text, int limit, boolean isDensityMatch, boolean isGreedMatch) { + if (StringUtils.isBlank(text)) { + return Collections.emptyList(); + } + + var findWords = new ArrayList(); + WordTree current = this; + int length = text.length(); + // 存放查找到的字符缓存。完整出现一个词时加到findedWords中,否则清空 + StringBuilder wordBuffer; + char currentChar; + for (int i = 0; i < length; i++) { + wordBuffer = new StringBuilder(); + for (int j = i; j < length; j++) { + currentChar = text.charAt(j); + if (StringUtils.isStopChar(currentChar)) { + if (wordBuffer.length() > 0) { + // 做为关键词中间的停顿词被当作关键词的一部分被返回 + wordBuffer.append(currentChar); + } else { + // 停顿词做为关键词的第一个字符时需要跳过 + i++; + } + continue; + } else if (!current.containsKey(currentChar)) { + // 非关键字符被整体略过,重新以下个字符开始检查 + break; + } + wordBuffer.append(currentChar); + if (current.isEnd(currentChar)) { + // 到达单词末尾,关键词成立,从此词的下一个位置开始查找 + findWords.add(wordBuffer.toString()); + if (limit > 0 && findWords.size() >= limit) { + // 超过匹配限制个数,直接返回 + return findWords; + } + if (!isDensityMatch) { + // 如果非密度匹配,跳过匹配到的词 + i = j; + } + if (!isGreedMatch) { + // 如果懒惰匹配(非贪婪匹配)。当遇到第一个结尾标记就结束本轮匹配 + break; + } + } + current = current.get(currentChar); + if (null == current) { + break; + } + } + current = this; + } + return findWords; + } + + + /** + * 是否末尾 + * + * @param c 检查的字符 + * @return 是否末尾 + */ + private boolean isEnd(Character c) { + return this.endCharacterSet.contains(c); + } + + /** + * 设置是否到达末尾 + * + * @param c 设置结尾的字符 + */ + private void setEnd(Character c) { + if (null != c) { + this.endCharacterSet.add(c); + } + } +} diff --git a/util/src/main/java/com/zfoo/util/math/lexer/LexicalAnalysis.java b/util/src/main/java/com/zfoo/util/math/lexer/LexicalAnalysis.java new file mode 100644 index 00000000..b31e4b1e --- /dev/null +++ b/util/src/main/java/com/zfoo/util/math/lexer/LexicalAnalysis.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math.lexer; + +import com.zfoo.protocol.util.StringUtils; + +import java.util.Set; + +/** + * 词法分析器 + * + * @author jaysunxiao + * @version 3.0 + */ +public class LexicalAnalysis { + private static final Set KEY_WORDS = Set.of("break", "include", "begin", "end", "if", "else", "while", "switch"); + + + //判断是否是关键字 + private boolean isKey(String str) { + return KEY_WORDS.contains(str); + } + + //判断是否是字母 + boolean isLetter(char letter) { + return StringUtils.isEnglishChar(letter); + } + + //判断是否是数字 + boolean isDigit(char digit) { + return Character.isDigit(digit); + } + + //词法分析 + void analyze(char[] chars) { + for (var i = 0; i < chars.length; i++) { + var ch = chars[i]; + if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') { + continue; + } + var arr = ""; + + if (isLetter(ch)) { + while (isLetter(ch) || isDigit(ch)) { + arr += ch; + ch = chars[++i]; + } + //回退一个字符 + i--; + if (isKey(arr)) { + //关键字 + System.out.println(arr + "\t4" + "\t关键字"); + } else { + //标识符 + System.out.println(arr + "\t4" + "\t标识符"); + } + } else if (isDigit(ch) || (ch == '.')) { + while (isDigit(ch) || (ch == '.' && isDigit(chars[++i]))) { + if (ch == '.') { + i--; + } + arr = arr + ch; + ch = chars[++i]; + } + //属于无符号常数 + System.out.println(arr + "\t5" + "\t常数"); + } else { + switch (ch) { + //运算符 + case '+': + System.out.println(ch + "\t2" + "\t运算符"); + break; + case '-': + System.out.println(ch + "\t2" + "\t运算符"); + break; + case '*': + System.out.println(ch + "\t2" + "\t运算符"); + break; + case '/': + System.out.println(ch + "\t2" + "\t运算符"); + break; + //分界符 + case '(': + System.out.println(ch + "\t3" + "\t分界符"); + break; + case ')': + System.out.println(ch + "\t3" + "\t分界符"); + break; + case '[': + System.out.println(ch + "\t3" + "\t分界符"); + break; + case ']': + System.out.println(ch + "\t3" + "\t分界符"); + break; + case ';': + System.out.println(ch + "\t3" + "\t分界符"); + break; + case '{': + System.out.println(ch + "\t3" + "\t分界符"); + break; + case '}': + System.out.println(ch + "\t3" + "\t分界符"); + break; + // 字符串 + case '"': + do { + arr += ch; + ch = chars[++i]; + } while (ch != '"'); + arr += ch; + + System.out.println(arr + "\t3" + "\t字符串"); + break; + //运算符 + case '=': { + ch = chars[++i]; + if (ch == '=') { + System.out.println("==" + "\t2" + "\t运算符"); + } else { + System.out.println("=" + "\t2" + "\t运算符"); + i--; + } + } + break; + case ':': { + ch = chars[++i]; + if (ch == '=') { + System.out.println(":=" + "\t2" + "\t运算符"); + } else { + System.out.println(":" + "\t2" + "\t运算符"); + i--; + } + } + break; + case '>': { + ch = chars[++i]; + if (ch == '=') { + System.out.println(">=" + "\t2" + "\t运算符"); + } else { + System.out.println(">" + "\t2" + "\t运算符"); + i--; + } + } + break; + case '<': { + ch = chars[++i]; + if (ch == '=') { + System.out.println("<=" + "\t2" + "\t运算符"); + } else { + System.out.println("<" + "\t2" + "\t运算符"); + i--; + } + } + break; + //无识别 + default: + System.out.println(ch + "\t6" + "\t无识别符"); + } + } + } + } +} diff --git a/util/src/main/java/com/zfoo/util/net/HostAndPort.java b/util/src/main/java/com/zfoo/util/net/HostAndPort.java new file mode 100644 index 00000000..de058ff7 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/net/HostAndPort.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.net; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.util.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class HostAndPort { + + private String host; + private int port; + + public static HostAndPort valueOf(String host, int port) { + HostAndPort hostAndPort = new HostAndPort(); + hostAndPort.host = host; + hostAndPort.port = port; + return hostAndPort; + } + + /** + * @param hostAndPort example -> localhost:port + */ + public static HostAndPort valueOf(String hostAndPort) { + var split = hostAndPort.trim().split(StringUtils.COLON_REGEX); + return valueOf(split[0].trim(), Integer.parseInt(split[1].trim())); + } + + /** + * @param hostAndPorts example -> localhost:port,localhost:port,localhost:port + */ + public static List toHostAndPortList(String hostAndPorts) { + if (StringUtils.isEmpty(hostAndPorts)) { + return Collections.emptyList(); + } + + var hostAndPortSplits = hostAndPorts.split(StringUtils.COMMA_REGEX); + var hostAndPortList = new ArrayList(); + for (var hostAndPort : hostAndPortSplits) { + hostAndPortList.add(valueOf(hostAndPort)); + } + return hostAndPortList; + } + + public static List toHostAndPortList(Collection list) { + if (CollectionUtils.isEmpty(list)) { + return Collections.emptyList(); + } + var hostAndPortList = new ArrayList(); + list.forEach(it -> hostAndPortList.addAll(toHostAndPortList(it))); + return hostAndPortList; + } + + public static String toHostAndPortListStr(Collection list) { + var urlList = list.stream() + .map(it -> it.toHostAndPortStr()) + .collect(Collectors.toList()); + return StringUtils.joinWith(StringUtils.COMMA, urlList.toArray()); + } + + + public String toHostAndPortStr() { + return StringUtils.format("{}:{}", this.host.trim(), this.port); + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + HostAndPort that = (HostAndPort) o; + return port == that.port && Objects.equals(host, that.host); + } + + @Override + public int hashCode() { + return Objects.hash(host, port); + } + + @Override + public String toString() { + return StringUtils.format("[{}]", toHostAndPortStr()); + } +} diff --git a/util/src/main/java/com/zfoo/util/net/NetUtils.java b/util/src/main/java/com/zfoo/util/net/NetUtils.java new file mode 100644 index 00000000..4dd9da09 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/net/NetUtils.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.net; + +import com.zfoo.protocol.util.AssertionUtils; +import com.zfoo.protocol.util.IOUtils; +import com.zfoo.protocol.util.StringUtils; +import com.zfoo.util.EnumUtils; +import org.springframework.lang.Nullable; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.*; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * 网络相关工具 + * + * @author jaysunxiao + * @version 3.0 + */ +public class NetUtils { + + + public final static String LOCAL_LOOPBACK_IP = "127.0.0.1"; + + /** + * 默认最小端口,1024 + */ + public static final int PORT_RANGE_MIN = 1024; + /** + * 默认最大端口,65535 + */ + public static final int PORT_RANGE_MAX = 0xFFFF; + + /** + * IP v4 + */ + public final static Pattern IPV4 = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"); + /** + * IP v6 + */ + public final static Pattern IPV6 = Pattern.compile("(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"); + + /** + * 根据long值获取ip v4地址 + * + * @param longIP IP的long表示形式 + * @return IP V4 地址 + */ + public static String longToIpv4(long longIP) { + final StringBuilder sb = new StringBuilder(); + // 直接右移24位 + sb.append((longIP / 1_000_000_000)); + sb.append("."); + // 将高8位置0,然后右移16位 + sb.append(longIP / 1_000_000 % 1_000); + sb.append("."); + sb.append(longIP / 1_000 % 1_000); + sb.append("."); + sb.append(longIP % 1_000); + return sb.toString(); + } + + /** + * 根据ip地址计算出long型的数据 + * + * @param strIP IP V4 地址 + * @return long值 + */ + public static long ipv4ToLong(String strIP) { + if (isValidAddress(strIP)) { + long[] ip = new long[4]; + // 先找到IP地址字符串中.的位置 + int position1 = strIP.indexOf("."); + int position2 = strIP.indexOf(".", position1 + 1); + int position3 = strIP.indexOf(".", position2 + 1); + // 将每个.之间的字符串转换成整型 + ip[0] = Long.parseLong(strIP.substring(0, position1)); + ip[1] = Long.parseLong(strIP.substring(position1 + 1, position2)); + ip[2] = Long.parseLong(strIP.substring(position2 + 1, position3)); + ip[3] = Long.parseLong(strIP.substring(position3 + 1)); + return (ip[0] * 1_000_000_000) + (ip[1] * 1_000_000) + (ip[2] * 1_000) + ip[3]; + } + return 0; + } + + public static boolean isValidAddress(String address) { + return IPV4.matcher(address).matches() || IPV6.matcher(address).matches(); + } + + /** + * 检测本地端口可用性
+ * 来自org.springframework.util.SocketUtils + * + * @param port 被检测的端口 + * @return 是否可用 + */ + public static boolean isAvailablePort(int port) { + if (!isValidPort(port)) { + // 给定的IP未在指定端口范围中 + return false; + } + try (ServerSocket ss = new ServerSocket(port)) { + return true; + } catch (IOException e) { + return false; + } + } + + /** + * 是否为有效的端口
+ * 此方法并不检查端口是否被占用 + * + * @param port 端口号 + * @return 是否有效 + */ + public static boolean isValidPort(int port) { + // 有效端口是0~65535 + return port >= 0 && port <= PORT_RANGE_MAX; + } + + /** + * 查找1024~65535范围内的可用端口
+ * 此方法只检测给定范围内的随机一个端口,检测65535-1024次
+ * + * @return 可用的端口 + */ + public static int getAvailablePort() { + return getAvailablePort(PORT_RANGE_MIN); + } + + /** + * 查找指定范围内的可用端口,最大值为65535
+ * 此方法只检测给定范围内的随机一个端口,检测65535-minPort次
+ * + * @param minPort 端口最小值(包含) + * @return 可用的端口 + */ + public static int getAvailablePort(int minPort) { + return getAvailablePort(minPort, PORT_RANGE_MAX); + } + + /** + * 查找指定范围内的可用端口
+ * 此方法只检测给定范围内的随机一个端口,检测maxPort-minPort次
+ * + * @param minPort 端口最小值(包含) + * @param maxPort 端口最大值(包含) + * @return 可用的端口 + */ + public static int getAvailablePort(int minPort, int maxPort) { + for (int i = minPort; i <= maxPort; i++) { + if (isAvailablePort(i)) { + return i; + } + } + + throw new IllegalArgumentException(StringUtils + .format("Could not find an available port in the range [{}, {}] after {} attempts", minPort, maxPort, maxPort - minPort)); + } + + /** + * 获取多个本地可用端口
+ * + * @param minPort 端口最小值(包含) + * @param maxPort 端口最大值(包含) + * @return 可用的端口 + */ + public static TreeSet getAvailablePorts(int numRequested, int minPort, int maxPort) { + final TreeSet availablePorts = new TreeSet<>(); + int attemptCount = 0; + while ((++attemptCount <= numRequested + 100) && availablePorts.size() < numRequested) { + availablePorts.add(getAvailablePort(minPort, maxPort)); + } + + if (availablePorts.size() != numRequested) { + throw new IllegalArgumentException(StringUtils + .format("Could not find {} available ports in the range [{}, {}]", numRequested, minPort, maxPort)); + } + + return availablePorts; + } + + /** + * 判定是否为内网IP
+ * 私有IP:A类 10.0.0.0-10.255.255.255 B类 172.16.0.0-172.31.255.255 C类 192.168.0.0-192.168.255.255 当然,还有127这个网段是环回地址 + * + * @param ipAddress IP地址 + * @return 是否为内网IP + */ + public static boolean isInnerIP(String ipAddress) { + long ipNum = NetUtils.ipv4ToLong(ipAddress); + + long aBegin = NetUtils.ipv4ToLong("10.0.0.0"); + long aEnd = NetUtils.ipv4ToLong("10.255.255.255"); + + long bBegin = NetUtils.ipv4ToLong("172.16.0.0"); + long bEnd = NetUtils.ipv4ToLong("172.31.255.255"); + + long cBegin = NetUtils.ipv4ToLong("192.168.0.0"); + long cEnd = NetUtils.ipv4ToLong("192.168.255.255"); + + boolean isInnerIp = isInner(ipNum, aBegin, aEnd) || isInner(ipNum, bBegin, bEnd) || isInner(ipNum, cBegin, cEnd) || ipAddress.equals(LOCAL_LOOPBACK_IP); + return isInnerIp; + } + + /** + * 指定IP的long是否在指定范围内 + * + * @param userIp 用户IP + * @param begin 开始IP + * @param end 结束IP + * @return 是否在范围内 + */ + private static boolean isInner(long userIp, long begin, long end) { + return (userIp >= begin) && (userIp <= end); + } + + + /** + * 通过域名得到IP + * + * @param hostName HOST + * @return ip address or hostName if UnknownHostException + */ + public static String getIpByHost(String hostName) { + try { + return InetAddress.getByName(hostName).getHostAddress(); + } catch (UnknownHostException e) { + return hostName; + } + } + + /** + * 获取本机所有网卡 + */ + public static Set getAllNetworkInterface() { + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + return EnumUtils.enumerationToSet(networkInterfaces); + } catch (SocketException e) { + return Collections.emptySet(); + } + } + + /** + * 获取所有满足过滤条件的本地IP地址对象 + * + * @return 过滤后的地址对象列表 + */ + public static Set getAllAddress() { + var networkInterfaces = getAllNetworkInterface(); + var ipSet = new LinkedHashSet(); + networkInterfaces.forEach(it -> { + final Enumeration inetAddresses = it.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + final InetAddress inetAddress = inetAddresses.nextElement(); + if (Objects.nonNull(inetAddress)) { + ipSet.add(inetAddress); + } + } + }); + return ipSet; + } + + /** + * 获得本机的IPv4地址列表
+ * 返回的IP列表有序,按照系统设备顺序 + */ + public static Set localIpv4s() { + var localAddressList = getAllAddress().stream() + .filter(it -> it instanceof Inet4Address).collect(Collectors.toSet()); + return toIpList(localAddressList); + } + + /** + * 获得本机的IPv6地址列表
+ * 返回的IP列表有序,按照系统设备顺序 + */ + public static Set localIpv6s() { + var localAddressList = getAllAddress().stream() + .filter(it -> it instanceof Inet6Address).collect(Collectors.toSet()); + return toIpList(localAddressList); + } + + /** + * 地址列表转换为IP地址列表 + */ + public static Set toIpList(Set addressList) { + var ipSet = new LinkedHashSet(); + for (InetAddress address : addressList) { + ipSet.add(address.getHostAddress()); + } + return ipSet; + } + + /** + * 获得本机的IP地址列表(包括Ipv4和Ipv6)
+ */ + public static Set localIps() { + var localAddressList = getAllAddress(); + return toIpList(localAddressList); + } + + + /** + * 获取本机网卡IP地址,这个地址为所有网卡中非回路地址的第一个
+ * 如果获取失败调用 {@link InetAddress#getLocalHost()}方法获取。
+ * 此方法不会抛出异常,获取失败将返回null
+ *

+ * 参考:http://stackoverflow.com/questions/9481865/getting-the-ip-address-of-the-current-machine-using-java + * + * @return 本机网卡IP地址,获取失败返回空字符串 + */ + public static String getLocalhostStr() { + InetAddress localhost = getLocalhost(); + if (null != localhost) { + return localhost.getHostAddress(); + } + return StringUtils.EMPTY; + } + + /** + * 获取本机网卡IP地址,规则如下: + * + *

+     * 1. 查找所有网卡地址,必须非回路(loopback)地址、非局域网地址(siteLocal)、IPv4地址
+     * 2. 如果无满足要求的地址,调用 {@link InetAddress#getLocalHost()} 获取地址
+     * 
+ * + * @return 本机网卡IP地址,获取失败返回null + */ + @Nullable + public static InetAddress getLocalhost() { + var address = getAllAddress().stream() + .filter(it -> !it.isLoopbackAddress() + // 非地区本地地址,指10.0.0.0 ~ 10.255.255.255、172.16.0.0 ~ 172.31.255.255、192.168.0.0 ~ 192.168.255.255 + && it.isSiteLocalAddress() + // 需为IPV4地址 + && it instanceof Inet4Address + && !it.getHostAddress().contains(":")) + .findFirst(); + + if (address.isPresent()) { + return address.get(); + } + + try { + return InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + return null; + } + } + + /** + * 获得本机MAC地址 + * + * @return 本机MAC地址 + */ + public static String getLocalMacAddress() { + return getMacAddress(getLocalhost()); + } + + /** + * 获得指定地址信息中的MAC地址,使用分隔符“-” + * + * @param inetAddress {@link InetAddress} + * @return MAC地址,用-分隔 + */ + public static String getMacAddress(InetAddress inetAddress) { + return getMacAddress(inetAddress, "-"); + } + + /** + * 获得指定地址信息中的MAC地址 + * + * @param inetAddress {@link InetAddress} + * @param separator 分隔符,推荐使用“-”或者“:” + * @return MAC地址,用-分隔 + */ + public static String getMacAddress(InetAddress inetAddress, String separator) { + AssertionUtils.notNull(inetAddress); + + byte[] mac; + try { + mac = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress(); + } catch (SocketException e) { + throw new IllegalArgumentException(e); + } + if (null != mac) { + final StringBuilder sb = new StringBuilder(); + String s; + for (int i = 0; i < mac.length; i++) { + if (i != 0) { + sb.append(separator); + } + // 字节转换为整数 + s = Integer.toHexString(mac[i] & 0xFF); + sb.append(s.length() == 1 ? 0 + s : s); + } + return sb.toString(); + } + return null; + } + + /** + * 使用普通Socket发送数据 + * + * @param host Server主机 + * @param port Server端口 + * @param data 数据 + * @throws IOException IO异常 + */ + public static void netCat(String host, int port, byte[] data) throws IOException { + OutputStream out = null; + try { + Socket socket = new Socket(host, port); + out = socket.getOutputStream(); + out.write(data); + out.flush(); + } finally { + IOUtils.closeIO(out); + } + } + +} diff --git a/util/src/main/java/com/zfoo/util/security/AesUtils.java b/util/src/main/java/com/zfoo/util/security/AesUtils.java new file mode 100644 index 00000000..6a403561 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/security/AesUtils.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + + +import com.zfoo.protocol.util.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.util.Base64; + +/** + * AES加密和解密 + *

+ * 默认AES/ECB/PKCS5Padding + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class AesUtils { + + private static final Key KEY; + private static final String KEY_STR = "=jE[`B],YO24Vt+Akh&}D7@s9l1uLKP)"; + + /** + * 密钥算法 + */ + private static final String ALGORITHM = "AES"; + /** + * 加解密算法/工作模式/填充方式 + */ + private static final String ALGORITHM_STR = "AES/ECB/PKCS5Padding"; + + static { + try { + KEY = new SecretKeySpec(KEY_STR.getBytes(StringUtils.DEFAULT_CHARSET_NAME), ALGORITHM); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * 对str进行AES加密 + * + * @param str 需要加密的字符串 + * @return AES加密后的字符串 + */ + public static String getEncryptString(String str) { + try { + var base64Encoder = Base64.getEncoder(); + var strBytes = str.getBytes(StringUtils.DEFAULT_CHARSET_NAME); + var cipher = Cipher.getInstance(ALGORITHM_STR); + cipher.init(Cipher.ENCRYPT_MODE, KEY); + var encryptStrBytes = cipher.doFinal(strBytes); + return base64Encoder.encodeToString(encryptStrBytes); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 对str进行AES解密 + * + * @param str 需要解密的字符串 + * @return AES解密后的字符串 + */ + public static String getDecryptString(String str) { + try { + var base64Decoder = Base64.getDecoder(); + var strBytes = base64Decoder.decode(str); + var cipher = Cipher.getInstance(ALGORITHM_STR); + cipher.init(Cipher.DECRYPT_MODE, KEY); + var decryptStrBytes = cipher.doFinal(strBytes); + return new String(decryptStrBytes, StringUtils.DEFAULT_CHARSET_NAME); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } +} diff --git a/util/src/main/java/com/zfoo/util/security/IdUtils.java b/util/src/main/java/com/zfoo/util/security/IdUtils.java new file mode 100644 index 00000000..4e81c899 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/security/IdUtils.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class IdUtils { + + private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0); + + /** + * 本地int的id,如果达到最大值则重新从最小值重新计算 + */ + public static int getLocalIntId() { + return ATOMIC_INTEGER.incrementAndGet(); + } + + + /** + * 获得分布式环境下唯一id + * + * @return String UUID + */ + public static String getUUID() { + String uuid = UUID.randomUUID().toString(); + //去掉“-”符号 + return uuid.replaceAll("-", ""); + } + + + /** + * 小的id在前 - 大的id在后 + * + * @param a 第一个数字 + * @param b 第二个数字 + * @return 生成的id + */ + public static String generateStringId(long a, long b) { + return Math.min(a, b) + "-" + Math.max(a, b); + } + +} diff --git a/util/src/main/java/com/zfoo/util/security/MD5Utils.java b/util/src/main/java/com/zfoo/util/security/MD5Utils.java new file mode 100644 index 00000000..e272ee4b --- /dev/null +++ b/util/src/main/java/com/zfoo/util/security/MD5Utils.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + +import com.zfoo.protocol.util.StringUtils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * MD5的全称是Message-Digest Algorithm 5,在90年代初由MIT的计算机科学实验室和RSA Data Security Inc发明,经MD2、MD3和MD4发展而来。 + *

+ * MD5将任意长度的“字节串”变换成一个128bit的大整数,并且它是一个不可逆的字符串变换算法。 + *

+ * 换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串。 + *

+ * 从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。 + *

+ * MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。 + *

+ * 举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人, + * 别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”, + * 这就是所谓的数字签名应用。MD5还广泛用于加密和解密技术上,在很多操作系统中,用户的密码是以MD5值(或类似的其它算法)的方式保存的, + * 用户Login的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的MD5值进行比较,而系统并不“知道”用户的密码是什么。 + * + * @author jaysunxiao + * @version 3.0 + */ +public abstract class MD5Utils { + + private static final String MD5_ALGORITHM = "MD5"; + + /** + * 16进制字符 + */ + private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + + public static String strToMD5(String str) { + if (StringUtils.isBlank(str)) { + throw new NullPointerException(); + } + + return bytesToMD5(StringUtils.bytes(str)); + } + + // MD5将任意长度的“字节串”变换成一个128bit的大整数 + public static String bytesToMD5(byte[] bytes) { + try { + + var messageDigest = MessageDigest.getInstance(MD5_ALGORITHM); + // inputByteArray是输入字符串转换得到的字节数组 + messageDigest.update(bytes); + + // 转换并返回结果,也是字节数组,包含16个元素 + // 字符数组转换成字符串返回,MD5将任意长度的字节数组变换成一个16个字节,128bit的大整数 + return byteArrayToHex(messageDigest.digest()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("MD加密出现未知异常", e); + } + } + + //下面这个函数用于将字节数组换成成16进制的字符串 + private static String byteArrayToHex(byte[] bytes) { + // new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方)) + var resultCharArray = new char[bytes.length * 2]; + // 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去 + var index = 0; + for (var b : bytes) { + resultCharArray[index++] = HEX_CHARS[b >>> 4 & 0xf]; + resultCharArray[index++] = HEX_CHARS[b & 0xf]; + } + + // 字符数组组合成字符串返回 + return new String(resultCharArray); + } +} diff --git a/util/src/main/java/com/zfoo/util/security/ZipUtils.java b/util/src/main/java/com/zfoo/util/security/ZipUtils.java new file mode 100644 index 00000000..924ebaf7 --- /dev/null +++ b/util/src/main/java/com/zfoo/util/security/ZipUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + +import com.zfoo.protocol.util.IOUtils; + +import java.io.ByteArrayOutputStream; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public abstract class ZipUtils { + + private static final int BUFFER_SIZE = IOUtils.ONE_BYTE; + + // compression level (0-9),只能是0-9 + private static final int COMPRESS_LEVEL = 5; + + public static byte[] zip(byte[] bytes) { + // deflate [dɪ'fleɪt] v.抽出空气; 缩小 + Deflater deflater = new Deflater(COMPRESS_LEVEL); + deflater.setInput(bytes); + deflater.finish(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE); + byte[] buffer = new byte[BUFFER_SIZE]; + + while (!deflater.finished()) { + int count = deflater.deflate(buffer); + baos.write(buffer, 0, count); + } + deflater.end(); + + IOUtils.closeIO(baos); + + return baos.toByteArray(); + } + + public static byte[] unZip(byte[] bytes) { + Inflater inflater = new Inflater(); + inflater.setInput(bytes); + ByteArrayOutputStream baos = new ByteArrayOutputStream(BUFFER_SIZE); + try { + byte[] buffer = new byte[BUFFER_SIZE]; + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + baos.write(buffer, 0, count); + } + } catch (DataFormatException e) { + throw new RuntimeException(e); + } finally { + IOUtils.closeIO(baos); + } + return baos.toByteArray(); + } + +} diff --git a/util/src/main/resources/captcha/actionj.ttf b/util/src/main/resources/captcha/actionj.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8439247f84524bafbc11161b739bc003a74e65b0 GIT binary patch literal 34944 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 PbbZ_c#iM*|Uf|^yqU-eI literal 0 HcmV?d00001 diff --git a/util/src/main/resources/captcha/fresnel.ttf b/util/src/main/resources/captcha/fresnel.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7e32a510d8e86c8b24b747ac8b1ac8aedff9a29e GIT binary patch literal 75560 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⪙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�!o?4jw_80_#lnNlOkVc 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 RP%=%u`R&*br_LkK`Ult-^acO` literal 0 HcmV?d00001 diff --git a/util/src/main/resources/captcha/headache.ttf b/util/src/main/resources/captcha/headache.ttf new file mode 100644 index 0000000000000000000000000000000000000000..26fa0af9f000b0b8ab7697e001393623723f80d2 GIT binary patch literal 45292 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.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 w@*C=b#%g?X@~ literal 0 HcmV?d00001 diff --git a/util/src/main/resources/captcha/prefix.ttf b/util/src/main/resources/captcha/prefix.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a1aeed09ea4a575be65e7c8597b47a7e28a96ed0 GIT binary patch literal 99016 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 G{=WgPB%B%m literal 0 HcmV?d00001 diff --git a/util/src/main/resources/captcha/progbot.ttf b/util/src/main/resources/captcha/progbot.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c98d1221ea8177cd9417cf633e03b6822fbc85ea GIT binary patch literal 86956 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≷WOT( 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 literal 0 HcmV?d00001 diff --git a/util/src/main/resources/captcha/robot.ttf b/util/src/main/resources/captcha/robot.ttf new file mode 100644 index 0000000000000000000000000000000000000000..09e2de05db1cde8324746959043eb93401f626bb GIT binary patch literal 45072 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 if5tn&g_}x~v>Qmfyvldhlcr^W> set = ClassUtils.getAllClasses("com.zfoo"); + for (Class clazz : set) { + System.out.println(clazz.getName()); + } + System.out.println(StringUtils.MULTIPLE_HYPHENS); + } + + @Test + public void getClassPath() { + System.out.println(ClassUtils.getClassAbsPath(User.class)); + } + + + @Test + public void getClassFromClassPath() throws IOException { + System.out.println(new String(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("com.zfoo.util.ClassUtilsTest")))); + } + +} diff --git a/util/src/test/java/com/zfoo/util/DomUtilsTest.java b/util/src/test/java/com/zfoo/util/DomUtilsTest.java new file mode 100644 index 00000000..bb5a12a3 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/DomUtilsTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class DomUtilsTest { + + private static final String XML_WITH_HEAD = "" + + "\n" + + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n" + + ""; + + private static final String XML_OF_STANDARD_TEXT = + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + "\n" + + ""; + + @Test + public void testXmlWithHead() { + Protos protos = DomUtils.string2Object(XML_WITH_HEAD, Protos.class); + Assert.assertEquals("jaysunxiao", protos.getAuthor()); + Assert.assertEquals("1.0", protos.getVersion()); + } + + @Test + public void testXmlOfStandardText() { + Protos protos = DomUtils.string2Object(XML_OF_STANDARD_TEXT, Protos.class); + Assert.assertEquals("jaysunxiao", protos.getAuthor()); + Assert.assertEquals("1.0", protos.getVersion()); + } + +} + +@JsonPropertyOrder({"version", "author", "list"}) +@JacksonXmlRootElement(localName = "protocols") +class Protos { + @JacksonXmlProperty(isAttribute = true, localName = "version") + private String version; + @JacksonXmlProperty(isAttribute = true, localName = "author") + private String author; + @JacksonXmlProperty(localName = "protocol") + @JacksonXmlElementWrapper(useWrapping = false) + private List protos; + + public String getVersion() { + return version; + } + + public String getAuthor() { + return author; + } + + public List getProtos() { + return protos; + } +} + +@JsonPropertyOrder({"id", "location"}) +class Proto { + @JacksonXmlProperty(isAttribute = true, localName = "id") + private int id; + @JacksonXmlProperty(isAttribute = true, localName = "location") + private String location; + + public int getId() { + return id; + } + + public String getLocation() { + return location; + } +} + + diff --git a/util/src/test/java/com/zfoo/util/EnumUtilsTest.java b/util/src/test/java/com/zfoo/util/EnumUtilsTest.java new file mode 100644 index 00000000..c888d032 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/EnumUtilsTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ + +enum Num { + ONE, + TWO, + THREE; +} + +public class EnumUtilsTest { + + @Test + public void isInEnumsTest() { + Assert.assertTrue(EnumUtils.isInEnums("ONE", new Num[]{Num.ONE, Num.TWO, Num.THREE})); + } + +} diff --git a/util/src/test/java/com/zfoo/util/FileUtilTest.java b/util/src/test/java/com/zfoo/util/FileUtilTest.java new file mode 100644 index 00000000..3611f56b --- /dev/null +++ b/util/src/test/java/com/zfoo/util/FileUtilTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.zfoo.protocol.util.FileUtils; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class FileUtilTest { + + @Test + public void absPathTest() { + var absPath = FileUtils.getProAbsPath(); + System.out.println(absPath); + } + + @Test + public void createFile() throws IOException { + FileUtils.createFile(FileUtils.getProAbsPath() + File.separator + "hello", "hhh"); + } + + @Test + public void deleteFile() { + FileUtils.deleteFile(new File(FileUtils.getProAbsPath() + File.separator + "hello")); + } + + @Test + public void writeFile() { + FileUtils.writeStringToFile(new File(FileUtils.getProAbsPath() + File.separator + "test.txt"), "hello world!"); + } + + + @Test + public void readFile() { + String str = FileUtils.readFileToString(new File(FileUtils.getProAbsPath() + File.separator + "test.txt")); + System.out.println(str); + } + + + @Test + public void getProjectPath() { + System.out.println(FileUtils.getProAbsPath()); + } + + + @Test + public void searchFile() { + FileUtils.searchFileInProject(new File(FileUtils.getProAbsPath())); + } + + @Test + public void getAllFiles() { + List list = FileUtils.getAllReadableFiles(new File(FileUtils.getProAbsPath())); + for (File file : list) { + System.out.println(file.getName()); + } + } + + @Test + public void searchFileInProject() { + System.out.println(FileUtils.searchFileInProject("User")); + } + +} diff --git a/util/src/test/java/com/zfoo/util/JsonUtilTest.java b/util/src/test/java/com/zfoo/util/JsonUtilTest.java new file mode 100644 index 00000000..f3d3c606 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/JsonUtilTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.zfoo.protocol.model.Triple; +import com.zfoo.protocol.util.JsonUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.util.*; + +/** + * @author jaysunxiao + * @version 3.0 + */ + + +public class JsonUtilTest { + + public static String id = "\"id\":\"1000\""; + public static String name = "\"name\":\"jaysunxiao\""; + public static String sex = "\"sex\":\"man\""; + public static String age = "\"age\":22"; + public static String list = "\"list\":[1,2,3]"; + public static String map = "\"map\":{\"1\":1,\"2\":2,\"3\":3}"; + + public static String userJson = "{" + id + "," + name + "," + sex + "," + + age + "," + list + "," + map + "}"; + + @Test + public void string2Object() { + User user = JsonUtils.string2Object(userJson, User.class); + Assert.assertEquals(user.getId(), "1000"); + Assert.assertEquals(user.getName(), "jaysunxiao"); + Assert.assertEquals(user.getSex(), "man"); + Assert.assertEquals(user.getList().size(), 3); + Assert.assertEquals(user.getMap().size(), 3); + } + + @Test + public void object2String() { + User user = new User(); + user.setId("1000"); + user.setName("jaysunxiao"); + user.setSex("man"); + user.setAge(22); + //数组,链表,list + List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + user.setList(list); + //map + Map map = new HashMap<>(); + map.put(1, 1); + map.put(2, 2); + map.put(3, 3); + user.setMap(map); + + Assert.assertEquals(JsonUtils.object2String(user), userJson); + } + + @Test + public void string2List() { + String str = "[1,2,3]"; + List list = JsonUtils.string2List(str, Integer.class); + + Assert.assertEquals(list.size(), 3); + } + + @Test + public void string2Set() { + String str = "[1,2,3]"; + Set set = JsonUtils.string2Set(str, Integer.class); + + Assert.assertEquals(set.size(), 3); + } + + @Test + public void string2Map() { + String str = "{\"1\":1,\"2\":2,\"3\":3}"; + Map map = JsonUtils.string2Map(str, Integer.class, Integer.class); + + Assert.assertEquals(map.size(), 3); + } + + @Test + public void string2Array() { + String str = "[1,2,3]"; + Integer[] list = JsonUtils.string2Array(str, Integer.class); + + Assert.assertEquals(list.length, 3); + } + + @Test + public void getNodeTest() { + Assert.assertEquals(JsonUtils.getNode(userJson, "id").asText(), "1000"); + } + + @Test + public void tripleTest() { + var triple = new Triple("a", "b", "c"); + var tripleStr = JsonUtils.object2String(triple); + var temp = JsonUtils.string2Object(tripleStr, Triple.class); + } +} + +//@JsonIgnoreProperties({"name", "age"})//可以将它看做是@JsonIgnore的批量操作 +class User { + private String id; + //@JsonIgnore//作用在字段或方法上,用来完全忽略被注解的字段和方法对应的属性 + //@JsonProperty//注意这里必须得有该注解,因为没有提供对应的getId和setId函数,而是其他的getter和setter,防止遗漏该属性 + private String name; + private String sex; + private int age; + private List list; + private Map map; + + @Override + public String toString() { + return "User{" + + "id='" + id + '\'' + + ", name='" + name + '\'' + + ", sex='" + sex + '\'' + + ", age=" + age + + ", list=" + list + + ", map=" + map + + '}'; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} + diff --git a/util/src/test/java/com/zfoo/util/NetUtilsTest.java b/util/src/test/java/com/zfoo/util/NetUtilsTest.java new file mode 100644 index 00000000..3a496c3f --- /dev/null +++ b/util/src/test/java/com/zfoo/util/NetUtilsTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.zfoo.util.net.NetUtils; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.IOException; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class NetUtilsTest { + + @Test + public void localhostTest() { + var localHostStr = NetUtils.getLocalhostStr(); + var localHost = NetUtils.getLocalhost(); + Assert.assertEquals(localHostStr, localHost.getHostAddress()); + } + + @Test + public void ipv4Test() { + var ipLong = NetUtils.ipv4ToLong(NetUtils.LOCAL_LOOPBACK_IP); + var ipStr = NetUtils.longToIpv4(ipLong); + Assert.assertEquals(ipStr, NetUtils.LOCAL_LOOPBACK_IP); + } + + @Test + public void isInnerIpTest() { + Assert.assertTrue(NetUtils.isInnerIP(NetUtils.LOCAL_LOOPBACK_IP)); + } + + @Ignore + @Test + public void isNetCatTest() throws IOException { + NetUtils.netCat(NetUtils.LOCAL_LOOPBACK_IP, 9000, new byte[]{1, 2, 3}); + } + + @Ignore + @Test + public void isUsableLocalPortTest() { + System.out.println(NetUtils.isAvailablePort(2181)); + System.out.println(NetUtils.getAvailablePort(2181)); + } + + @Ignore + @Test + public void getAllNetworkInterfaceTest() { + var set = NetUtils.getAllNetworkInterface(); + set.stream().forEach(it -> System.out.println(it)); + } + + @Ignore + @Test + public void getAllAddressTest() { + var set = NetUtils.getAllAddress(); + set.stream().forEach(it -> System.out.println(it)); + } + + @Ignore + @Test + public void localIpv4sTest() { + var set = NetUtils.localIpv4s(); + set.stream().forEach(it -> System.out.println(it)); + } + +} diff --git a/util/src/test/java/com/zfoo/util/ReflectUtilTest.java b/util/src/test/java/com/zfoo/util/ReflectUtilTest.java new file mode 100644 index 00000000..e19a4626 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/ReflectUtilTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.zfoo.protocol.util.ReflectionUtils; +import org.junit.Ignore; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.function.Consumer; +import java.util.function.Predicate; + +/** + * @author jaysunxiao + * @version 3.0 + */ + +@interface Id { +} + + +@Ignore +public class ReflectUtilTest { + + @Test + public void testGetFieldsByAnnotation() { + Field[] fields = ReflectionUtils.getFieldsByAnnoInPOJOClass(User.class, Id.class); + System.out.println(fields.length); + } + + @Test + public void testFilterFieldsInClass() { + ReflectionUtils.filterFieldsInClass(User.class, new Predicate() { + @Override + public boolean test(Field field) { + return field != null; + } + }, new Consumer() { + @Override + public void accept(Field field) { + System.out.println(field.getName()); + } + }); + } + +} diff --git a/util/src/test/java/com/zfoo/util/StringUtilTest.java b/util/src/test/java/com/zfoo/util/StringUtilTest.java new file mode 100644 index 00000000..2496be91 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/StringUtilTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util; + +import com.zfoo.protocol.util.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class StringUtilTest { + + @Test + public void formatTest() { + String str = StringUtils.format("this is {} for {}", "a", "b"); + Assert.assertEquals("this is a for b", str); + } + + @Test + public void isEmpty() { + Assert.assertFalse(StringUtils.isEmpty(" ")); + } + + @Test + public void capitalize() { + String str = "hello world!"; + Assert.assertEquals(StringUtils.capitalize(str), "Hello world!"); + } + + @Test + public void unCapitalize() { + String str = "Hello world!"; + Assert.assertEquals(StringUtils.uncapitalize(str), "hello world!"); + } + +} diff --git a/util/src/test/java/com/zfoo/util/captcha/CaptchaTest.java b/util/src/test/java/com/zfoo/util/captcha/CaptchaTest.java new file mode 100644 index 00000000..f40e4dcd --- /dev/null +++ b/util/src/test/java/com/zfoo/util/captcha/CaptchaTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.captcha; + +import com.zfoo.util.captcha.model.CaptchaFontEnum; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.io.FileOutputStream; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class CaptchaTest { + + @Test + public void pngTest() throws Exception { + + for (var font : CaptchaFontEnum.values()) { + PngCaptcha pngCaptcha = new PngCaptcha(); + pngCaptcha.setLength(4); + pngCaptcha.setCaptchaFont(font); + pngCaptcha.buildCaptcha(); + System.out.println(pngCaptcha.captcha()); + pngCaptcha.drawImage(new FileOutputStream(new File(font.name() + ".png"))); + } + } + + @Test + public void gifTest() throws Exception { + for (var font : CaptchaFontEnum.values()) { + GifCaptcha gifCaptcha = new GifCaptcha(); + gifCaptcha.setLength(5); + gifCaptcha.setCaptchaFont(font); + gifCaptcha.buildCaptcha(); + System.out.println(gifCaptcha.captcha()); + gifCaptcha.drawImage(new FileOutputStream(new File(font.name() + ".gif"))); + } + } + + @Test + public void arithmeticTest() throws Exception { + for (var font : CaptchaFontEnum.values()) { + ArithmeticCaptcha specCaptcha = new ArithmeticCaptcha(); + specCaptcha.setLength(3); + specCaptcha.setCaptchaFont(font); + specCaptcha.buildCaptcha(); + System.out.println(specCaptcha.getArithmeticString() + " " + specCaptcha.captcha()); + specCaptcha.drawImage(new FileOutputStream(new File(font.name() + ".png"))); + } + } + + @Test + public void base64Test() throws Exception { + for (var i = 0; i < Long.MAX_VALUE; i++) { + GifCaptcha gifCaptcha = new GifCaptcha(); + gifCaptcha.buildCaptcha(); + gifCaptcha.toBase64(); + + PngCaptcha pngCaptcha = new PngCaptcha(); + pngCaptcha.buildCaptcha(); + pngCaptcha.toBase64(); + + ArithmeticCaptcha arithmeticCaptcha = new ArithmeticCaptcha(); + arithmeticCaptcha.buildCaptcha(); + arithmeticCaptcha.toBase64(); + } + } + +} diff --git a/util/src/test/java/com/zfoo/util/collection/CollectionUtilsTest.java b/util/src/test/java/com/zfoo/util/collection/CollectionUtilsTest.java new file mode 100644 index 00000000..c995fe96 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/collection/CollectionUtilsTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.collection; + +import com.zfoo.protocol.collection.CollectionUtils; +import com.zfoo.protocol.model.Pair; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class CollectionUtilsTest { + + @Test + public void mapRemoveTest() { + Map map = new ConcurrentHashMap<>(); + map.put("a", "a"); + Assert.assertEquals(map.remove("a"), "a"); + } + + @Test + public void collateTest() { + var a = new ArrayList<>(List.of(1, 2, 3, 9)); + var b = new ArrayList<>(List.of(2, 4, 6, 7, 8)); + var c = CollectionUtils.collate(a, b); + var d = List.of(1, 2, 2, 3, 4, 6, 7, 8, 9); + Assert.assertArrayEquals(c.toArray(), d.toArray()); + + a = new ArrayList<>(List.of(1, 2, 3)); + b = new ArrayList<>(); + c = CollectionUtils.collate(a, b); + Assert.assertArrayEquals(c.toArray(), a.toArray()); + } + + @Test + public void listJoinListTest() { + var a = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9); + var b = List.of(11, 22, 33, 44, 55); + var c = List.of(1, 2, 3, 4, 5, 6); + + var d = CollectionUtils.listJoinList(true, new Pair<>(2, a), new Pair<>(2, b), new Pair<>(2, c)); + Assert.assertArrayEquals(d.toArray(), List.of(1, 2, 11, 22, 3, 4, 5, 6, 33, 44, 7, 8, 55, 9).toArray()); + + var e = CollectionUtils.listJoinList(false, new Pair<>(2, a), new Pair<>(2, b), new Pair<>(2, c)); + Assert.assertArrayEquals(e.toArray(), List.of(1, 2, 11, 22, 1, 2, 3, 4, 33, 44, 3, 4, 5, 6, 55, 5, 6, 7, 8, 9).toArray()); + } + + @Test + public void subListLastTest() { + var list = List.of(1, 2, 3, 4, 5); + Assert.assertArrayEquals(CollectionUtils.subListLast(list, 1).toArray(), List.of(5).toArray()); + Assert.assertArrayEquals(CollectionUtils.subListLast(list, 4).toArray(), List.of(2, 3, 4, 5).toArray()); + Assert.assertArrayEquals(CollectionUtils.subListLast(list, 5).toArray(), list.toArray()); + Assert.assertArrayEquals(CollectionUtils.subListLast(list, 6).toArray(), list.toArray()); + Assert.assertArrayEquals(CollectionUtils.subListLast(list, 100).toArray(), list.toArray()); + } + +} diff --git a/util/src/test/java/com/zfoo/util/collection/GeneralTreeTest.java b/util/src/test/java/com/zfoo/util/collection/GeneralTreeTest.java new file mode 100644 index 00000000..c584bb8c --- /dev/null +++ b/util/src/test/java/com/zfoo/util/collection/GeneralTreeTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.collection; + +import com.zfoo.protocol.collection.tree.GeneralTree; +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class GeneralTreeTest { + + + @Test + public void test() { + var generalTree = new GeneralTree(); + generalTree.addNode("a", "hi"); + generalTree.addNode("a.b", "hello"); + generalTree.addNode("a.b.c", "world1"); + generalTree.addNode("a.b.d", "world2"); + generalTree.addNode("a.b.e", "world3"); + + System.out.println(generalTree.getNodeByPath("a.b").getData()); + System.out.println(generalTree.getNodeByPath("a.b").getChildren()); + } + +} diff --git a/util/src/test/java/com/zfoo/util/math/ConsistentHashTest.java b/util/src/test/java/com/zfoo/util/math/ConsistentHashTest.java new file mode 100644 index 00000000..5fd84b6b --- /dev/null +++ b/util/src/test/java/com/zfoo/util/math/ConsistentHashTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math; + +import com.zfoo.protocol.model.Pair; +import com.zfoo.protocol.util.JsonUtils; +import com.zfoo.protocol.util.StringUtils; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.TreeMap; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class ConsistentHashTest { + + //待添加入Hash环的服务器列表 + private static List> servers = List.of(new Pair<>("192.168.0.0:111", "192.168.0.0:111") + , new Pair<>("192.168.0.1:111", "192.168.0.1:111"), new Pair<>("192.168.0.2:111", "192.168.0.2:111")); + + private static List> nums = List.of(new Pair<>("1", "1"), new Pair<>("2", "2"), new Pair<>("3", "3")); + + private static List> chars = List.of(new Pair<>("a", "a"), new Pair<>("b", "b"), new Pair<>("c", "c")); + + + @Test + public void consistentHashTest() { + test(servers); + System.out.println(StringUtils.MULTIPLE_HYPHENS); + test(nums); + System.out.println(StringUtils.MULTIPLE_HYPHENS); + test(chars); + System.out.println(StringUtils.MULTIPLE_HYPHENS); + } + + public void test(List> list) { + var realNodeMap = new HashMap(); + var hitNodeMap = new HashMap(); + + list.forEach(it -> realNodeMap.put(it.getKey(), 0)); + + var consistentHash = new ConsistentHash<>(list, 300); + for (int i = 0; i < 100000; i++) { + var key = String.valueOf(i); + var realNode = consistentHash.getRealNode(key).getKey(); + + int nums = realNodeMap.get(realNode); + realNodeMap.put(realNode, ++nums); + hitNodeMap.put(i, realNode); + } + for (int i = 0; i < 1000; i++) { + for (int j = 0; j < 100000; j++) { + var key = String.valueOf(j); + var realNode = consistentHash.getRealNode(key).getKey(); + Assert.assertEquals(realNode, hitNodeMap.get(j)); + } + } + + System.out.println(JsonUtils.object2String(realNodeMap)); + } + + + @Test + public void testTreeMap() { + var treeMap = new TreeMap(); + treeMap.put(1, "a"); + treeMap.put(100, "b"); + treeMap.put(200, "c"); + + System.out.println(treeMap.ceilingEntry(8)); + System.out.println(treeMap.ceilingEntry(450)); + + } + +} diff --git a/util/src/test/java/com/zfoo/util/math/dfa/WordTreeTest.java b/util/src/test/java/com/zfoo/util/math/dfa/WordTreeTest.java new file mode 100644 index 00000000..746d2a73 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/math/dfa/WordTreeTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math.dfa; + +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; + +/** + * @author jaysunxiao + * @version 3.0 + */ +@Ignore +public class WordTreeTest { + + @Test + public void test() { + WordTree tree = new WordTree(); + tree.addWord("大"); + tree.addWord("大土豆"); + tree.addWord("土豆"); + tree.addWord("刚出锅"); + tree.addWord("出锅"); + tree.addWord("fuck"); + + // 正文 + String text = "text asdff asdf afucksdf "; + List matchAll = tree.matchAll(text, -1, true, false); + System.out.println(matchAll); + } + +} diff --git a/util/src/test/java/com/zfoo/util/math/lexer/LexicalAnalysisTest.java b/util/src/test/java/com/zfoo/util/math/lexer/LexicalAnalysisTest.java new file mode 100644 index 00000000..a14ca56a --- /dev/null +++ b/util/src/test/java/com/zfoo/util/math/lexer/LexicalAnalysisTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.math.lexer; + +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class LexicalAnalysisTest { + private static final String str = " void main() {\n" + + " if (a == 8) {\n" + + " c = c +2;\n" + + " b = 1.2;\n" + + " c = c + 3;\n" + + " d = \"hello world\";\n" + + " }\n" + + " }"; + + @Test + public void test() { + var lexical = new LexicalAnalysis(); + lexical.analyze(str.toCharArray()); + } +} diff --git a/util/src/test/java/com/zfoo/util/security/AesUtilsTest.java b/util/src/test/java/com/zfoo/util/security/AesUtilsTest.java new file mode 100644 index 00000000..977fdd68 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/security/AesUtilsTest.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * 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. + */ + +package com.zfoo.util.security; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class AesUtilsTest { + + @Test + public void test() { + String passWord = "hello world"; + String encodePassWorld = AesUtils.getEncryptString(passWord); + Assert.assertEquals(passWord, AesUtils.getDecryptString(encodePassWorld)); + } + +} diff --git a/util/src/test/java/com/zfoo/util/security/IdUtilsTest.java b/util/src/test/java/com/zfoo/util/security/IdUtilsTest.java new file mode 100644 index 00000000..240a57f0 --- /dev/null +++ b/util/src/test/java/com/zfoo/util/security/IdUtilsTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class IdUtilsTest { + + @Test + public void test() { + var atomicInteger = new AtomicInteger(Integer.MAX_VALUE); + atomicInteger.getAndIncrement(); + Assert.assertEquals(atomicInteger.get(), Integer.MIN_VALUE); + } + +} diff --git a/util/src/test/java/com/zfoo/util/security/MD5UtilsTest.java b/util/src/test/java/com/zfoo/util/security/MD5UtilsTest.java new file mode 100644 index 00000000..ce2b23cf --- /dev/null +++ b/util/src/test/java/com/zfoo/util/security/MD5UtilsTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.util.DigestUtils; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class MD5UtilsTest { + + @Test + public void md5Test() { + var str = "qwerasdfzxcv1234;:'"; + Assert.assertEquals(MD5Utils.bytesToMD5(str.getBytes()).toLowerCase(), DigestUtils.md5DigestAsHex(str.getBytes())); + } + +} diff --git a/util/src/test/java/com/zfoo/util/security/ZipTest.java b/util/src/test/java/com/zfoo/util/security/ZipTest.java new file mode 100644 index 00000000..e575176a --- /dev/null +++ b/util/src/test/java/com/zfoo/util/security/ZipTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 The zfoo Authors + * + * 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. + */ + +package com.zfoo.util.security; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author jaysunxiao + * @version 3.0 + */ +public class ZipTest { + + // Zip算法压缩测试 + @Test + public void test() { + String str = "ZIP,是一个文件的压缩的算法,原名Deflate(真空),发明者为菲利普·卡兹(Phil Katz))," + + "他于1989年1月公布了该格式的资料。ZIP通常使用后缀名“.zip”,它的MIME格式为 application/zip 。" + + "目前,ZIP格式属于几种主流的压缩格式之一,其竞争者包括RAR格式以及开放源码的7-Zip格式。" + + "从性能上比较,RAR格式较ZIP格式压缩率较高,但是它的压缩时间远远高于Zip。" + + "而7-Zip(7z)由于提供了免费的压缩工具而逐渐在更多的领域得到应用。"; + + byte[] bytes = str.getBytes(); + + // 压缩前数组长度 + Assert.assertEquals(bytes.length, 555); + + bytes = ZipUtils.zip(bytes); + // 压缩后数组长度 + Assert.assertEquals(bytes.length, 438); + + bytes = ZipUtils.unZip(bytes); + + Assert.assertEquals(new String(bytes), str); + } + +}