├── .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 iterator = Arrays.asList(objects).iterator(); 1119 | 1120 | while (iterator.hasNext()) { 1121 | String value = Objects.toString(iterator.next(), ""); 1122 | result.append(value); 1123 | if (iterator.hasNext()) { 1124 | result.append(sanitizedSeparator); 1125 | } 1126 | } 1127 | 1128 | return result.toString(); 1129 | } 1130 | } 1131 | 1132 | public static String deleteWhitespace(String str) { 1133 | if (isEmpty(str)) { 1134 | return str; 1135 | } else { 1136 | int sz = str.length(); 1137 | char[] chs = new char[sz]; 1138 | int count = 0; 1139 | 1140 | for (int i = 0; i < sz; ++i) { 1141 | if (!Character.isWhitespace(str.charAt(i))) { 1142 | chs[count++] = str.charAt(i); 1143 | } 1144 | } 1145 | 1146 | if (count == sz) { 1147 | return str; 1148 | } else { 1149 | return new String(chs, 0, count); 1150 | } 1151 | } 1152 | } 1153 | 1154 | public static String removeStart(String str, String remove) { 1155 | if (!isEmpty(str) && !isEmpty(remove)) { 1156 | return str.startsWith(remove) ? str.substring(remove.length()) : str; 1157 | } else { 1158 | return str; 1159 | } 1160 | } 1161 | 1162 | public static String removeEnd(String str, String remove) { 1163 | if (!isEmpty(str) && !isEmpty(remove)) { 1164 | return str.endsWith(remove) ? str.substring(0, str.length() - remove.length()) : str; 1165 | } else { 1166 | return str; 1167 | } 1168 | } 1169 | 1170 | public static String remove(String str, String remove) { 1171 | return !isEmpty(str) && !isEmpty(remove) ? replace(str, remove, "", -1) : str; 1172 | } 1173 | 1174 | public static String removeIgnoreCase(String str, String remove) { 1175 | return !isEmpty(str) && !isEmpty(remove) ? replaceIgnoreCase(str, remove, "", -1) : str; 1176 | } 1177 | 1178 | public static String remove(String str, char remove) { 1179 | if (!isEmpty(str) && str.indexOf(remove) != -1) { 1180 | char[] chars = str.toCharArray(); 1181 | int pos = 0; 1182 | 1183 | for (int i = 0; i < chars.length; ++i) { 1184 | if (chars[i] != remove) { 1185 | chars[pos++] = chars[i]; 1186 | } 1187 | } 1188 | 1189 | return new String(chars, 0, pos); 1190 | } else { 1191 | return str; 1192 | } 1193 | } 1194 | 1195 | /** 1196 | * @deprecated 1197 | */ 1198 | @Deprecated 1199 | public static String removeAll(String text, String regex) { 1200 | return RegExUtils.removeAll(text, regex); 1201 | } 1202 | 1203 | /** 1204 | * @deprecated 1205 | */ 1206 | @Deprecated 1207 | public static String removeFirst(String text, String regex) { 1208 | return replaceFirst(text, regex, ""); 1209 | } 1210 | 1211 | public static String replaceOnce(String text, String searchString, String replacement) { 1212 | return replace(text, searchString, replacement, 1); 1213 | } 1214 | 1215 | public static String replaceOnceIgnoreCase(String text, String searchString, String replacement) { 1216 | return replaceIgnoreCase(text, searchString, replacement, 1); 1217 | } 1218 | 1219 | /** 1220 | * @deprecated 1221 | */ 1222 | @Deprecated 1223 | public static String replacePattern(String source, String regex, String replacement) { 1224 | return RegExUtils.replacePattern(source, regex, replacement); 1225 | } 1226 | 1227 | /** 1228 | * @deprecated 1229 | */ 1230 | @Deprecated 1231 | public static String removePattern(String source, String regex) { 1232 | return RegExUtils.removePattern(source, regex); 1233 | } 1234 | 1235 | /** 1236 | * @deprecated 1237 | */ 1238 | @Deprecated 1239 | public static String replaceAll(String text, String regex, String replacement) { 1240 | return RegExUtils.replaceAll(text, regex, replacement); 1241 | } 1242 | 1243 | /** 1244 | * @deprecated 1245 | */ 1246 | @Deprecated 1247 | public static String replaceFirst(String text, String regex, String replacement) { 1248 | return RegExUtils.replaceFirst(text, regex, replacement); 1249 | } 1250 | 1251 | public static String replace(String text, String searchString, String replacement) { 1252 | return replace(text, searchString, replacement, -1); 1253 | } 1254 | 1255 | public static String replaceIgnoreCase(String text, String searchString, String replacement) { 1256 | return replaceIgnoreCase(text, searchString, replacement, -1); 1257 | } 1258 | 1259 | public static String replace(String text, String searchString, String replacement, int max) { 1260 | return replace(text, searchString, replacement, max, false); 1261 | } 1262 | 1263 | private static String replace(String text, String searchString, String replacement, int max, boolean ignoreCase) { 1264 | if (!isEmpty(text) && !isEmpty(searchString) && replacement != null && max != 0) { 1265 | String searchText = text; 1266 | if (ignoreCase) { 1267 | searchText = text.toLowerCase(); 1268 | searchString = searchString.toLowerCase(); 1269 | } 1270 | 1271 | int start = 0; 1272 | int end = searchText.indexOf(searchString, start); 1273 | if (end == -1) { 1274 | return text; 1275 | } else { 1276 | int replLength = searchString.length(); 1277 | int increase = replacement.length() - replLength; 1278 | increase = increase < 0 ? 0 : increase; 1279 | increase *= max < 0 ? 16 : (max > 64 ? 64 : max); 1280 | 1281 | StringBuilder buf; 1282 | for (buf = new StringBuilder(text.length() + increase); end != -1; end = searchText.indexOf(searchString, start)) { 1283 | buf.append(text, start, end).append(replacement); 1284 | start = end + replLength; 1285 | --max; 1286 | if (max == 0) { 1287 | break; 1288 | } 1289 | } 1290 | 1291 | buf.append(text, start, text.length()); 1292 | return buf.toString(); 1293 | } 1294 | } else { 1295 | return text; 1296 | } 1297 | } 1298 | 1299 | public static String replaceIgnoreCase(String text, String searchString, String replacement, int max) { 1300 | return replace(text, searchString, replacement, max, true); 1301 | } 1302 | 1303 | public static String replaceChars(String str, char searchChar, char replaceChar) { 1304 | return str == null ? null : str.replace(searchChar, replaceChar); 1305 | } 1306 | 1307 | public static String replaceChars(String str, String searchChars, String replaceChars) { 1308 | if (!isEmpty(str) && !isEmpty(searchChars)) { 1309 | if (replaceChars == null) { 1310 | replaceChars = ""; 1311 | } 1312 | 1313 | boolean modified = false; 1314 | int replaceCharsLength = replaceChars.length(); 1315 | int strLength = str.length(); 1316 | StringBuilder buf = new StringBuilder(strLength); 1317 | 1318 | for (int i = 0; i < strLength; ++i) { 1319 | char ch = str.charAt(i); 1320 | int index = searchChars.indexOf(ch); 1321 | if (index >= 0) { 1322 | modified = true; 1323 | if (index < replaceCharsLength) { 1324 | buf.append(replaceChars.charAt(index)); 1325 | } 1326 | } else { 1327 | buf.append(ch); 1328 | } 1329 | } 1330 | 1331 | if (modified) { 1332 | return buf.toString(); 1333 | } else { 1334 | return str; 1335 | } 1336 | } else { 1337 | return str; 1338 | } 1339 | } 1340 | 1341 | public static String overlay(String str, String overlay, int start, int end) { 1342 | if (str == null) { 1343 | return null; 1344 | } else { 1345 | if (overlay == null) { 1346 | overlay = ""; 1347 | } 1348 | 1349 | int len = str.length(); 1350 | if (start < 0) { 1351 | start = 0; 1352 | } 1353 | 1354 | if (start > len) { 1355 | start = len; 1356 | } 1357 | 1358 | if (end < 0) { 1359 | end = 0; 1360 | } 1361 | 1362 | if (end > len) { 1363 | end = len; 1364 | } 1365 | 1366 | if (start > end) { 1367 | int temp = start; 1368 | start = end; 1369 | end = temp; 1370 | } 1371 | 1372 | return str.substring(0, start) + overlay + str.substring(end); 1373 | } 1374 | } 1375 | 1376 | public static String chomp(String str) { 1377 | if (isEmpty(str)) { 1378 | return str; 1379 | } else if (str.length() == 1) { 1380 | char ch = str.charAt(0); 1381 | return ch != '\r' && ch != '\n' ? str : ""; 1382 | } else { 1383 | int lastIdx = str.length() - 1; 1384 | char last = str.charAt(lastIdx); 1385 | if (last == '\n') { 1386 | if (str.charAt(lastIdx - 1) == '\r') { 1387 | --lastIdx; 1388 | } 1389 | } else if (last != '\r') { 1390 | ++lastIdx; 1391 | } 1392 | 1393 | return str.substring(0, lastIdx); 1394 | } 1395 | } 1396 | 1397 | /** 1398 | * @deprecated 1399 | */ 1400 | @Deprecated 1401 | public static String chomp(String str, String separator) { 1402 | return removeEnd(str, separator); 1403 | } 1404 | 1405 | public static String chop(String str) { 1406 | if (str == null) { 1407 | return null; 1408 | } else { 1409 | int strLen = str.length(); 1410 | if (strLen < 2) { 1411 | return ""; 1412 | } else { 1413 | int lastIdx = strLen - 1; 1414 | String ret = str.substring(0, lastIdx); 1415 | char last = str.charAt(lastIdx); 1416 | return last == '\n' && ret.charAt(lastIdx - 1) == '\r' ? ret.substring(0, lastIdx - 1) : ret; 1417 | } 1418 | } 1419 | } 1420 | 1421 | public static String repeat(String str, int repeat) { 1422 | if (str == null) { 1423 | return null; 1424 | } else if (repeat <= 0) { 1425 | return ""; 1426 | } else { 1427 | int inputLength = str.length(); 1428 | if (repeat != 1 && inputLength != 0) { 1429 | if (inputLength == 1 && repeat <= 8192) { 1430 | return repeat(str.charAt(0), repeat); 1431 | } else { 1432 | int outputLength = inputLength * repeat; 1433 | switch (inputLength) { 1434 | case 1: 1435 | return repeat(str.charAt(0), repeat); 1436 | case 2: 1437 | char ch0 = str.charAt(0); 1438 | char ch1 = str.charAt(1); 1439 | char[] output2 = new char[outputLength]; 1440 | 1441 | for (int i = repeat * 2 - 2; i >= 0; --i) { 1442 | output2[i] = ch0; 1443 | output2[i + 1] = ch1; 1444 | --i; 1445 | } 1446 | 1447 | return new String(output2); 1448 | default: 1449 | StringBuilder buf = new StringBuilder(outputLength); 1450 | 1451 | for (int i = 0; i < repeat; ++i) { 1452 | buf.append(str); 1453 | } 1454 | 1455 | return buf.toString(); 1456 | } 1457 | } 1458 | } else { 1459 | return str; 1460 | } 1461 | } 1462 | } 1463 | 1464 | public static String repeat(String str, String separator, int repeat) { 1465 | if (str != null && separator != null) { 1466 | String result = repeat(str + separator, repeat); 1467 | return removeEnd(result, separator); 1468 | } else { 1469 | return repeat(str, repeat); 1470 | } 1471 | } 1472 | 1473 | public static String repeat(char ch, int repeat) { 1474 | if (repeat <= 0) { 1475 | return ""; 1476 | } else { 1477 | char[] buf = new char[repeat]; 1478 | 1479 | for (int i = repeat - 1; i >= 0; --i) { 1480 | buf[i] = ch; 1481 | } 1482 | 1483 | return new String(buf); 1484 | } 1485 | } 1486 | 1487 | public static String rightPad(String str, int size) { 1488 | return rightPad(str, size, ' '); 1489 | } 1490 | 1491 | public static String rightPad(String str, int size, char padChar) { 1492 | if (str == null) { 1493 | return null; 1494 | } else { 1495 | int pads = size - str.length(); 1496 | if (pads <= 0) { 1497 | return str; 1498 | } else { 1499 | return pads > 8192 ? rightPad(str, size, String.valueOf(padChar)) : str.concat(repeat(padChar, pads)); 1500 | } 1501 | } 1502 | } 1503 | 1504 | public static String rightPad(String str, int size, String padStr) { 1505 | if (str == null) { 1506 | return null; 1507 | } else { 1508 | if (isEmpty(padStr)) { 1509 | padStr = " "; 1510 | } 1511 | 1512 | int padLen = padStr.length(); 1513 | int strLen = str.length(); 1514 | int pads = size - strLen; 1515 | if (pads <= 0) { 1516 | return str; 1517 | } else if (padLen == 1 && pads <= 8192) { 1518 | return rightPad(str, size, padStr.charAt(0)); 1519 | } else if (pads == padLen) { 1520 | return str.concat(padStr); 1521 | } else if (pads < padLen) { 1522 | return str.concat(padStr.substring(0, pads)); 1523 | } else { 1524 | char[] padding = new char[pads]; 1525 | char[] padChars = padStr.toCharArray(); 1526 | 1527 | for (int i = 0; i < pads; ++i) { 1528 | padding[i] = padChars[i % padLen]; 1529 | } 1530 | 1531 | return str.concat(new String(padding)); 1532 | } 1533 | } 1534 | } 1535 | 1536 | public static String leftPad(String str, int size) { 1537 | return leftPad(str, size, ' '); 1538 | } 1539 | 1540 | public static String leftPad(String str, int size, char padChar) { 1541 | if (str == null) { 1542 | return null; 1543 | } else { 1544 | int pads = size - str.length(); 1545 | if (pads <= 0) { 1546 | return str; 1547 | } else { 1548 | return pads > 8192 ? leftPad(str, size, String.valueOf(padChar)) : repeat(padChar, pads).concat(str); 1549 | } 1550 | } 1551 | } 1552 | 1553 | public static String leftPad(String str, int size, String padStr) { 1554 | if (str == null) { 1555 | return null; 1556 | } else { 1557 | if (isEmpty(padStr)) { 1558 | padStr = " "; 1559 | } 1560 | 1561 | int padLen = padStr.length(); 1562 | int strLen = str.length(); 1563 | int pads = size - strLen; 1564 | if (pads <= 0) { 1565 | return str; 1566 | } else if (padLen == 1 && pads <= 8192) { 1567 | return leftPad(str, size, padStr.charAt(0)); 1568 | } else if (pads == padLen) { 1569 | return padStr.concat(str); 1570 | } else if (pads < padLen) { 1571 | return padStr.substring(0, pads).concat(str); 1572 | } else { 1573 | char[] padding = new char[pads]; 1574 | char[] padChars = padStr.toCharArray(); 1575 | 1576 | for (int i = 0; i < pads; ++i) { 1577 | padding[i] = padChars[i % padLen]; 1578 | } 1579 | 1580 | return (new String(padding)).concat(str); 1581 | } 1582 | } 1583 | } 1584 | 1585 | public static int length(CharSequence cs) { 1586 | return cs == null ? 0 : cs.length(); 1587 | } 1588 | 1589 | public static String center(String str, int size) { 1590 | return center(str, size, ' '); 1591 | } 1592 | 1593 | public static String center(String str, int size, char padChar) { 1594 | if (str != null && size > 0) { 1595 | int strLen = str.length(); 1596 | int pads = size - strLen; 1597 | if (pads <= 0) { 1598 | return str; 1599 | } else { 1600 | str = leftPad(str, strLen + pads / 2, padChar); 1601 | str = rightPad(str, size, padChar); 1602 | return str; 1603 | } 1604 | } else { 1605 | return str; 1606 | } 1607 | } 1608 | 1609 | public static String center(String str, int size, String padStr) { 1610 | if (str != null && size > 0) { 1611 | if (isEmpty(padStr)) { 1612 | padStr = " "; 1613 | } 1614 | 1615 | int strLen = str.length(); 1616 | int pads = size - strLen; 1617 | if (pads <= 0) { 1618 | return str; 1619 | } else { 1620 | str = leftPad(str, strLen + pads / 2, padStr); 1621 | str = rightPad(str, size, padStr); 1622 | return str; 1623 | } 1624 | } else { 1625 | return str; 1626 | } 1627 | } 1628 | 1629 | public static String upperCase(String str) { 1630 | return str == null ? null : str.toUpperCase(); 1631 | } 1632 | 1633 | public static String upperCase(String str, Locale locale) { 1634 | return str == null ? null : str.toUpperCase(locale); 1635 | } 1636 | 1637 | public static String lowerCase(String str) { 1638 | return str == null ? null : str.toLowerCase(); 1639 | } 1640 | 1641 | public static String lowerCase(String str, Locale locale) { 1642 | return str == null ? null : str.toLowerCase(locale); 1643 | } 1644 | 1645 | public static String capitalize(String str) { 1646 | int strLen; 1647 | if (str != null && (strLen = str.length()) != 0) { 1648 | int firstCodepoint = str.codePointAt(0); 1649 | int newCodePoint = Character.toTitleCase(firstCodepoint); 1650 | if (firstCodepoint == newCodePoint) { 1651 | return str; 1652 | } else { 1653 | int[] newCodePoints = new int[strLen]; 1654 | int outOffset = 0; 1655 | newCodePoints[outOffset++] = newCodePoint; 1656 | 1657 | int codepoint; 1658 | for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen; inOffset += Character.charCount(codepoint)) { 1659 | codepoint = str.codePointAt(inOffset); 1660 | newCodePoints[outOffset++] = codepoint; 1661 | } 1662 | 1663 | return new String(newCodePoints, 0, outOffset); 1664 | } 1665 | } else { 1666 | return str; 1667 | } 1668 | } 1669 | 1670 | public static String uncapitalize(String str) { 1671 | int strLen; 1672 | if (str != null && (strLen = str.length()) != 0) { 1673 | int firstCodepoint = str.codePointAt(0); 1674 | int newCodePoint = Character.toLowerCase(firstCodepoint); 1675 | if (firstCodepoint == newCodePoint) { 1676 | return str; 1677 | } else { 1678 | int[] newCodePoints = new int[strLen]; 1679 | int outOffset = 0; 1680 | newCodePoints[outOffset++] = newCodePoint; 1681 | 1682 | int codepoint; 1683 | for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen; inOffset += Character.charCount(codepoint)) { 1684 | codepoint = str.codePointAt(inOffset); 1685 | newCodePoints[outOffset++] = codepoint; 1686 | } 1687 | 1688 | return new String(newCodePoints, 0, outOffset); 1689 | } 1690 | } else { 1691 | return str; 1692 | } 1693 | } 1694 | 1695 | public static String swapCase(String str) { 1696 | if (isEmpty(str)) { 1697 | return str; 1698 | } else { 1699 | int strLen = str.length(); 1700 | int[] newCodePoints = new int[strLen]; 1701 | int outOffset = 0; 1702 | 1703 | int newCodePoint; 1704 | for (int i = 0; i < strLen; i += Character.charCount(newCodePoint)) { 1705 | int oldCodepoint = str.codePointAt(i); 1706 | if (Character.isUpperCase(oldCodepoint)) { 1707 | newCodePoint = Character.toLowerCase(oldCodepoint); 1708 | } else if (Character.isTitleCase(oldCodepoint)) { 1709 | newCodePoint = Character.toLowerCase(oldCodepoint); 1710 | } else if (Character.isLowerCase(oldCodepoint)) { 1711 | newCodePoint = Character.toUpperCase(oldCodepoint); 1712 | } else { 1713 | newCodePoint = oldCodepoint; 1714 | } 1715 | 1716 | newCodePoints[outOffset++] = newCodePoint; 1717 | } 1718 | 1719 | return new String(newCodePoints, 0, outOffset); 1720 | } 1721 | } 1722 | 1723 | public static int countMatches(CharSequence str, char ch) { 1724 | if (isEmpty(str)) { 1725 | return 0; 1726 | } else { 1727 | int count = 0; 1728 | 1729 | for (int i = 0; i < str.length(); ++i) { 1730 | if (ch == str.charAt(i)) { 1731 | ++count; 1732 | } 1733 | } 1734 | 1735 | return count; 1736 | } 1737 | } 1738 | 1739 | public static boolean isAlpha(CharSequence cs) { 1740 | if (isEmpty(cs)) { 1741 | return false; 1742 | } else { 1743 | int sz = cs.length(); 1744 | 1745 | for (int i = 0; i < sz; ++i) { 1746 | if (!Character.isLetter(cs.charAt(i))) { 1747 | return false; 1748 | } 1749 | } 1750 | 1751 | return true; 1752 | } 1753 | } 1754 | 1755 | public static boolean isAlphaSpace(CharSequence cs) { 1756 | if (cs == null) { 1757 | return false; 1758 | } else { 1759 | int sz = cs.length(); 1760 | 1761 | for (int i = 0; i < sz; ++i) { 1762 | if (!Character.isLetter(cs.charAt(i)) && cs.charAt(i) != ' ') { 1763 | return false; 1764 | } 1765 | } 1766 | 1767 | return true; 1768 | } 1769 | } 1770 | 1771 | public static boolean isAlphanumeric(CharSequence cs) { 1772 | if (isEmpty(cs)) { 1773 | return false; 1774 | } else { 1775 | int sz = cs.length(); 1776 | 1777 | for (int i = 0; i < sz; ++i) { 1778 | if (!Character.isLetterOrDigit(cs.charAt(i))) { 1779 | return false; 1780 | } 1781 | } 1782 | 1783 | return true; 1784 | } 1785 | } 1786 | 1787 | public static boolean isAlphanumericSpace(CharSequence cs) { 1788 | if (cs == null) { 1789 | return false; 1790 | } else { 1791 | int sz = cs.length(); 1792 | 1793 | for (int i = 0; i < sz; ++i) { 1794 | if (!Character.isLetterOrDigit(cs.charAt(i)) && cs.charAt(i) != ' ') { 1795 | return false; 1796 | } 1797 | } 1798 | 1799 | return true; 1800 | } 1801 | } 1802 | 1803 | public static boolean isAsciiPrintable(CharSequence cs) { 1804 | if (cs == null) { 1805 | return false; 1806 | } else { 1807 | int sz = cs.length(); 1808 | 1809 | for (int i = 0; i < sz; ++i) { 1810 | if (!CharUtils.isAsciiPrintable(cs.charAt(i))) { 1811 | return false; 1812 | } 1813 | } 1814 | 1815 | return true; 1816 | } 1817 | } 1818 | 1819 | public static boolean isNumeric(CharSequence cs) { 1820 | if (isEmpty(cs)) { 1821 | return false; 1822 | } else { 1823 | int sz = cs.length(); 1824 | 1825 | for (int i = 0; i < sz; ++i) { 1826 | if (!Character.isDigit(cs.charAt(i))) { 1827 | return false; 1828 | } 1829 | } 1830 | 1831 | return true; 1832 | } 1833 | } 1834 | 1835 | public static boolean isNumericSpace(CharSequence cs) { 1836 | if (cs == null) { 1837 | return false; 1838 | } else { 1839 | int sz = cs.length(); 1840 | 1841 | for (int i = 0; i < sz; ++i) { 1842 | if (!Character.isDigit(cs.charAt(i)) && cs.charAt(i) != ' ') { 1843 | return false; 1844 | } 1845 | } 1846 | 1847 | return true; 1848 | } 1849 | } 1850 | 1851 | public static String getDigits(String str) { 1852 | if (isEmpty(str)) { 1853 | return str; 1854 | } else { 1855 | int sz = str.length(); 1856 | StringBuilder strDigits = new StringBuilder(sz); 1857 | 1858 | for (int i = 0; i < sz; ++i) { 1859 | char tempChar = str.charAt(i); 1860 | if (Character.isDigit(tempChar)) { 1861 | strDigits.append(tempChar); 1862 | } 1863 | } 1864 | 1865 | return strDigits.toString(); 1866 | } 1867 | } 1868 | 1869 | public static boolean isWhitespace(CharSequence cs) { 1870 | if (cs == null) { 1871 | return false; 1872 | } else { 1873 | int sz = cs.length(); 1874 | 1875 | for (int i = 0; i < sz; ++i) { 1876 | if (!Character.isWhitespace(cs.charAt(i))) { 1877 | return false; 1878 | } 1879 | } 1880 | 1881 | return true; 1882 | } 1883 | } 1884 | 1885 | public static boolean isAllLowerCase(CharSequence cs) { 1886 | if (cs != null && !isEmpty(cs)) { 1887 | int sz = cs.length(); 1888 | 1889 | for (int i = 0; i < sz; ++i) { 1890 | if (!Character.isLowerCase(cs.charAt(i))) { 1891 | return false; 1892 | } 1893 | } 1894 | 1895 | return true; 1896 | } else { 1897 | return false; 1898 | } 1899 | } 1900 | 1901 | public static boolean isAllUpperCase(CharSequence cs) { 1902 | if (cs != null && !isEmpty(cs)) { 1903 | int sz = cs.length(); 1904 | 1905 | for (int i = 0; i < sz; ++i) { 1906 | if (!Character.isUpperCase(cs.charAt(i))) { 1907 | return false; 1908 | } 1909 | } 1910 | 1911 | return true; 1912 | } else { 1913 | return false; 1914 | } 1915 | } 1916 | 1917 | public static boolean isMixedCase(CharSequence cs) { 1918 | if (!isEmpty(cs) && cs.length() != 1) { 1919 | boolean containsUppercase = false; 1920 | boolean containsLowercase = false; 1921 | int sz = cs.length(); 1922 | 1923 | for (int i = 0; i < sz; ++i) { 1924 | if (containsUppercase && containsLowercase) { 1925 | return true; 1926 | } 1927 | 1928 | if (Character.isUpperCase(cs.charAt(i))) { 1929 | containsUppercase = true; 1930 | } else if (Character.isLowerCase(cs.charAt(i))) { 1931 | containsLowercase = true; 1932 | } 1933 | } 1934 | 1935 | return containsUppercase && containsLowercase; 1936 | } else { 1937 | return false; 1938 | } 1939 | } 1940 | 1941 | public static String defaultString(String str) { 1942 | return defaultString(str, ""); 1943 | } 1944 | 1945 | public static String defaultString(String str, String defaultStr) { 1946 | return str == null ? defaultStr : str; 1947 | } 1948 | 1949 | public static T defaultIfBlank(T str, T defaultStr) { 1950 | return isBlank(str) ? defaultStr : str; 1951 | } 1952 | 1953 | public static T defaultIfEmpty(T str, T defaultStr) { 1954 | return isEmpty(str) ? defaultStr : str; 1955 | } 1956 | 1957 | public static String rotate(String str, int shift) { 1958 | if (str == null) { 1959 | return null; 1960 | } else { 1961 | int strLen = str.length(); 1962 | if (shift != 0 && strLen != 0 && shift % strLen != 0) { 1963 | StringBuilder builder = new StringBuilder(strLen); 1964 | int offset = -(shift % strLen); 1965 | builder.append(substring(str, offset)); 1966 | builder.append(substring(str, 0, offset)); 1967 | return builder.toString(); 1968 | } else { 1969 | return str; 1970 | } 1971 | } 1972 | } 1973 | 1974 | public static String reverse(String str) { 1975 | return str == null ? null : (new StringBuilder(str)).reverse().toString(); 1976 | } 1977 | 1978 | public static String abbreviate(String str, int maxWidth) { 1979 | String defaultAbbrevMarker = "..."; 1980 | return abbreviate(str, "...", 0, maxWidth); 1981 | } 1982 | 1983 | public static String abbreviate(String str, int offset, int maxWidth) { 1984 | String defaultAbbrevMarker = "..."; 1985 | return abbreviate(str, "...", offset, maxWidth); 1986 | } 1987 | 1988 | public static String abbreviate(String str, String abbrevMarker, int maxWidth) { 1989 | return abbreviate(str, abbrevMarker, 0, maxWidth); 1990 | } 1991 | 1992 | public static String abbreviate(String str, String abbrevMarker, int offset, int maxWidth) { 1993 | if (!isEmpty(str) && !isEmpty(abbrevMarker)) { 1994 | int abbrevMarkerLength = abbrevMarker.length(); 1995 | int minAbbrevWidth = abbrevMarkerLength + 1; 1996 | int minAbbrevWidthOffset = abbrevMarkerLength + abbrevMarkerLength + 1; 1997 | if (maxWidth < minAbbrevWidth) { 1998 | throw new IllegalArgumentException(String.format("Minimum abbreviation width is %d", minAbbrevWidth)); 1999 | } else if (str.length() <= maxWidth) { 2000 | return str; 2001 | } else { 2002 | if (offset > str.length()) { 2003 | offset = str.length(); 2004 | } 2005 | 2006 | if (str.length() - offset < maxWidth - abbrevMarkerLength) { 2007 | offset = str.length() - (maxWidth - abbrevMarkerLength); 2008 | } 2009 | 2010 | if (offset <= abbrevMarkerLength + 1) { 2011 | return str.substring(0, maxWidth - abbrevMarkerLength) + abbrevMarker; 2012 | } else if (maxWidth < minAbbrevWidthOffset) { 2013 | throw new IllegalArgumentException(String.format("Minimum abbreviation width with offset is %d", minAbbrevWidthOffset)); 2014 | } else { 2015 | return offset + maxWidth - abbrevMarkerLength < str.length() ? abbrevMarker + abbreviate(str.substring(offset), abbrevMarker, maxWidth - abbrevMarkerLength) : abbrevMarker + str.substring(str.length() - (maxWidth - abbrevMarkerLength)); 2016 | } 2017 | } 2018 | } else { 2019 | return str; 2020 | } 2021 | } 2022 | 2023 | public static String abbreviateMiddle(String str, String middle, int length) { 2024 | if (!isEmpty(str) && !isEmpty(middle)) { 2025 | if (length < str.length() && length >= middle.length() + 2) { 2026 | int targetSting = length - middle.length(); 2027 | int startOffset = targetSting / 2 + targetSting % 2; 2028 | int endOffset = str.length() - targetSting / 2; 2029 | return str.substring(0, startOffset) + middle + str.substring(endOffset); 2030 | } else { 2031 | return str; 2032 | } 2033 | } else { 2034 | return str; 2035 | } 2036 | } 2037 | 2038 | public static String difference(String str1, String str2) { 2039 | if (str1 == null) { 2040 | return str2; 2041 | } else if (str2 == null) { 2042 | return str1; 2043 | } else { 2044 | int at = indexOfDifference(str1, str2); 2045 | return at == -1 ? "" : str2.substring(at); 2046 | } 2047 | } 2048 | 2049 | public static int indexOfDifference(CharSequence cs1, CharSequence cs2) { 2050 | if (cs1 == cs2) { 2051 | return -1; 2052 | } else if (cs1 != null && cs2 != null) { 2053 | int i; 2054 | for (i = 0; i < cs1.length() && i < cs2.length() && cs1.charAt(i) == cs2.charAt(i); ++i) { 2055 | } 2056 | 2057 | return i >= cs2.length() && i >= cs1.length() ? -1 : i; 2058 | } else { 2059 | return 0; 2060 | } 2061 | } 2062 | 2063 | public static int indexOfDifference(CharSequence... css) { 2064 | if (css != null && css.length > 1) { 2065 | boolean anyStringNull = false; 2066 | boolean allStringsNull = true; 2067 | int arrayLen = css.length; 2068 | int shortestStrLen = Integer.MAX_VALUE; 2069 | int longestStrLen = 0; 2070 | CharSequence[] var6 = css; 2071 | int stringPos = css.length; 2072 | 2073 | for (int var8 = 0; var8 < stringPos; ++var8) { 2074 | CharSequence cs = var6[var8]; 2075 | if (cs == null) { 2076 | anyStringNull = true; 2077 | shortestStrLen = 0; 2078 | } else { 2079 | allStringsNull = false; 2080 | shortestStrLen = Math.min(cs.length(), shortestStrLen); 2081 | longestStrLen = Math.max(cs.length(), longestStrLen); 2082 | } 2083 | } 2084 | 2085 | if (allStringsNull || longestStrLen == 0 && !anyStringNull) { 2086 | return -1; 2087 | } else if (shortestStrLen == 0) { 2088 | return 0; 2089 | } else { 2090 | int firstDiff = -1; 2091 | 2092 | for (stringPos = 0; stringPos < shortestStrLen; ++stringPos) { 2093 | char comparisonChar = css[0].charAt(stringPos); 2094 | 2095 | for (int arrayPos = 1; arrayPos < arrayLen; ++arrayPos) { 2096 | if (css[arrayPos].charAt(stringPos) != comparisonChar) { 2097 | firstDiff = stringPos; 2098 | break; 2099 | } 2100 | } 2101 | 2102 | if (firstDiff != -1) { 2103 | break; 2104 | } 2105 | } 2106 | 2107 | return firstDiff == -1 && shortestStrLen != longestStrLen ? shortestStrLen : firstDiff; 2108 | } 2109 | } else { 2110 | return -1; 2111 | } 2112 | } 2113 | 2114 | public static String getCommonPrefix(String... strs) { 2115 | if (strs != null && strs.length != 0) { 2116 | int smallestIndexOfDiff = indexOfDifference(strs); 2117 | if (smallestIndexOfDiff == -1) { 2118 | return strs[0] == null ? "" : strs[0]; 2119 | } else { 2120 | return smallestIndexOfDiff == 0 ? "" : strs[0].substring(0, smallestIndexOfDiff); 2121 | } 2122 | } else { 2123 | return ""; 2124 | } 2125 | } 2126 | 2127 | /** 2128 | * @deprecated 2129 | */ 2130 | @Deprecated 2131 | public static int getLevenshteinDistance(CharSequence s, CharSequence t) { 2132 | if (s != null && t != null) { 2133 | int n = s.length(); 2134 | int m = t.length(); 2135 | if (n == 0) { 2136 | return m; 2137 | } else if (m == 0) { 2138 | return n; 2139 | } else { 2140 | if (n > m) { 2141 | CharSequence tmp = s; 2142 | s = t; 2143 | t = tmp; 2144 | n = m; 2145 | m = tmp.length(); 2146 | } 2147 | 2148 | int[] p = new int[n + 1]; 2149 | 2150 | int i; 2151 | for (i = 0; i <= n; p[i] = i++) { 2152 | } 2153 | 2154 | for (int j = 1; j <= m; ++j) { 2155 | int upper_left = p[0]; 2156 | char t_j = t.charAt(j - 1); 2157 | p[0] = j; 2158 | 2159 | for (i = 1; i <= n; ++i) { 2160 | int upper = p[i]; 2161 | int cost = s.charAt(i - 1) == t_j ? 0 : 1; 2162 | p[i] = Math.min(Math.min(p[i - 1] + 1, p[i] + 1), upper_left + cost); 2163 | upper_left = upper; 2164 | } 2165 | } 2166 | 2167 | return p[n]; 2168 | } 2169 | } else { 2170 | throw new IllegalArgumentException("Strings must not be null"); 2171 | } 2172 | } 2173 | 2174 | /** 2175 | * @deprecated 2176 | */ 2177 | @Deprecated 2178 | public static int getLevenshteinDistance(CharSequence s, CharSequence t, int threshold) { 2179 | if (s != null && t != null) { 2180 | if (threshold < 0) { 2181 | throw new IllegalArgumentException("Threshold must not be negative"); 2182 | } else { 2183 | int n = s.length(); 2184 | int m = t.length(); 2185 | if (n == 0) { 2186 | return m <= threshold ? m : -1; 2187 | } else if (m == 0) { 2188 | return n <= threshold ? n : -1; 2189 | } else if (Math.abs(n - m) > threshold) { 2190 | return -1; 2191 | } else { 2192 | if (n > m) { 2193 | CharSequence tmp = s; 2194 | s = t; 2195 | t = tmp; 2196 | n = m; 2197 | m = tmp.length(); 2198 | } 2199 | 2200 | int[] p = new int[n + 1]; 2201 | int[] d = new int[n + 1]; 2202 | int boundary = Math.min(n, threshold) + 1; 2203 | 2204 | int j; 2205 | for (j = 0; j < boundary; p[j] = j++) { 2206 | } 2207 | 2208 | Arrays.fill(p, boundary, p.length, Integer.MAX_VALUE); 2209 | Arrays.fill(d, Integer.MAX_VALUE); 2210 | 2211 | for (j = 1; j <= m; ++j) { 2212 | char t_j = t.charAt(j - 1); 2213 | d[0] = j; 2214 | int min = Math.max(1, j - threshold); 2215 | int max = j > Integer.MAX_VALUE - threshold ? n : Math.min(n, j + threshold); 2216 | if (min > max) { 2217 | return -1; 2218 | } 2219 | 2220 | if (min > 1) { 2221 | d[min - 1] = Integer.MAX_VALUE; 2222 | } 2223 | 2224 | for (int i = min; i <= max; ++i) { 2225 | if (s.charAt(i - 1) == t_j) { 2226 | d[i] = p[i - 1]; 2227 | } else { 2228 | d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]); 2229 | } 2230 | } 2231 | 2232 | int[] _d = p; 2233 | p = d; 2234 | d = _d; 2235 | } 2236 | 2237 | if (p[n] <= threshold) { 2238 | return p[n]; 2239 | } else { 2240 | return -1; 2241 | } 2242 | } 2243 | } 2244 | } else { 2245 | throw new IllegalArgumentException("Strings must not be null"); 2246 | } 2247 | } 2248 | 2249 | /** 2250 | * @deprecated 2251 | */ 2252 | @Deprecated 2253 | public static double getJaroWinklerDistance(CharSequence first, CharSequence second) { 2254 | double DEFAULT_SCALING_FACTOR = 0.1; 2255 | if (first != null && second != null) { 2256 | int[] mtp = matches(first, second); 2257 | double m = (double) mtp[0]; 2258 | if (m == 0.0) { 2259 | return 0.0; 2260 | } else { 2261 | double j = (m / (double) first.length() + m / (double) second.length() + (m - (double) mtp[1]) / m) / 3.0; 2262 | double jw = j < 0.7 ? j : j + Math.min(0.1, 1.0 / (double) mtp[3]) * (double) mtp[2] * (1.0 - j); 2263 | return (double) Math.round(jw * 100.0) / 100.0; 2264 | } 2265 | } else { 2266 | throw new IllegalArgumentException("Strings must not be null"); 2267 | } 2268 | } 2269 | 2270 | private static int[] matches(CharSequence first, CharSequence second) { 2271 | CharSequence max; 2272 | CharSequence min; 2273 | if (first.length() > second.length()) { 2274 | max = first; 2275 | min = second; 2276 | } else { 2277 | max = second; 2278 | min = first; 2279 | } 2280 | 2281 | int range = Math.max(max.length() / 2 - 1, 0); 2282 | int[] matchIndexes = new int[min.length()]; 2283 | Arrays.fill(matchIndexes, -1); 2284 | boolean[] matchFlags = new boolean[max.length()]; 2285 | int matches = 0; 2286 | 2287 | int transpositions; 2288 | int prefix; 2289 | for (int mi = 0; mi < min.length(); ++mi) { 2290 | char c1 = min.charAt(mi); 2291 | transpositions = Math.max(mi - range, 0); 2292 | 2293 | for (prefix = Math.min(mi + range + 1, max.length()); transpositions < prefix; ++transpositions) { 2294 | if (!matchFlags[transpositions] && c1 == max.charAt(transpositions)) { 2295 | matchIndexes[mi] = transpositions; 2296 | matchFlags[transpositions] = true; 2297 | ++matches; 2298 | break; 2299 | } 2300 | } 2301 | } 2302 | 2303 | char[] ms1 = new char[matches]; 2304 | char[] ms2 = new char[matches]; 2305 | transpositions = 0; 2306 | 2307 | for (prefix = 0; transpositions < min.length(); ++transpositions) { 2308 | if (matchIndexes[transpositions] != -1) { 2309 | ms1[prefix] = min.charAt(transpositions); 2310 | ++prefix; 2311 | } 2312 | } 2313 | 2314 | transpositions = 0; 2315 | 2316 | for (prefix = 0; transpositions < max.length(); ++transpositions) { 2317 | if (matchFlags[transpositions]) { 2318 | ms2[prefix] = max.charAt(transpositions); 2319 | ++prefix; 2320 | } 2321 | } 2322 | 2323 | transpositions = 0; 2324 | 2325 | for (prefix = 0; prefix < ms1.length; ++prefix) { 2326 | if (ms1[prefix] != ms2[prefix]) { 2327 | ++transpositions; 2328 | } 2329 | } 2330 | 2331 | prefix = 0; 2332 | 2333 | for (int mi = 0; mi < min.length() && first.charAt(mi) == second.charAt(mi); ++mi) { 2334 | ++prefix; 2335 | } 2336 | 2337 | return new int[]{matches, transpositions / 2, prefix, max.length()}; 2338 | } 2339 | 2340 | /** 2341 | * @deprecated 2342 | */ 2343 | @Deprecated 2344 | public static int getFuzzyDistance(CharSequence term, CharSequence query, Locale locale) { 2345 | if (term != null && query != null) { 2346 | if (locale == null) { 2347 | throw new IllegalArgumentException("Locale must not be null"); 2348 | } else { 2349 | String termLowerCase = term.toString().toLowerCase(locale); 2350 | String queryLowerCase = query.toString().toLowerCase(locale); 2351 | int score = 0; 2352 | int termIndex = 0; 2353 | int previousMatchingCharacterIndex = Integer.MIN_VALUE; 2354 | 2355 | for (int queryIndex = 0; queryIndex < queryLowerCase.length(); ++queryIndex) { 2356 | char queryChar = queryLowerCase.charAt(queryIndex); 2357 | 2358 | for (boolean termCharacterMatchFound = false; termIndex < termLowerCase.length() && !termCharacterMatchFound; ++termIndex) { 2359 | char termChar = termLowerCase.charAt(termIndex); 2360 | if (queryChar == termChar) { 2361 | ++score; 2362 | if (previousMatchingCharacterIndex + 1 == termIndex) { 2363 | score += 2; 2364 | } 2365 | 2366 | previousMatchingCharacterIndex = termIndex; 2367 | termCharacterMatchFound = true; 2368 | } 2369 | } 2370 | } 2371 | 2372 | return score; 2373 | } 2374 | } else { 2375 | throw new IllegalArgumentException("Strings must not be null"); 2376 | } 2377 | } 2378 | 2379 | public static String normalizeSpace(String str) { 2380 | if (isEmpty(str)) { 2381 | return str; 2382 | } else { 2383 | int size = str.length(); 2384 | char[] newChars = new char[size]; 2385 | int count = 0; 2386 | int whitespacesCount = 0; 2387 | boolean startWhitespaces = true; 2388 | 2389 | for (int i = 0; i < size; ++i) { 2390 | char actualChar = str.charAt(i); 2391 | boolean isWhitespace = Character.isWhitespace(actualChar); 2392 | if (isWhitespace) { 2393 | if (whitespacesCount == 0 && !startWhitespaces) { 2394 | newChars[count++] = " ".charAt(0); 2395 | } 2396 | 2397 | ++whitespacesCount; 2398 | } else { 2399 | startWhitespaces = false; 2400 | newChars[count++] = actualChar == 160 ? 32 : actualChar; 2401 | whitespacesCount = 0; 2402 | } 2403 | } 2404 | 2405 | if (startWhitespaces) { 2406 | return ""; 2407 | } else { 2408 | return (new String(newChars, 0, count - (whitespacesCount > 0 ? 1 : 0))).trim(); 2409 | } 2410 | } 2411 | } 2412 | 2413 | /** 2414 | * @deprecated 2415 | */ 2416 | @Deprecated 2417 | public static String toString(byte[] bytes, String charsetName) throws UnsupportedEncodingException { 2418 | return charsetName != null ? new String(bytes, charsetName) : new String(bytes, Charset.defaultCharset()); 2419 | } 2420 | 2421 | public static String toEncodedString(byte[] bytes, Charset charset) { 2422 | return new String(bytes, charset != null ? charset : Charset.defaultCharset()); 2423 | } 2424 | 2425 | public static String wrap(String str, char wrapWith) { 2426 | return !isEmpty(str) && wrapWith != 0 ? wrapWith + str + wrapWith : str; 2427 | } 2428 | 2429 | public static String wrap(String str, String wrapWith) { 2430 | return !isEmpty(str) && !isEmpty(wrapWith) ? wrapWith.concat(str).concat(wrapWith) : str; 2431 | } 2432 | 2433 | public static String wrapIfMissing(String str, char wrapWith) { 2434 | if (!isEmpty(str) && wrapWith != 0) { 2435 | StringBuilder builder = new StringBuilder(str.length() + 2); 2436 | if (str.charAt(0) != wrapWith) { 2437 | builder.append(wrapWith); 2438 | } 2439 | 2440 | builder.append(str); 2441 | if (str.charAt(str.length() - 1) != wrapWith) { 2442 | builder.append(wrapWith); 2443 | } 2444 | 2445 | return builder.toString(); 2446 | } else { 2447 | return str; 2448 | } 2449 | } 2450 | 2451 | public static String wrapIfMissing(String str, String wrapWith) { 2452 | if (!isEmpty(str) && !isEmpty(wrapWith)) { 2453 | StringBuilder builder = new StringBuilder(str.length() + wrapWith.length() + wrapWith.length()); 2454 | if (!str.startsWith(wrapWith)) { 2455 | builder.append(wrapWith); 2456 | } 2457 | 2458 | builder.append(str); 2459 | if (!str.endsWith(wrapWith)) { 2460 | builder.append(wrapWith); 2461 | } 2462 | 2463 | return builder.toString(); 2464 | } else { 2465 | return str; 2466 | } 2467 | } 2468 | 2469 | public static int[] toCodePoints(CharSequence str) { 2470 | if (str == null) { 2471 | return null; 2472 | } else if (str.length() == 0) { 2473 | return new int[0]; 2474 | } else { 2475 | String s = str.toString(); 2476 | int[] result = new int[s.codePointCount(0, s.length())]; 2477 | int index = 0; 2478 | 2479 | for (int i = 0; i < result.length; ++i) { 2480 | result[i] = s.codePointAt(index); 2481 | index += Character.charCount(result[i]); 2482 | } 2483 | 2484 | return result; 2485 | } 2486 | } 2487 | 2488 | public static String valueOf(char[] value) { 2489 | return value == null ? null : String.valueOf(value); 2490 | } 2491 | } 2492 | --------------------------------------------------------------------------------