├── .DS_Store
├── .gitignore
├── README.md
├── images
├── faild.png
├── flowChart.png
└── process.jpg
├── merkel_tree_bg.json
├── pom.xml
└── src
└── main
└── java
└── com
└── upex
├── BgMerkleValidator.java
├── constants
├── MerkelTreeConstants.java
└── TreeNodeRoleConstants.java
├── model
├── MerKelTree.java
├── MerkleProof.java
└── TreeNode.java
└── util
├── CollectionUtils.java
├── EncryptionUtils.java
├── MerkelTreeUtils.java
├── NumberUtils.java
└── StringUtils.java
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BitgetLimited/proof-of-reserves/c8a61864cfb91808300ac0e26418a42f49e8ed84/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /src/test/
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proof of Reserves Licensed to Bitget Limited
2 | ## Background
3 |
4 | Bitget launches Proof of Reserve (PoR) to improve the security and transparency of user assets. These tools will allow you to independently audit Bitget’s Proof of Reserves as well as verify that Bitget’s reserves have exceed the exchange’s known liabilities to all users to confirm Bitget’s solvency.
5 |
6 | ## Introduction
7 | ### Build from source
8 | Download the latest version for your operating system and architecture. Also, you can build the source code yourself.
9 |
10 | [Download] (https://www.oracle.com/java/technologies/downloads/)Install JDK(Java Development Kit)
11 | [Download] (https://maven.apache.org/download.cgi.)Install Maven build tool
12 |
13 | The minimum prerequisite to build this project requires Java version >= 11, Maven version >= 3.8.4
14 |
15 | ### Package and compile source code
16 | #### Enter the path for the project
17 | `cd ~/Downloads/proof-of-reserves`
18 |
19 | #### Install dependencies
20 | `mvn clean install`
21 |
22 | #### Start up
23 | `java -jar proof-of-reserves.jar`
24 |
25 | # Technical Description
26 | ## What is the Merkle Tree?
27 | Merkle Tree is a data structure, also known as a Hash Tree. Merkle tree stores data in the leaf nodes of the tree structure, and by hashing the data step by step up to the top root node, any changes in the data of the leaf nodes will be passed to the higher level nodes and eventually displayed as changes in the root of the tree.
28 |
29 | ### 1. The roles of Merkle tree
30 | - Zero-knowledge proof
31 | - Ensure data immutability
32 | - Ensures data privacy
33 | ### 2. Bitget Limited Merkle Tree Definition
34 | #### 2.1 Node Information
35 | Information stored in every tree node includes:
36 | 1. hash value;
37 | 2. the number of coins contained in the user's asset snapshot (BTC, ETH, USDT for example);
38 | ```
39 | hash value,{"BTC":"BTC amount","ETH":"ETH amount","USDT":"USDT amount"}
40 | 2070b6a5b12f4ea7,{"BTC":1.763,"ETH":362,"USDT":1077200.2274}
41 | ```
42 | #### 2.2 Hash Rules
43 | ##### Leaf nodes (except padding nodes)
44 | `hash=sha256Function(encryptUid,nonce,balances).substring(0,16)`
45 | - encryptUid: encrypted UID of the user
46 | - nonce: a unique value assigned to each user
47 | - balances: json string composed of the number of coins in the user's asset snapshot, (note: remove the invalid 0 at the end and keep precision of 8 bits)
48 | - For example:
49 | ```json
50 | {"BTC":1.763,"ETH":362,"USDT":1077200.2274}
51 | ```
52 | ##### Parent node
53 | ```
54 | Parent node's hash = sha256Function(hash1+hash2,{"BTC":(hash1(BTC amount)+hash2(BTC amount)),"ETH":(hash1(ETH amount)+hash2(ETH amount)),"USDT":(hash1(USDT amount)+hash2(USDT amount))},parent node level).substring(0,16)
55 | ```
56 | - h1: hash of the left child node of the current node,
57 | - h2: hash of the right child node of the current node,
58 | - level: where the parent node lies in
59 |
60 | **Definition of tree node level**:A complete Merkle Tree (full binary tree) requires 2^n leaf node data, leaf node level = n + 1, parent node level = child node level - 1, root node level = 1, leaf node level is the maximum
61 |
62 | ##### Padding node rules
63 | A complete Merkle Tree (full binary tree) requires 2^n leaf node data, but the actual number of data may not satisfy and may be odd. In such a case, if a node k has no sibling node, then auto padding generates a sibling node k', and`hash(k')=hash(k)`, and the number of coins of node k' is set to zero.
64 |
65 |
66 | ###### For example:
67 | | Hash | balances |
68 | |--------| -------------|
69 | | hash1 | {"BTC":1,"ETH": 6,"USDT":10}|
70 | | hash2 | {"BTC":2,"ETH":4,"USDT":8}|
71 | | hash3 | {"BTC":5,"ETH":9,"USDT":74}|
72 |
73 | Then the padding node hash4 = hash3, stored balances are `{"BTC": 0, "ETH": 0,"USDT": 0}`,as shown in the highlighted node in Figure one:
74 | Figure one
75 |
76 |
77 | ```
78 | Parent node's hash = sha256Function(hash1+hash2,{"BTC":(hash1(BTC amount)+hash2(BTC amount)),"ETH":(hash1(ETH amount)+hash2(ETH amount)),"USDT":(hash1(USDT amount)+hash2(USDT amount))},parent node level).substring(0,16)
79 | ```
80 | Thus:
81 | `hash6 = SHA256(hash3 + hash3, {BTC: (2+0), ETH:(1+0), USDT:(12+0)}, level)`
82 |
83 | ### Verification Principle
84 | #### 1、Verification principle:
85 | According to the definition of Bitget Limited Merkle tree, the hash value of the parent node is calculated from the user's own leaf node up to the root node, and the hash value of the root node is compared with the hash value of the Merkle tree in "Verification Step - Step 1", if the two are equal, the verification passes, if not, the verification fails.
86 |
87 | #### 2、Example:
88 | Combining figure one and the following json text, and based on the user's own leaf node h3 and the information provided by the adjacent node h4, we can calculate out the hash of the parent node h6, and then with the information provided by the adjacent node h5, we can calculate out the hash of the parent node h7, and then compare the hash value with the root node h7 provided in the Merkle tree path data to see if the hash values are equal to complete the validation.
89 | Merkle tree path data json text:
90 | ```json
91 | {
92 | "path": [
93 | {
94 | "auditId": "Au20221125",
95 | "balances": {
96 | "BTC": 4.6115136,
97 | "ETH": 0,
98 | "USDT": 4372722.80025793
99 | },
100 | "encryptUid": "58b8f244a465335eb0c67e0d5c13a66b52c76abc1e41a6373763da35f5ce7ce1",
101 | "level": 3,
102 | "merkelLeaf": "8cf0243a2c76fe0b",
103 | "nonce": "gblzjurybs7fiptqdaez6t3pegazguye77fhsr6q4tbqkndubnjf1962csg54em4",
104 | "role": 2
105 | },
106 | {
107 | "auditId": "Au20221125",
108 | "balances": {
109 | "BTC": 0.000098,
110 | "ETH": 0,
111 | "USDT": 9000.30237189
112 | },
113 | "level": 2,
114 | "merkelLeaf": "8306844dff98ba79",
115 | "role": 2
116 | },
117 | {
118 | "auditId": "Au20221125",
119 | "balances": {
120 | "BTC": 2001254.40269617,
121 | "ETH": 1999998.0656526,
122 | "USDT": 993781612.22955519
123 | },
124 | "level": 1,
125 | "merkelLeaf": "94d0d60f7cdce5fe",
126 | "role": 3
127 | }
128 | ],
129 | "self": {
130 | "auditId": "Au20221125",
131 | "balances": {
132 | "BTC": 2001249.79108457,
133 | "ETH": 1999998.0656526,
134 | "USDT": 989399889.12692537
135 | },
136 | "encryptUid": "8c3358cd4d2572cf01de53f41717e72a91b4c6da53ce1232113c91e5cf192dd4",
137 | "level": 3,
138 | "merkelLeaf": "cb575fb1eb6462f9",
139 | "nonce": "fi9honco6fww8afc4t2se8aml3i46pzfwjgepy3n2bbvuouns4tfiasz60klcm1p",
140 | "role": 1
141 | }
142 | }
143 | ```
144 |
145 | #### Verification Steps
146 | 1. Take the executable verifier that you need to download on the Bitget platform for your operating system and architecture.
147 | - proof-of-reserves-linux-amd64-v1.0.2.zip
148 | - proof-of-reserves-linux-arm64-v1.0.2.zip
149 | - proof-of-reserves-macos-v1.0.2.zip
150 | - proof-of-reserves-windows-v1.0.2.zip
151 | 2. Unzip the file to a specified directory, for example:
152 | `~/Downloads/proof-of-reserves-*`
153 | 3. Download the file merkel_tree_bg.json and substitute the file with the same name under your directory`~/Downloads/proof-of-reserves-*`
154 | 4. Run start file `sh start.sh` or Click the `start.bat` file
155 | 5. View results
156 | 1)If your data are correct and the verification passed, then the result is "Consistent with the Merkle tree root hash. The verification succeeds".
157 |
158 | 2)If your data are wrong and the verification fails, the result is "Inconsistent with the Merkle tree root hash. The verification fails".
159 |
160 | 6. You can also refer to the Bitget Limited open source verification tool code and Merkle tree definition (refer to the "What is the Merkle Tree" section) and write your own program to verify the path data obtained in step 2, or check to make sure your assets are included in the Merkel tree generated by this audit.
--------------------------------------------------------------------------------
/images/faild.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BitgetLimited/proof-of-reserves/c8a61864cfb91808300ac0e26418a42f49e8ed84/images/faild.png
--------------------------------------------------------------------------------
/images/flowChart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BitgetLimited/proof-of-reserves/c8a61864cfb91808300ac0e26418a42f49e8ed84/images/flowChart.png
--------------------------------------------------------------------------------
/images/process.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BitgetLimited/proof-of-reserves/c8a61864cfb91808300ac0e26418a42f49e8ed84/images/process.jpg
--------------------------------------------------------------------------------
/merkel_tree_bg.json:
--------------------------------------------------------------------------------
1 | {
2 | "path": [
3 | {
4 | "auditId": "Au20221129",
5 | "balances": {
6 | "BTC": 0,
7 | "ETH": 0,
8 | "USDT": 7681.73476302
9 | },
10 | "encryptUid": "e1117ce2af7d7fbf4fe7a77cc9515e92f6cf11f59ab803cb749d64c426342f6d",
11 | "level": 11,
12 | "merkelLeaf": "64ced402edefa4da",
13 | "nonce": "5j01abmm722ak2wvlq75bntkz18idvfimwwhaypt66bjn0ptne81160et3ajv1z3",
14 | "role": 2
15 | },
16 | {
17 | "auditId": "Au20221129",
18 | "balances": {
19 | "BTC": 1000000.0001998,
20 | "ETH": 300000,
21 | "USDT": 1299991.9972
22 | },
23 | "level": 10,
24 | "merkelLeaf": "87bd27437fbf59c2",
25 | "role": 1
26 | },
27 | {
28 | "auditId": "Au20221129",
29 | "balances": {
30 | "BTC": 0,
31 | "ETH": 0,
32 | "USDT": 53051.10598709
33 | },
34 | "level": 9,
35 | "merkelLeaf": "d61c58070ee1dc12",
36 | "role": 2
37 | },
38 | {
39 | "auditId": "Au20221129",
40 | "balances": {
41 | "BTC": 11.98852944,
42 | "ETH": 157.31252,
43 | "USDT": 1099754333.25209158
44 | },
45 | "level": 8,
46 | "merkelLeaf": "81cc6c9125fa98cb",
47 | "role": 2
48 | },
49 | {
50 | "auditId": "Au20221129",
51 | "balances": {
52 | "BTC": 715.09237636,
53 | "ETH": 2100.0404595,
54 | "USDT": 2020155332.95708230
55 | },
56 | "level": 7,
57 | "merkelLeaf": "4ccb436f7883fe91",
58 | "role": 1
59 | },
60 | {
61 | "auditId": "Au20221129",
62 | "balances": {
63 | "BTC": 61400096.00294400,
64 | "ETH": 433168.81989999,
65 | "USDT": 1016785013.13079698
66 | },
67 | "level": 6,
68 | "merkelLeaf": "795423500e6d597d",
69 | "role": 1
70 | },
71 | {
72 | "auditId": "Au20221129",
73 | "balances": {
74 | "BTC": 286595751.08265201,
75 | "ETH": 26727776.90857214,
76 | "USDT": 2453648994.37423942
77 | },
78 | "level": 5,
79 | "merkelLeaf": "6050ca3d4e946783",
80 | "role": 2
81 | },
82 | {
83 | "auditId": "Au20221129",
84 | "balances": {
85 | "BTC": 8508725002.69311378,
86 | "ETH": 2146389444.08493589,
87 | "USDT": 15787098930.60614156
88 | },
89 | "level": 4,
90 | "merkelLeaf": "8ad038df7e82fce6",
91 | "role": 2
92 | },
93 | {
94 | "auditId": "Au20221129",
95 | "balances": {
96 | "BTC": 9944837661.61102144,
97 | "ETH": 10112560970.15270098,
98 | "USDT": 63861352068.33045056
99 | },
100 | "level": 3,
101 | "merkelLeaf": "e33150a464c294ed",
102 | "role": 2
103 | },
104 | {
105 | "auditId": "Au20221129",
106 | "balances": {
107 | "BTC": 420217093.46290786,
108 | "ETH": 221002355.98534612,
109 | "USDT": 6649023671.61392569
110 | },
111 | "level": 2,
112 | "merkelLeaf": "864f125992c7bae9",
113 | "role": 2
114 | },
115 | {
116 | "auditId": "Au20221129",
117 | "balances": {
118 | "BTC": 19222776331.93374469,
119 | "ETH": 12507420973.30443462,
120 | "USDT": 92889184629.59267820
121 | },
122 | "level": 1,
123 | "merkelLeaf": "ffedeaf82363b23c",
124 | "role": 3
125 | }
126 | ],
127 | "self": {
128 | "auditId": "Au20221129",
129 | "balances": {
130 | "BTC": 0,
131 | "ETH": 5000,
132 | "USDT": 5560.49
133 | },
134 | "encryptUid": "b3e887893212aa3faea8923b3b4c8589131895e7f468d394c4c508e31e3c85bf",
135 | "level": 11,
136 | "merkelLeaf": "1fa8ef498dc505f6",
137 | "nonce": "wx9bzgdojl0hoz7ckgft0k1cdyly7qohglaphsmqkx0hv80hjlwf0igvnamdqbh7",
138 | "role": 1
139 | }
140 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.upex
6 | proof-of-reserves
7 | 1.0.0
8 |
9 | jar
10 |
11 | proof-of-reserves
12 | https://www.bitget.com
13 |
14 |
15 | UTF-8
16 |
17 |
18 |
19 |
20 |
21 | org.apache.commons
22 | commons-lang3
23 | 3.9
24 |
25 |
26 |
27 | commons-collections
28 | commons-collections
29 | 3.2.2
30 |
31 |
32 |
33 |
34 | com.alibaba
35 | fastjson
36 | 1.2.83
37 |
38 |
39 |
40 |
41 |
42 | proof-of-reserves
43 |
44 |
45 | maven-assembly-plugin
46 |
47 | false
48 |
49 | jar-with-dependencies
50 |
51 |
52 |
53 | com.upex.BgMerkleValidator
54 |
55 |
56 |
57 |
58 |
59 | make-assembly
60 | package
61 |
62 | assembly
63 |
64 |
65 |
66 |
67 |
68 | org.apache.maven.plugins
69 | maven-compiler-plugin
70 |
71 | 11
72 | 11
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/BgMerkleValidator.java:
--------------------------------------------------------------------------------
1 | package com.upex;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.upex.model.MerkleProof;
5 | import com.upex.util.CollectionUtils;
6 | import com.upex.util.StringUtils;
7 |
8 | import java.io.IOException;
9 | import java.nio.charset.StandardCharsets;
10 | import java.nio.file.Files;
11 | import java.nio.file.Path;
12 |
13 | /**
14 | * Verify whether the account assets are included in the Merkle tree published by Bitget
15 | *
16 | * @author BitgetLimited
17 | */
18 | public class BgMerkleValidator {
19 | /**
20 | * merkel_tree_bg.json
21 | **/
22 | private static final String MERKLE_TREE_BG_FILE_PATH = "merkel_tree_bg.json";
23 |
24 | public static void main(String[] args) {
25 | System.out.println("Merkel tree path validation start");
26 | // 获取"merkel_tree_bg.json"文件内容
27 | String merkleJsonFile = getMerkleJsonFile();
28 | if (StringUtils.isBlank(merkleJsonFile)) {
29 | System.out.println("Merkel tree path validation failed, invalid merkle proof file");
30 | return;
31 | }
32 |
33 | // 获得默克尔树证明对象
34 | MerkleProof merkleProof = JSONObject.parseObject(merkleJsonFile, MerkleProof.class);
35 |
36 | // 默克尔树参数验证
37 | if(validate(merkleProof)){
38 | System.out.println("Consistent with the Merkle tree root hash. The verification succeeds");
39 | }else {
40 | System.out.println("Inconsistent with the Merkle tree root hash. The verification fails");
41 | }
42 | }
43 |
44 | /**
45 | * validate
46 | * @param merkleProof
47 | * @return
48 | * @author BitgetLimited
49 | * @date 2022/11/25 20:29
50 | */
51 | private static boolean validate(MerkleProof merkleProof){
52 |
53 | // self节点不能为空 并且 path节点也不能为空
54 | if(merkleProof.getSelf() == null || CollectionUtils.isEmpty(merkleProof.getPath())){
55 | return false;
56 | }
57 |
58 | // 验证self数据一致性
59 | if(!merkleProof.getSelf().validateSelf()){
60 | return false;
61 | }
62 |
63 | // 验证path参数验证
64 | if(!merkleProof.getPath().get(0).validatePath()){
65 | return false;
66 | }
67 |
68 | if(merkleProof.getPath().get(0).getRole().intValue() == merkleProof.getSelf().getRole().intValue()){
69 | return false;
70 | }
71 |
72 | return merkleProof.validate();
73 | }
74 |
75 |
76 | /**
77 | * get merkel_tree_bg.json content
78 | *
79 | * @author BitgetLimited
80 | * @date 2022/11/25 16:53
81 | */
82 | private static String getMerkleJsonFile() {
83 | StringBuilder builder = new StringBuilder();
84 | try {
85 | Files.readAllLines(Path.of(MERKLE_TREE_BG_FILE_PATH), StandardCharsets.UTF_8).forEach(builder::append);
86 | return builder.toString();
87 | } catch (IOException e) {
88 | throw new RuntimeException(MERKLE_TREE_BG_FILE_PATH + " file does not exist");
89 | }
90 | }
91 | }
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/constants/MerkelTreeConstants.java:
--------------------------------------------------------------------------------
1 | package com.upex.constants;
2 |
3 | /**
4 | * MerkelTreeConstants
5 | * @author BitgetLimited
6 | * @date 2022/11/25 23:03
7 | */
8 | public class MerkelTreeConstants {
9 |
10 | /**
11 | * 逗号
12 | */
13 | public static final String COMMA = ",";
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/constants/TreeNodeRoleConstants.java:
--------------------------------------------------------------------------------
1 | package com.upex.constants;
2 |
3 | /** TreeNodeRoleConstants
4 | * @author BitgetLimited
5 | * @date 2020-11-18 16:14
6 | * @desc
7 | **/
8 | public class TreeNodeRoleConstants {
9 | /**
10 | * 空节点
11 | */
12 | public static final Integer EMPTY_NODE = 0;
13 |
14 | /**
15 | * 左节点
16 | */
17 | public static final Integer LEFT_NODE = 1;
18 |
19 | /**
20 | * 右节点
21 | */
22 | public static final Integer RIGHT_NODE = 2;
23 |
24 | /**
25 | * 根节点
26 | */
27 | public static final Integer ROOT_NODE = 3;
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/model/MerKelTree.java:
--------------------------------------------------------------------------------
1 | package com.upex.model;
2 |
3 | import com.upex.constants.TreeNodeRoleConstants;
4 | import com.upex.util.MerkelTreeUtils;
5 |
6 | import java.math.BigDecimal;
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | /**
12 | * 默克尔树
13 | * @author BitgetLimited
14 | * @date 2022/11/26 00:04
15 | */
16 | public class MerKelTree {
17 |
18 | /**
19 | * build merkelTree root node
20 | * @param path
21 | * @param self
22 | * @return {@link TreeNode }
23 | * @author BitgetLimited
24 | * @date 2022/11/27 11:56
25 | */
26 | public TreeNode buildMerkelTreeRoot(List path, TreeNode self) {
27 | if (path.size() <= 1) {
28 | throw new IllegalArgumentException("Must be at least two leafs to construct a Merkle tree");
29 | }
30 |
31 | TreeNode parent = createParentTreeNode(path.get(0), self);
32 |
33 | for (int i = 1; i < path.size() - 1; i++) {
34 | self = parent;
35 | parent = createParentTreeNode(path.get(i), self);
36 | }
37 |
38 | return parent;
39 | }
40 |
41 | /**
42 | * createParentTreeNode
43 | * @param friend
44 | * @param self
45 | * @return {@link TreeNode }
46 | * @author BitgetLimited
47 | * @date 2022/11/27 11:57
48 | */
49 | TreeNode createParentTreeNode(TreeNode friend, TreeNode self) {
50 | TreeNode parent;
51 | if (TreeNodeRoleConstants.LEFT_NODE.equals(friend.getRole())) {
52 | // friend 是左节点
53 | parent = constructInternalNode(friend, self);
54 | }else {
55 | // friend 是右节点或者空节点
56 | parent = constructInternalNode(self, friend);
57 | }
58 |
59 | return parent;
60 | }
61 |
62 | /**
63 | * constructInternalNode
64 | * @param left
65 | * @param right
66 | * @return
67 | */
68 | private TreeNode constructInternalNode(TreeNode left, TreeNode right) {
69 | TreeNode parent = new TreeNode();
70 |
71 | if (right == null) {
72 | right = createEmptyTreeNode(left);
73 | }
74 | parent.mergeAsset(left);
75 | parent.mergeAsset(right);
76 |
77 | // 左节点hash+右节点hash+parent资产+左节点层级
78 | parent.setLevel(left.getLevel() - 1);
79 | parent.setMerkelLeaf(MerkelTreeUtils.createMerkelParentLeaf(left, right, parent));
80 | return parent;
81 | }
82 |
83 | /**
84 | * clearAssetsMap
85 | * @param right
86 | * @return {@link java.util.Map }
87 | * @author BitgetLimited
88 | * @date 2022/11/27 11:59
89 | */
90 | private static Map clearAssetsMap(TreeNode right) {
91 | Map assetsMap = right.getBalances();
92 | Map result = new HashMap<>();
93 | assetsMap.keySet().forEach(coinName -> result.put(coinName, BigDecimal.ZERO));
94 | return result;
95 | }
96 |
97 | /**
98 | * createEmptyTreeNode
99 | * @param source
100 | * @return {@link TreeNode }
101 | * @author BitgetLimited
102 | * @date 2022/11/25 23:40
103 | */
104 | private TreeNode createEmptyTreeNode(TreeNode source){
105 | TreeNode target = new TreeNode();
106 |
107 | target.setAuditId(source.getAuditId());
108 | target.setNonce(source.getNonce());
109 | target.setMerkelLeaf(source.getMerkelLeaf());
110 | target.setLevel(source.getLevel());
111 | target.setRole(source.getRole());
112 | target.setEncryptUid(source.getEncryptUid());
113 | target.setRole(TreeNodeRoleConstants.EMPTY_NODE);
114 | target.setBalances(clearAssetsMap(target));
115 |
116 | return target;
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/model/MerkleProof.java:
--------------------------------------------------------------------------------
1 | package com.upex.model;
2 |
3 | import java.util.List;
4 | /**
5 | * MerkleProof
6 | * @author BitgetLimited
7 | * @date 2022/11/25 23:40
8 | */
9 | public class MerkleProof {
10 | private List path;
11 | private TreeNode self;
12 |
13 | public List getPath() {
14 | return path;
15 | }
16 |
17 | public TreeNode getSelf() {
18 | return self;
19 | }
20 |
21 | public void setPath(List path) {
22 | this.path = path;
23 | }
24 |
25 | public void setSelf(TreeNode self) {
26 | this.self = self;
27 | }
28 |
29 | /**
30 | * validate
31 | * A new root is being introduced through the path and self provided by the user. Compare with the root in the path
32 | * @return {@link boolean }
33 | * @author BitgetLimited
34 | * @date 2022/11/27 11:54
35 | */
36 | public boolean validate() {
37 | TreeNode newRoot = new MerKelTree().buildMerkelTreeRoot(path, self);
38 | TreeNode oldRoot = path.get(path.size() - 1);
39 |
40 | System.out.printf("Generator Root BTC balance : %s ,merkel_tree_bg Root BTC balance in file: %s%n", newRoot.getBalances().get("BTC"), oldRoot.getBalances().get("BTC"));
41 | System.out.printf("Generator Root ETH balance : %s ,merkel_tree_bg Root ETH balance in file: %s%n", newRoot.getBalances().get("ETH"), oldRoot.getBalances().get("ETH"));
42 | System.out.printf("Generator Root USDT balance : %s ,merkel_tree_bg Root USDT balance in file: %s%n", newRoot.getBalances().get("USDT"), oldRoot.getBalances().get("USDT"));
43 | System.out.printf("Generator Root MerkelLeaf : %s ,merkel_tree_bg Root MerkelLeaf in file: %s%n", newRoot.getMerkelLeaf(), oldRoot.getMerkelLeaf());
44 |
45 | if (newRoot.getMerkelLeaf().equals(oldRoot.getMerkelLeaf()) && newRoot.validateEqualsBalances(oldRoot) && newRoot.getLevel().equals(oldRoot.getLevel())) {
46 | return true;
47 | }
48 | return false;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/model/TreeNode.java:
--------------------------------------------------------------------------------
1 | package com.upex.model;
2 |
3 | import com.upex.util.MerkelTreeUtils;
4 | import com.upex.util.StringUtils;
5 | import org.apache.commons.collections.MapUtils;
6 |
7 | import java.math.BigDecimal;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 | /**
11 | * TreeNode
12 | * @author BitgetLimited
13 | * @date 2022/11/25 23:39
14 | */
15 | public class TreeNode {
16 |
17 | private String auditId;
18 |
19 | private Map balances = new HashMap<>();
20 |
21 | private String nonce;
22 |
23 | private String merkelLeaf;
24 |
25 | private Integer level;
26 |
27 | /**
28 | * role 0-empty, 1-left, 2-right, 3-root
29 | */
30 | private Integer role;
31 |
32 | private String encryptUid;
33 |
34 | public String getAuditId() {
35 | return auditId;
36 | }
37 |
38 | public void setAuditId(String auditId) {
39 | this.auditId = auditId;
40 | }
41 |
42 | public Map getBalances() {
43 | return balances;
44 | }
45 |
46 | public void setBalances(Map balances) {
47 | this.balances = balances;
48 | }
49 |
50 | public String getNonce() {
51 | return nonce;
52 | }
53 |
54 | public void setNonce(String nonce) {
55 | this.nonce = nonce;
56 | }
57 |
58 | public String getMerkelLeaf() {
59 | return merkelLeaf;
60 | }
61 |
62 | public void setMerkelLeaf(String merkelLeaf) {
63 | this.merkelLeaf = merkelLeaf;
64 | }
65 |
66 | public Integer getLevel() {
67 | return level;
68 | }
69 |
70 | public void setLevel(Integer level) {
71 | this.level = level;
72 | }
73 |
74 | public Integer getRole() {
75 | return role;
76 | }
77 |
78 | public void setRole(Integer role) {
79 | this.role = role;
80 | }
81 |
82 | public String getEncryptUid() {
83 | return encryptUid;
84 | }
85 |
86 | public void setEncryptUid(String encryptUid) {
87 | this.encryptUid = encryptUid;
88 | }
89 |
90 | /**
91 | * 验证self节点
92 | * @return {@link boolean }
93 | * @author BitgetLimited
94 | * @date 2022/11/25 23:44
95 | */
96 | public boolean validateSelf() {
97 | String merkelNodeLeaf = MerkelTreeUtils.createMerkelNodeLeaf(this);
98 | return merkelLeaf.equals(merkelNodeLeaf);
99 | }
100 |
101 | /**
102 | * 验证Path节点数据
103 | * @return {@link boolean }
104 | * @author BitgetLimited
105 | * @date 2022/11/25 23:45
106 | */
107 | public boolean validatePath() {
108 | if (!validateBalances() || StringUtils.isBlank(merkelLeaf) || role < 0 || role > 3 || level < 1) {
109 | return false;
110 | }
111 | return true;
112 | }
113 |
114 | /**
115 | * 验证资产集合
116 | * @return {@link boolean }
117 | * @author BitgetLimited
118 | * @date 2022/11/25 22:30
119 | */
120 | public boolean validateBalances() {
121 | if(MapUtils.isEmpty(balances)){
122 | return false;
123 | }
124 |
125 | for (BigDecimal decimal : balances.values()) {
126 | if(decimal == null){
127 | return false;
128 | }
129 | }
130 | return true;
131 | }
132 |
133 | /**
134 | * mergeAsset
135 | * @param childNode
136 | */
137 | public void mergeAsset(TreeNode childNode){
138 | if (childNode == null) {
139 | return;
140 | }
141 | childNode.balances.forEach((coinName,amount)->{
142 | BigDecimal oldValue = balances.get(coinName);
143 | if (oldValue != null) {
144 | BigDecimal newValue = oldValue.add(amount);
145 | balances.put(coinName,newValue);
146 | }else{
147 | balances.put(coinName, amount);
148 | }
149 | });
150 | }
151 |
152 | /**
153 | * validateEqualsBalances
154 | * @return {@link boolean }
155 | * @author BitgetLimited
156 | * @date 2022/11/25 22:30
157 | */
158 | public boolean validateEqualsBalances(TreeNode oldRoot) {
159 | Map oldBalances = oldRoot.getBalances();
160 | for (Map.Entry entry : balances.entrySet()) {
161 | if(oldBalances.get(entry.getKey()).compareTo(entry.getValue()) != 0){
162 | return false;
163 | }
164 | }
165 | return true;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/util/CollectionUtils.java:
--------------------------------------------------------------------------------
1 | package com.upex.util;
2 |
3 | import java.lang.reflect.Array;
4 | import java.util.*;
5 |
6 | public class CollectionUtils {
7 | private static Integer INTEGER_ONE = new Integer(1);
8 |
9 | public CollectionUtils() {
10 | }
11 |
12 | public static Collection union(Collection a, Collection b) {
13 | ArrayList list = new ArrayList();
14 | Map mapa = getCardinalityMap(a);
15 | Map mapb = getCardinalityMap(b);
16 | Set elts = new HashSet(a);
17 | elts.addAll(b);
18 | Iterator it = elts.iterator();
19 |
20 | while(it.hasNext()) {
21 | Object obj = it.next();
22 | int i = 0;
23 |
24 | for(int m = Math.max(getFreq(obj, mapa), getFreq(obj, mapb)); i < m; ++i) {
25 | list.add(obj);
26 | }
27 | }
28 |
29 | return list;
30 | }
31 |
32 | public static Collection intersection(Collection a, Collection b) {
33 | ArrayList list = new ArrayList();
34 | Map mapa = getCardinalityMap(a);
35 | Map mapb = getCardinalityMap(b);
36 | Set elts = new HashSet(a);
37 | elts.addAll(b);
38 | Iterator it = elts.iterator();
39 |
40 | while(it.hasNext()) {
41 | Object obj = it.next();
42 | int i = 0;
43 |
44 | for(int m = Math.min(getFreq(obj, mapa), getFreq(obj, mapb)); i < m; ++i) {
45 | list.add(obj);
46 | }
47 | }
48 |
49 | return list;
50 | }
51 |
52 | public static Collection disjunction(Collection a, Collection b) {
53 | ArrayList list = new ArrayList();
54 | Map mapa = getCardinalityMap(a);
55 | Map mapb = getCardinalityMap(b);
56 | Set elts = new HashSet(a);
57 | elts.addAll(b);
58 | Iterator it = elts.iterator();
59 |
60 | while(it.hasNext()) {
61 | Object obj = it.next();
62 | int i = 0;
63 |
64 | for(int m = Math.max(getFreq(obj, mapa), getFreq(obj, mapb)) - Math.min(getFreq(obj, mapa), getFreq(obj, mapb)); i < m; ++i) {
65 | list.add(obj);
66 | }
67 | }
68 |
69 | return list;
70 | }
71 |
72 | public static Collection subtract(Collection a, Collection b) {
73 | ArrayList list = new ArrayList(a);
74 | Iterator it = b.iterator();
75 |
76 | while(it.hasNext()) {
77 | list.remove(it.next());
78 | }
79 |
80 | return list;
81 | }
82 |
83 | public static boolean containsAny(Collection coll1, Collection coll2) {
84 | Iterator it;
85 | if (coll1.size() < coll2.size()) {
86 | it = coll1.iterator();
87 |
88 | while(it.hasNext()) {
89 | if (coll2.contains(it.next())) {
90 | return true;
91 | }
92 | }
93 | } else {
94 | it = coll2.iterator();
95 |
96 | while(it.hasNext()) {
97 | if (coll1.contains(it.next())) {
98 | return true;
99 | }
100 | }
101 | }
102 |
103 | return false;
104 | }
105 |
106 | public static Map getCardinalityMap(Collection coll) {
107 | Map count = new HashMap();
108 | Iterator it = coll.iterator();
109 |
110 | while(it.hasNext()) {
111 | Object obj = it.next();
112 | Integer c = (Integer)((Integer)count.get(obj));
113 | if (c == null) {
114 | count.put(obj, INTEGER_ONE);
115 | } else {
116 | count.put(obj, new Integer(c + 1));
117 | }
118 | }
119 |
120 | return count;
121 | }
122 |
123 | public static boolean isSubCollection(Collection a, Collection b) {
124 | Map mapa = getCardinalityMap(a);
125 | Map mapb = getCardinalityMap(b);
126 | Iterator it = a.iterator();
127 |
128 | Object obj;
129 | do {
130 | if (!it.hasNext()) {
131 | return true;
132 | }
133 |
134 | obj = it.next();
135 | } while(getFreq(obj, mapa) <= getFreq(obj, mapb));
136 |
137 | return false;
138 | }
139 |
140 | public static boolean isProperSubCollection(Collection a, Collection b) {
141 | return a.size() < b.size() && isSubCollection(a, b);
142 | }
143 |
144 | public static boolean isEqualCollection(Collection a, Collection b) {
145 | if (a.size() != b.size()) {
146 | return false;
147 | } else {
148 | Map mapa = getCardinalityMap(a);
149 | Map mapb = getCardinalityMap(b);
150 | if (mapa.size() != mapb.size()) {
151 | return false;
152 | } else {
153 | Iterator it = mapa.keySet().iterator();
154 |
155 | Object obj;
156 | do {
157 | if (!it.hasNext()) {
158 | return true;
159 | }
160 |
161 | obj = it.next();
162 | } while(getFreq(obj, mapa) == getFreq(obj, mapb));
163 |
164 | return false;
165 | }
166 | }
167 | }
168 |
169 | public static boolean addIgnoreNull(Collection collection, Object object) {
170 | return object == null ? false : collection.add(object);
171 | }
172 |
173 | public static void addAll(Collection collection, Iterator iterator) {
174 | while(iterator.hasNext()) {
175 | collection.add(iterator.next());
176 | }
177 |
178 | }
179 |
180 | public static void addAll(Collection collection, Enumeration enumeration) {
181 | while(enumeration.hasMoreElements()) {
182 | collection.add(enumeration.nextElement());
183 | }
184 |
185 | }
186 |
187 | public static void addAll(Collection collection, Object[] elements) {
188 | int i = 0;
189 |
190 | for(int size = elements.length; i < size; ++i) {
191 | collection.add(elements[i]);
192 | }
193 |
194 | }
195 |
196 | /** @deprecated */
197 | public static Object index(Object obj, int idx) {
198 | return index(obj, new Integer(idx));
199 | }
200 |
201 | /** @deprecated */
202 | public static Object index(Object obj, Object index) {
203 | if (obj instanceof Map) {
204 | Map map = (Map)obj;
205 | if (map.containsKey(index)) {
206 | return map.get(index);
207 | }
208 | }
209 |
210 | int idx = -1;
211 | if (index instanceof Integer) {
212 | idx = (Integer)index;
213 | }
214 |
215 | if (idx < 0) {
216 | return obj;
217 | } else if (obj instanceof Map) {
218 | Map map = (Map)obj;
219 | Iterator iterator = map.keySet().iterator();
220 | return index(iterator, idx);
221 | } else if (obj instanceof List) {
222 | return ((List)obj).get(idx);
223 | } else if (obj instanceof Object[]) {
224 | return ((Object[])((Object[])obj))[idx];
225 | } else {
226 | if (obj instanceof Enumeration) {
227 | Enumeration it = (Enumeration)obj;
228 |
229 | while(it.hasMoreElements()) {
230 | --idx;
231 | if (idx == -1) {
232 | return it.nextElement();
233 | }
234 |
235 | it.nextElement();
236 | }
237 | } else {
238 | if (obj instanceof Iterator) {
239 | return index((Iterator)obj, idx);
240 | }
241 |
242 | if (obj instanceof Collection) {
243 | Iterator iterator = ((Collection)obj).iterator();
244 | return index(iterator, idx);
245 | }
246 | }
247 |
248 | return obj;
249 | }
250 | }
251 |
252 | private static Object index(Iterator iterator, int idx) {
253 | while(iterator.hasNext()) {
254 | --idx;
255 | if (idx == -1) {
256 | return iterator.next();
257 | }
258 |
259 | iterator.next();
260 | }
261 |
262 | return iterator;
263 | }
264 |
265 | public static int size(Object object) {
266 | int total = 0;
267 | if (object instanceof Map) {
268 | total = ((Map)object).size();
269 | } else if (object instanceof Collection) {
270 | total = ((Collection)object).size();
271 | } else if (object instanceof Object[]) {
272 | total = ((Object[])((Object[])object)).length;
273 | } else if (object instanceof Iterator) {
274 | Iterator it = (Iterator)object;
275 |
276 | while(it.hasNext()) {
277 | ++total;
278 | it.next();
279 | }
280 | } else if (object instanceof Enumeration) {
281 | Enumeration it = (Enumeration)object;
282 |
283 | while(it.hasMoreElements()) {
284 | ++total;
285 | it.nextElement();
286 | }
287 | } else {
288 | if (object == null) {
289 | throw new IllegalArgumentException("Unsupported object type: null");
290 | }
291 |
292 | try {
293 | total = Array.getLength(object);
294 | } catch (IllegalArgumentException var3) {
295 | throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
296 | }
297 | }
298 |
299 | return total;
300 | }
301 |
302 | public static boolean sizeIsEmpty(Object object) {
303 | if (object instanceof Collection) {
304 | return ((Collection)object).isEmpty();
305 | } else if (object instanceof Map) {
306 | return ((Map)object).isEmpty();
307 | } else if (object instanceof Object[]) {
308 | return ((Object[])((Object[])object)).length == 0;
309 | } else if (object instanceof Iterator) {
310 | return !((Iterator)object).hasNext();
311 | } else if (object instanceof Enumeration) {
312 | return !((Enumeration)object).hasMoreElements();
313 | } else if (object == null) {
314 | throw new IllegalArgumentException("Unsupported object type: null");
315 | } else {
316 | try {
317 | return Array.getLength(object) == 0;
318 | } catch (IllegalArgumentException var2) {
319 | throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
320 | }
321 | }
322 | }
323 |
324 | public static boolean isEmpty(Collection coll) {
325 | return coll == null || coll.isEmpty();
326 | }
327 |
328 | public static boolean isNotEmpty(Collection coll) {
329 | return !isEmpty(coll);
330 | }
331 |
332 | public static void reverseArray(Object[] array) {
333 | int i = 0;
334 |
335 | for(int j = array.length - 1; j > i; ++i) {
336 | Object tmp = array[j];
337 | array[j] = array[i];
338 | array[i] = tmp;
339 | --j;
340 | }
341 |
342 | }
343 |
344 | private static final int getFreq(Object obj, Map freqMap) {
345 | Integer count = (Integer)freqMap.get(obj);
346 | return count != null ? count : 0;
347 | }
348 | }
349 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/util/EncryptionUtils.java:
--------------------------------------------------------------------------------
1 | package com.upex.util;
2 |
3 | import java.io.UnsupportedEncodingException;
4 | import java.security.MessageDigest;
5 | import java.security.NoSuchAlgorithmException;
6 |
7 | public class EncryptionUtils {
8 |
9 | public static String sha256(String str) {
10 | MessageDigest messageDigest;
11 | String encdeStr = "";
12 | try {
13 | messageDigest = MessageDigest.getInstance("SHA-256");
14 | byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
15 | encdeStr = byteTohex(hash);
16 | } catch (NoSuchAlgorithmException e) {
17 | e.printStackTrace();
18 | } catch (UnsupportedEncodingException e) {
19 | e.printStackTrace();
20 | }
21 | return encdeStr;
22 | }
23 |
24 | /**
25 | * 将二进制比特数组转化为字符串
26 | *
27 | * @param b
28 | * @return
29 | */
30 | private static String byteTohex(byte[] b) {
31 | String hs = "";
32 | String stmp = "";
33 | for (byte element : b) {
34 | stmp = (Integer.toHexString(element & 0XFF));
35 | if (stmp.length() == 1) {
36 | hs = hs + "0" + stmp;
37 | } else {
38 | hs = hs + stmp;
39 | }
40 | }
41 | return hs;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/util/MerkelTreeUtils.java:
--------------------------------------------------------------------------------
1 | package com.upex.util;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 | import com.upex.constants.MerkelTreeConstants;
5 | import com.upex.model.TreeNode;
6 |
7 | /**
8 | * MerkelTreeUtils
9 | * @author BitgetLimited
10 | * @date 2022/11/25 23:11
11 | */
12 | public class MerkelTreeUtils {
13 |
14 | /**
15 | * 生成节点hash
16 | * @param treeNode
17 | */
18 | public static String createMerkelNodeLeaf(TreeNode treeNode) {
19 | StringBuffer merkelHash = new StringBuffer();
20 | merkelHash.append(treeNode.getEncryptUid())
21 | .append(MerkelTreeConstants.COMMA)
22 | .append(treeNode.getNonce())
23 | .append(MerkelTreeConstants.COMMA)
24 | .append(JSONObject.toJSONString(treeNode.getBalances()));
25 | return EncryptionUtils.sha256(merkelHash.toString()).substring(0,16);
26 | }
27 |
28 | /**
29 | * createMerkelParentLeaf
30 | * @param left
31 | * @param right
32 | * @param parent
33 | * @return {@link String }
34 | * @author BitgetLimited
35 | * @date 2022/11/25 23:37
36 | */
37 | public static String createMerkelParentLeaf(TreeNode left, TreeNode right, TreeNode parent) {
38 | String hashId = left.getMerkelLeaf() +
39 | right.getMerkelLeaf() +
40 | MerkelTreeConstants.COMMA +
41 | JSONObject.toJSONString(parent.getBalances()) +
42 | MerkelTreeConstants.COMMA +
43 | (parent.getLevel());
44 | return EncryptionUtils.sha256(hashId).substring(0, 16);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/util/NumberUtils.java:
--------------------------------------------------------------------------------
1 | package com.upex.util;
2 |
3 | /**
4 | * NumberUtils
5 | * @author BitgetLimited
6 | * @date 2022/11/23 21:31
7 | */
8 | public class NumberUtils {
9 | /**
10 | * 获取数字为2的几次幂
11 | * @param number 数字
12 | * @return {@link int }
13 | * @author BitgetLimited
14 | * @date 2022/11/23 21:30
15 | */
16 | public static int isTimesTwo(int number) {
17 | if (number < 0) {
18 | throw new IllegalArgumentException("The Number Cannot Be Less Than Zero!!!");
19 | }
20 | if (!(number > 0 && (number & (number - 1)) == 0)) {
21 | return Integer.toBinaryString(number).length();
22 | }
23 | return Integer.toBinaryString(number).length() - 1;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/upex/util/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.upex.util;
2 |
3 | import org.apache.commons.lang3.CharUtils;
4 | import org.apache.commons.lang3.RegExUtils;
5 |
6 | import java.io.UnsupportedEncodingException;
7 | import java.nio.charset.Charset;
8 | import java.text.Normalizer;
9 | import java.text.Normalizer.Form;
10 | import java.util.*;
11 | import java.util.regex.Pattern;
12 |
13 | public class StringUtils {
14 | private static final int STRING_BUILDER_SIZE = 256;
15 | public static final String SPACE = " ";
16 | public static final String EMPTY = "";
17 | public static final String LF = "\n";
18 | public static final String CR = "\r";
19 | public static final int INDEX_NOT_FOUND = -1;
20 | private static final int PAD_LIMIT = 8192;
21 |
22 | public StringUtils() {
23 | }
24 |
25 | public static boolean isEmpty(CharSequence cs) {
26 | return cs == null || cs.length() == 0;
27 | }
28 |
29 | public static boolean isNotEmpty(CharSequence cs) {
30 | return !isEmpty(cs);
31 | }
32 |
33 | public static boolean isBlank(CharSequence cs) {
34 | int strLen;
35 | if (cs != null && (strLen = cs.length()) != 0) {
36 | for (int i = 0; i < strLen; ++i) {
37 | if (!Character.isWhitespace(cs.charAt(i))) {
38 | return false;
39 | }
40 | }
41 |
42 | return true;
43 | } else {
44 | return true;
45 | }
46 | }
47 |
48 | public static boolean isNotBlank(CharSequence cs) {
49 | return !isBlank(cs);
50 | }
51 |
52 | public static String trim(String str) {
53 | return str == null ? null : str.trim();
54 | }
55 |
56 | public static String trimToNull(String str) {
57 | String ts = trim(str);
58 | return isEmpty(ts) ? null : ts;
59 | }
60 |
61 | public static String trimToEmpty(String str) {
62 | return str == null ? "" : str.trim();
63 | }
64 |
65 | public static String truncate(String str, int maxWidth) {
66 | return truncate(str, 0, maxWidth);
67 | }
68 |
69 | public static String truncate(String str, int offset, int maxWidth) {
70 | if (offset < 0) {
71 | throw new IllegalArgumentException("offset cannot be negative");
72 | } else if (maxWidth < 0) {
73 | throw new IllegalArgumentException("maxWith cannot be negative");
74 | } else if (str == null) {
75 | return null;
76 | } else if (offset > str.length()) {
77 | return "";
78 | } else if (str.length() > maxWidth) {
79 | int ix = offset + maxWidth > str.length() ? str.length() : offset + maxWidth;
80 | return str.substring(offset, ix);
81 | } else {
82 | return str.substring(offset);
83 | }
84 | }
85 |
86 | public static String strip(String str) {
87 | return strip(str, (String) null);
88 | }
89 |
90 | public static String stripToNull(String str) {
91 | if (str == null) {
92 | return null;
93 | } else {
94 | str = strip(str, (String) null);
95 | return str.isEmpty() ? null : str;
96 | }
97 | }
98 |
99 | public static String stripToEmpty(String str) {
100 | return str == null ? "" : strip(str, (String) null);
101 | }
102 |
103 | public static String strip(String str, String stripChars) {
104 | if (isEmpty(str)) {
105 | return str;
106 | } else {
107 | str = stripStart(str, stripChars);
108 | return stripEnd(str, stripChars);
109 | }
110 | }
111 |
112 | public static String stripStart(String str, String stripChars) {
113 | int strLen;
114 | if (str != null && (strLen = str.length()) != 0) {
115 | int start = 0;
116 | if (stripChars == null) {
117 | while (start != strLen && Character.isWhitespace(str.charAt(start))) {
118 | ++start;
119 | }
120 | } else {
121 | if (stripChars.isEmpty()) {
122 | return str;
123 | }
124 |
125 | while (start != strLen && stripChars.indexOf(str.charAt(start)) != -1) {
126 | ++start;
127 | }
128 | }
129 |
130 | return str.substring(start);
131 | } else {
132 | return str;
133 | }
134 | }
135 |
136 | public static String stripEnd(String str, String stripChars) {
137 | int end;
138 | if (str != null && (end = str.length()) != 0) {
139 | if (stripChars == null) {
140 | while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) {
141 | --end;
142 | }
143 | } else {
144 | if (stripChars.isEmpty()) {
145 | return str;
146 | }
147 |
148 | while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != -1) {
149 | --end;
150 | }
151 | }
152 |
153 | return str.substring(0, end);
154 | } else {
155 | return str;
156 | }
157 | }
158 |
159 | public static String[] stripAll(String... strs) {
160 | return stripAll(strs, (String) null);
161 | }
162 |
163 | public static String[] stripAll(String[] strs, String stripChars) {
164 | int strsLen;
165 | if (strs != null && (strsLen = strs.length) != 0) {
166 | String[] newArr = new String[strsLen];
167 |
168 | for (int i = 0; i < strsLen; ++i) {
169 | newArr[i] = strip(strs[i], stripChars);
170 | }
171 |
172 | return newArr;
173 | } else {
174 | return strs;
175 | }
176 | }
177 |
178 | public static String stripAccents(String input) {
179 | if (input == null) {
180 | return null;
181 | } else {
182 | Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
183 | StringBuilder decomposed = new StringBuilder(Normalizer.normalize(input, Form.NFD));
184 | convertRemainingAccentCharacters(decomposed);
185 | return pattern.matcher(decomposed).replaceAll("");
186 | }
187 | }
188 |
189 | private static void convertRemainingAccentCharacters(StringBuilder decomposed) {
190 | for (int i = 0; i < decomposed.length(); ++i) {
191 | if (decomposed.charAt(i) == 321) {
192 | decomposed.deleteCharAt(i);
193 | decomposed.insert(i, 'L');
194 | } else if (decomposed.charAt(i) == 322) {
195 | decomposed.deleteCharAt(i);
196 | decomposed.insert(i, 'l');
197 | }
198 | }
199 |
200 | }
201 |
202 | public static boolean equals(CharSequence cs1, CharSequence cs2) {
203 | if (cs1 == cs2) {
204 | return true;
205 | } else if (cs1 != null && cs2 != null) {
206 | if (cs1.length() != cs2.length()) {
207 | return false;
208 | } else if (cs1 instanceof String && cs2 instanceof String) {
209 | return cs1.equals(cs2);
210 | } else {
211 | int length = cs1.length();
212 |
213 | for (int i = 0; i < length; ++i) {
214 | if (cs1.charAt(i) != cs2.charAt(i)) {
215 | return false;
216 | }
217 | }
218 |
219 | return true;
220 | }
221 | } else {
222 | return false;
223 | }
224 | }
225 |
226 | public static int compare(String str1, String str2) {
227 | return compare(str1, str2, true);
228 | }
229 |
230 | public static int compare(String str1, String str2, boolean nullIsLess) {
231 | if (str1 == str2) {
232 | return 0;
233 | } else if (str1 == null) {
234 | return nullIsLess ? -1 : 1;
235 | } else if (str2 == null) {
236 | return nullIsLess ? 1 : -1;
237 | } else {
238 | return str1.compareTo(str2);
239 | }
240 | }
241 |
242 | public static int compareIgnoreCase(String str1, String str2) {
243 | return compareIgnoreCase(str1, str2, true);
244 | }
245 |
246 | public static int compareIgnoreCase(String str1, String str2, boolean nullIsLess) {
247 | if (str1 == str2) {
248 | return 0;
249 | } else if (str1 == null) {
250 | return nullIsLess ? -1 : 1;
251 | } else if (str2 == null) {
252 | return nullIsLess ? 1 : -1;
253 | } else {
254 | return str1.compareToIgnoreCase(str2);
255 | }
256 | }
257 |
258 | public static boolean containsWhitespace(CharSequence seq) {
259 | if (isEmpty(seq)) {
260 | return false;
261 | } else {
262 | int strLen = seq.length();
263 |
264 | for (int i = 0; i < strLen; ++i) {
265 | if (Character.isWhitespace(seq.charAt(i))) {
266 | return true;
267 | }
268 | }
269 |
270 | return false;
271 | }
272 | }
273 |
274 | public static boolean containsNone(CharSequence cs, char... searchChars) {
275 | if (cs != null && searchChars != null) {
276 | int csLen = cs.length();
277 | int csLast = csLen - 1;
278 | int searchLen = searchChars.length;
279 | int searchLast = searchLen - 1;
280 |
281 | for (int i = 0; i < csLen; ++i) {
282 | char ch = cs.charAt(i);
283 |
284 | for (int j = 0; j < searchLen; ++j) {
285 | if (searchChars[j] == ch) {
286 | if (!Character.isHighSurrogate(ch)) {
287 | return false;
288 | }
289 |
290 | if (j == searchLast) {
291 | return false;
292 | }
293 |
294 | if (i < csLast && searchChars[j + 1] == cs.charAt(i + 1)) {
295 | return false;
296 | }
297 | }
298 | }
299 | }
300 |
301 | return true;
302 | } else {
303 | return true;
304 | }
305 | }
306 |
307 | public static boolean containsNone(CharSequence cs, String invalidChars) {
308 | return cs != null && invalidChars != null ? containsNone(cs, invalidChars.toCharArray()) : true;
309 | }
310 |
311 | public static String substring(String str, int start) {
312 | if (str == null) {
313 | return null;
314 | } else {
315 | if (start < 0) {
316 | start += str.length();
317 | }
318 |
319 | if (start < 0) {
320 | start = 0;
321 | }
322 |
323 | return start > str.length() ? "" : str.substring(start);
324 | }
325 | }
326 |
327 | public static String substring(String str, int start, int end) {
328 | if (str == null) {
329 | return null;
330 | } else {
331 | if (end < 0) {
332 | end += str.length();
333 | }
334 |
335 | if (start < 0) {
336 | start += str.length();
337 | }
338 |
339 | if (end > str.length()) {
340 | end = str.length();
341 | }
342 |
343 | if (start > end) {
344 | return "";
345 | } else {
346 | if (start < 0) {
347 | start = 0;
348 | }
349 |
350 | if (end < 0) {
351 | end = 0;
352 | }
353 |
354 | return str.substring(start, end);
355 | }
356 | }
357 | }
358 |
359 | public static String left(String str, int len) {
360 | if (str == null) {
361 | return null;
362 | } else if (len < 0) {
363 | return "";
364 | } else {
365 | return str.length() <= len ? str : str.substring(0, len);
366 | }
367 | }
368 |
369 | public static String right(String str, int len) {
370 | if (str == null) {
371 | return null;
372 | } else if (len < 0) {
373 | return "";
374 | } else {
375 | return str.length() <= len ? str : str.substring(str.length() - len);
376 | }
377 | }
378 |
379 | public static String mid(String str, int pos, int len) {
380 | if (str == null) {
381 | return null;
382 | } else if (len >= 0 && pos <= str.length()) {
383 | if (pos < 0) {
384 | pos = 0;
385 | }
386 |
387 | return str.length() <= pos + len ? str.substring(pos) : str.substring(pos, pos + len);
388 | } else {
389 | return "";
390 | }
391 | }
392 |
393 | private static StringBuilder newStringBuilder(int noOfItems) {
394 | return new StringBuilder(noOfItems * 16);
395 | }
396 |
397 | public static String substringBefore(String str, String separator) {
398 | if (!isEmpty(str) && separator != null) {
399 | if (separator.isEmpty()) {
400 | return "";
401 | } else {
402 | int pos = str.indexOf(separator);
403 | return pos == -1 ? str : str.substring(0, pos);
404 | }
405 | } else {
406 | return str;
407 | }
408 | }
409 |
410 | public static String substringAfter(String str, String separator) {
411 | if (isEmpty(str)) {
412 | return str;
413 | } else if (separator == null) {
414 | return "";
415 | } else {
416 | int pos = str.indexOf(separator);
417 | return pos == -1 ? "" : str.substring(pos + separator.length());
418 | }
419 | }
420 |
421 | public static String substringBeforeLast(String str, String separator) {
422 | if (!isEmpty(str) && !isEmpty(separator)) {
423 | int pos = str.lastIndexOf(separator);
424 | return pos == -1 ? str : str.substring(0, pos);
425 | } else {
426 | return str;
427 | }
428 | }
429 |
430 | public static String substringAfterLast(String str, String separator) {
431 | if (isEmpty(str)) {
432 | return str;
433 | } else if (isEmpty(separator)) {
434 | return "";
435 | } else {
436 | int pos = str.lastIndexOf(separator);
437 | return pos != -1 && pos != str.length() - separator.length() ? str.substring(pos + separator.length()) : "";
438 | }
439 | }
440 |
441 | public static String substringBetween(String str, String tag) {
442 | return substringBetween(str, tag, tag);
443 | }
444 |
445 | public static String substringBetween(String str, String open, String close) {
446 | if (str != null && open != null && close != null) {
447 | int start = str.indexOf(open);
448 | if (start != -1) {
449 | int end = str.indexOf(close, start + open.length());
450 | if (end != -1) {
451 | return str.substring(start + open.length(), end);
452 | }
453 | }
454 |
455 | return null;
456 | } else {
457 | return null;
458 | }
459 | }
460 |
461 | public static String[] split(String str) {
462 | return split(str, (String) null, -1);
463 | }
464 |
465 | public static String[] split(String str, char separatorChar) {
466 | return splitWorker(str, separatorChar, false);
467 | }
468 |
469 | public static String[] split(String str, String separatorChars) {
470 | return splitWorker(str, separatorChars, -1, false);
471 | }
472 |
473 | public static String[] split(String str, String separatorChars, int max) {
474 | return splitWorker(str, separatorChars, max, false);
475 | }
476 |
477 | public static String[] splitByWholeSeparator(String str, String separator) {
478 | return splitByWholeSeparatorWorker(str, separator, -1, false);
479 | }
480 |
481 | public static String[] splitByWholeSeparator(String str, String separator, int max) {
482 | return splitByWholeSeparatorWorker(str, separator, max, false);
483 | }
484 |
485 | public static String[] splitByWholeSeparatorPreserveAllTokens(String str, String separator) {
486 | return splitByWholeSeparatorWorker(str, separator, -1, true);
487 | }
488 |
489 | public static String[] splitByWholeSeparatorPreserveAllTokens(String str, String separator, int max) {
490 | return splitByWholeSeparatorWorker(str, separator, max, true);
491 | }
492 |
493 | private static String[] splitByWholeSeparatorWorker(String str, String separator, int max, boolean preserveAllTokens) {
494 | if (str == null) {
495 | return null;
496 | } else {
497 | int len = str.length();
498 | if (len == 0) {
499 | return new String[0];
500 | } else if (separator != null && !"".equals(separator)) {
501 | int separatorLength = separator.length();
502 | ArrayList substrings = new ArrayList();
503 | int numberOfSubstrings = 0;
504 | int beg = 0;
505 | int end = 0;
506 |
507 | while (end < len) {
508 | end = str.indexOf(separator, beg);
509 | if (end > -1) {
510 | if (end > beg) {
511 | ++numberOfSubstrings;
512 | if (numberOfSubstrings == max) {
513 | end = len;
514 | substrings.add(str.substring(beg));
515 | } else {
516 | substrings.add(str.substring(beg, end));
517 | beg = end + separatorLength;
518 | }
519 | } else {
520 | if (preserveAllTokens) {
521 | ++numberOfSubstrings;
522 | if (numberOfSubstrings == max) {
523 | end = len;
524 | substrings.add(str.substring(beg));
525 | } else {
526 | substrings.add("");
527 | }
528 | }
529 |
530 | beg = end + separatorLength;
531 | }
532 | } else {
533 | substrings.add(str.substring(beg));
534 | end = len;
535 | }
536 | }
537 |
538 | return (String[]) substrings.toArray(new String[substrings.size()]);
539 | } else {
540 | return splitWorker(str, (String) null, max, preserveAllTokens);
541 | }
542 | }
543 | }
544 |
545 | public static String[] splitPreserveAllTokens(String str) {
546 | return splitWorker(str, (String) null, -1, true);
547 | }
548 |
549 | public static String[] splitPreserveAllTokens(String str, char separatorChar) {
550 | return splitWorker(str, separatorChar, true);
551 | }
552 |
553 | private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) {
554 | if (str == null) {
555 | return null;
556 | } else {
557 | int len = str.length();
558 | if (len == 0) {
559 | return new String[0];
560 | } else {
561 | List list = new ArrayList();
562 | int i = 0;
563 | int start = 0;
564 | boolean match = false;
565 | boolean lastMatch = false;
566 |
567 | while (true) {
568 | while (i < len) {
569 | if (str.charAt(i) == separatorChar) {
570 | if (match || preserveAllTokens) {
571 | list.add(str.substring(start, i));
572 | match = false;
573 | lastMatch = true;
574 | }
575 |
576 | ++i;
577 | start = i;
578 | } else {
579 | lastMatch = false;
580 | match = true;
581 | ++i;
582 | }
583 | }
584 |
585 | if (match || preserveAllTokens && lastMatch) {
586 | list.add(str.substring(start, i));
587 | }
588 |
589 | return (String[]) list.toArray(new String[list.size()]);
590 | }
591 | }
592 | }
593 | }
594 |
595 | public static String[] splitPreserveAllTokens(String str, String separatorChars) {
596 | return splitWorker(str, separatorChars, -1, true);
597 | }
598 |
599 | public static String[] splitPreserveAllTokens(String str, String separatorChars, int max) {
600 | return splitWorker(str, separatorChars, max, true);
601 | }
602 |
603 | private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) {
604 | if (str == null) {
605 | return null;
606 | } else {
607 | int len = str.length();
608 | if (len == 0) {
609 | return new String[0];
610 | } else {
611 | List list = new ArrayList();
612 | int sizePlus1 = 1;
613 | int i = 0;
614 | int start = 0;
615 | boolean match = false;
616 | boolean lastMatch = false;
617 | if (separatorChars != null) {
618 | if (separatorChars.length() != 1) {
619 | label87:
620 | while (true) {
621 | while (true) {
622 | if (i >= len) {
623 | break label87;
624 | }
625 |
626 | if (separatorChars.indexOf(str.charAt(i)) >= 0) {
627 | if (match || preserveAllTokens) {
628 | lastMatch = true;
629 | if (sizePlus1++ == max) {
630 | i = len;
631 | lastMatch = false;
632 | }
633 |
634 | list.add(str.substring(start, i));
635 | match = false;
636 | }
637 |
638 | ++i;
639 | start = i;
640 | } else {
641 | lastMatch = false;
642 | match = true;
643 | ++i;
644 | }
645 | }
646 | }
647 | } else {
648 | char sep = separatorChars.charAt(0);
649 |
650 | label71:
651 | while (true) {
652 | while (true) {
653 | if (i >= len) {
654 | break label71;
655 | }
656 |
657 | if (str.charAt(i) == sep) {
658 | if (match || preserveAllTokens) {
659 | lastMatch = true;
660 | if (sizePlus1++ == max) {
661 | i = len;
662 | lastMatch = false;
663 | }
664 |
665 | list.add(str.substring(start, i));
666 | match = false;
667 | }
668 |
669 | ++i;
670 | start = i;
671 | } else {
672 | lastMatch = false;
673 | match = true;
674 | ++i;
675 | }
676 | }
677 | }
678 | }
679 | } else {
680 | label103:
681 | while (true) {
682 | while (true) {
683 | if (i >= len) {
684 | break label103;
685 | }
686 |
687 | if (Character.isWhitespace(str.charAt(i))) {
688 | if (match || preserveAllTokens) {
689 | lastMatch = true;
690 | if (sizePlus1++ == max) {
691 | i = len;
692 | lastMatch = false;
693 | }
694 |
695 | list.add(str.substring(start, i));
696 | match = false;
697 | }
698 |
699 | ++i;
700 | start = i;
701 | } else {
702 | lastMatch = false;
703 | match = true;
704 | ++i;
705 | }
706 | }
707 | }
708 | }
709 |
710 | if (match || preserveAllTokens && lastMatch) {
711 | list.add(str.substring(start, i));
712 | }
713 |
714 | return (String[]) list.toArray(new String[list.size()]);
715 | }
716 | }
717 | }
718 |
719 | public static String[] splitByCharacterType(String str) {
720 | return splitByCharacterType(str, false);
721 | }
722 |
723 | public static String[] splitByCharacterTypeCamelCase(String str) {
724 | return splitByCharacterType(str, true);
725 | }
726 |
727 | private static String[] splitByCharacterType(String str, boolean camelCase) {
728 | if (str == null) {
729 | return null;
730 | } else if (str.isEmpty()) {
731 | return new String[0];
732 | } else {
733 | char[] c = str.toCharArray();
734 | List list = new ArrayList();
735 | int tokenStart = 0;
736 | int currentType = Character.getType(c[tokenStart]);
737 |
738 | for (int pos = tokenStart + 1; pos < c.length; ++pos) {
739 | int type = Character.getType(c[pos]);
740 | if (type != currentType) {
741 | if (camelCase && type == 2 && currentType == 1) {
742 | int newTokenStart = pos - 1;
743 | if (newTokenStart != tokenStart) {
744 | list.add(new String(c, tokenStart, newTokenStart - tokenStart));
745 | tokenStart = newTokenStart;
746 | }
747 | } else {
748 | list.add(new String(c, tokenStart, pos - tokenStart));
749 | tokenStart = pos;
750 | }
751 |
752 | currentType = type;
753 | }
754 | }
755 |
756 | list.add(new String(c, tokenStart, c.length - tokenStart));
757 | return (String[]) list.toArray(new String[list.size()]);
758 | }
759 | }
760 |
761 | @SafeVarargs
762 | public static String join(T... elements) {
763 | return join((Object[]) elements, (String) null);
764 | }
765 |
766 | public static String join(Object[] array, char separator) {
767 | return array == null ? null : join((Object[]) array, separator, 0, array.length);
768 | }
769 |
770 | public static String join(long[] array, char separator) {
771 | return array == null ? null : join((long[]) array, separator, 0, array.length);
772 | }
773 |
774 | public static String join(int[] array, char separator) {
775 | return array == null ? null : join((int[]) array, separator, 0, array.length);
776 | }
777 |
778 | public static String join(short[] array, char separator) {
779 | return array == null ? null : join((short[]) array, separator, 0, array.length);
780 | }
781 |
782 | public static String join(byte[] array, char separator) {
783 | return array == null ? null : join((byte[]) array, separator, 0, array.length);
784 | }
785 |
786 | public static String join(char[] array, char separator) {
787 | return array == null ? null : join((char[]) array, separator, 0, array.length);
788 | }
789 |
790 | public static String join(float[] array, char separator) {
791 | return array == null ? null : join((float[]) array, separator, 0, array.length);
792 | }
793 |
794 | public static String join(double[] array, char separator) {
795 | return array == null ? null : join((double[]) array, separator, 0, array.length);
796 | }
797 |
798 | public static String join(Object[] array, char separator, int startIndex, int endIndex) {
799 | if (array == null) {
800 | return null;
801 | } else {
802 | int noOfItems = endIndex - startIndex;
803 | if (noOfItems <= 0) {
804 | return "";
805 | } else {
806 | StringBuilder buf = newStringBuilder(noOfItems);
807 |
808 | for (int i = startIndex; i < endIndex; ++i) {
809 | if (i > startIndex) {
810 | buf.append(separator);
811 | }
812 |
813 | if (array[i] != null) {
814 | buf.append(array[i]);
815 | }
816 | }
817 |
818 | return buf.toString();
819 | }
820 | }
821 | }
822 |
823 | public static String join(long[] array, char separator, int startIndex, int endIndex) {
824 | if (array == null) {
825 | return null;
826 | } else {
827 | int noOfItems = endIndex - startIndex;
828 | if (noOfItems <= 0) {
829 | return "";
830 | } else {
831 | StringBuilder buf = newStringBuilder(noOfItems);
832 |
833 | for (int i = startIndex; i < endIndex; ++i) {
834 | if (i > startIndex) {
835 | buf.append(separator);
836 | }
837 |
838 | buf.append(array[i]);
839 | }
840 |
841 | return buf.toString();
842 | }
843 | }
844 | }
845 |
846 | public static String join(int[] array, char separator, int startIndex, int endIndex) {
847 | if (array == null) {
848 | return null;
849 | } else {
850 | int noOfItems = endIndex - startIndex;
851 | if (noOfItems <= 0) {
852 | return "";
853 | } else {
854 | StringBuilder buf = newStringBuilder(noOfItems);
855 |
856 | for (int i = startIndex; i < endIndex; ++i) {
857 | if (i > startIndex) {
858 | buf.append(separator);
859 | }
860 |
861 | buf.append(array[i]);
862 | }
863 |
864 | return buf.toString();
865 | }
866 | }
867 | }
868 |
869 | public static String join(byte[] array, char separator, int startIndex, int endIndex) {
870 | if (array == null) {
871 | return null;
872 | } else {
873 | int noOfItems = endIndex - startIndex;
874 | if (noOfItems <= 0) {
875 | return "";
876 | } else {
877 | StringBuilder buf = newStringBuilder(noOfItems);
878 |
879 | for (int i = startIndex; i < endIndex; ++i) {
880 | if (i > startIndex) {
881 | buf.append(separator);
882 | }
883 |
884 | buf.append(array[i]);
885 | }
886 |
887 | return buf.toString();
888 | }
889 | }
890 | }
891 |
892 | public static String join(short[] array, char separator, int startIndex, int endIndex) {
893 | if (array == null) {
894 | return null;
895 | } else {
896 | int noOfItems = endIndex - startIndex;
897 | if (noOfItems <= 0) {
898 | return "";
899 | } else {
900 | StringBuilder buf = newStringBuilder(noOfItems);
901 |
902 | for (int i = startIndex; i < endIndex; ++i) {
903 | if (i > startIndex) {
904 | buf.append(separator);
905 | }
906 |
907 | buf.append(array[i]);
908 | }
909 |
910 | return buf.toString();
911 | }
912 | }
913 | }
914 |
915 | public static String join(char[] array, char separator, int startIndex, int endIndex) {
916 | if (array == null) {
917 | return null;
918 | } else {
919 | int noOfItems = endIndex - startIndex;
920 | if (noOfItems <= 0) {
921 | return "";
922 | } else {
923 | StringBuilder buf = newStringBuilder(noOfItems);
924 |
925 | for (int i = startIndex; i < endIndex; ++i) {
926 | if (i > startIndex) {
927 | buf.append(separator);
928 | }
929 |
930 | buf.append(array[i]);
931 | }
932 |
933 | return buf.toString();
934 | }
935 | }
936 | }
937 |
938 | public static String join(double[] array, char separator, int startIndex, int endIndex) {
939 | if (array == null) {
940 | return null;
941 | } else {
942 | int noOfItems = endIndex - startIndex;
943 | if (noOfItems <= 0) {
944 | return "";
945 | } else {
946 | StringBuilder buf = newStringBuilder(noOfItems);
947 |
948 | for (int i = startIndex; i < endIndex; ++i) {
949 | if (i > startIndex) {
950 | buf.append(separator);
951 | }
952 |
953 | buf.append(array[i]);
954 | }
955 |
956 | return buf.toString();
957 | }
958 | }
959 | }
960 |
961 | public static String join(float[] array, char separator, int startIndex, int endIndex) {
962 | if (array == null) {
963 | return null;
964 | } else {
965 | int noOfItems = endIndex - startIndex;
966 | if (noOfItems <= 0) {
967 | return "";
968 | } else {
969 | StringBuilder buf = newStringBuilder(noOfItems);
970 |
971 | for (int i = startIndex; i < endIndex; ++i) {
972 | if (i > startIndex) {
973 | buf.append(separator);
974 | }
975 |
976 | buf.append(array[i]);
977 | }
978 |
979 | return buf.toString();
980 | }
981 | }
982 | }
983 |
984 | public static String join(Object[] array, String separator) {
985 | return array == null ? null : join((Object[]) array, separator, 0, array.length);
986 | }
987 |
988 | public static String join(Object[] array, String separator, int startIndex, int endIndex) {
989 | if (array == null) {
990 | return null;
991 | } else {
992 | if (separator == null) {
993 | separator = "";
994 | }
995 |
996 | int noOfItems = endIndex - startIndex;
997 | if (noOfItems <= 0) {
998 | return "";
999 | } else {
1000 | StringBuilder buf = newStringBuilder(noOfItems);
1001 |
1002 | for (int i = startIndex; i < endIndex; ++i) {
1003 | if (i > startIndex) {
1004 | buf.append(separator);
1005 | }
1006 |
1007 | if (array[i] != null) {
1008 | buf.append(array[i]);
1009 | }
1010 | }
1011 |
1012 | return buf.toString();
1013 | }
1014 | }
1015 | }
1016 |
1017 | public static String join(Iterator> iterator, char separator) {
1018 | if (iterator == null) {
1019 | return null;
1020 | } else if (!iterator.hasNext()) {
1021 | return "";
1022 | } else {
1023 | Object first = iterator.next();
1024 | if (!iterator.hasNext()) {
1025 | return Objects.toString(first, "");
1026 | } else {
1027 | StringBuilder buf = new StringBuilder(256);
1028 | if (first != null) {
1029 | buf.append(first);
1030 | }
1031 |
1032 | while (iterator.hasNext()) {
1033 | buf.append(separator);
1034 | Object obj = iterator.next();
1035 | if (obj != null) {
1036 | buf.append(obj);
1037 | }
1038 | }
1039 |
1040 | return buf.toString();
1041 | }
1042 | }
1043 | }
1044 |
1045 | public static String join(Iterator> iterator, String separator) {
1046 | if (iterator == null) {
1047 | return null;
1048 | } else if (!iterator.hasNext()) {
1049 | return "";
1050 | } else {
1051 | Object first = iterator.next();
1052 | if (!iterator.hasNext()) {
1053 | return Objects.toString(first, "");
1054 | } else {
1055 | StringBuilder buf = new StringBuilder(256);
1056 | if (first != null) {
1057 | buf.append(first);
1058 | }
1059 |
1060 | while (iterator.hasNext()) {
1061 | if (separator != null) {
1062 | buf.append(separator);
1063 | }
1064 |
1065 | Object obj = iterator.next();
1066 | if (obj != null) {
1067 | buf.append(obj);
1068 | }
1069 | }
1070 |
1071 | return buf.toString();
1072 | }
1073 | }
1074 | }
1075 |
1076 | public static String join(Iterable> iterable, char separator) {
1077 | return iterable == null ? null : join(iterable.iterator(), separator);
1078 | }
1079 |
1080 | public static String join(Iterable> iterable, String separator) {
1081 | return iterable == null ? null : join(iterable.iterator(), separator);
1082 | }
1083 |
1084 | public static String join(List> list, char separator, int startIndex, int endIndex) {
1085 | if (list == null) {
1086 | return null;
1087 | } else {
1088 | int noOfItems = endIndex - startIndex;
1089 | if (noOfItems <= 0) {
1090 | return "";
1091 | } else {
1092 | List> subList = list.subList(startIndex, endIndex);
1093 | return join(subList.iterator(), separator);
1094 | }
1095 | }
1096 | }
1097 |
1098 | public static String join(List> list, String separator, int startIndex, int endIndex) {
1099 | if (list == null) {
1100 | return null;
1101 | } else {
1102 | int noOfItems = endIndex - startIndex;
1103 | if (noOfItems <= 0) {
1104 | return "";
1105 | } else {
1106 | List> subList = list.subList(startIndex, endIndex);
1107 | return join(subList.iterator(), separator);
1108 | }
1109 | }
1110 | }
1111 |
1112 | public static String joinWith(String separator, Object... objects) {
1113 | if (objects == null) {
1114 | throw new IllegalArgumentException("Object varargs must not be null");
1115 | } else {
1116 | String sanitizedSeparator = defaultString(separator);
1117 | StringBuilder result = new StringBuilder();
1118 | Iterator