You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

StringUtil.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.util;
  16. import static java.nio.charset.StandardCharsets.ISO_8859_1;
  17. import java.nio.charset.Charset;
  18. import java.nio.charset.StandardCharsets;
  19. import java.util.Locale;
  20. /**
  21. * Collection of string handling utilities
  22. */
  23. @Internal
  24. public final class StringUtil {
  25. //arbitrarily selected; may need to increase
  26. private static final int DEFAULT_MAX_RECORD_LENGTH = 10000000;
  27. private static int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH;
  28. public static final Charset UTF16LE = StandardCharsets.UTF_16LE;
  29. public static final Charset UTF8 = StandardCharsets.UTF_8;
  30. public static final Charset WIN_1252 = Charset.forName("cp1252");
  31. /**
  32. * @param length the max record length allowed for StringUtil
  33. */
  34. public static void setMaxRecordLength(int length) {
  35. MAX_RECORD_LENGTH = length;
  36. }
  37. /**
  38. * @return the max record length allowed for StringUtil
  39. */
  40. public static int getMaxRecordLength() {
  41. return MAX_RECORD_LENGTH;
  42. }
  43. private StringUtil() {
  44. // no instances of this class
  45. }
  46. /**
  47. * Given a byte array of 16-bit unicode characters in Little Endian
  48. * format (most important byte last), return a Java String representation
  49. * of it.
  50. * <p>
  51. * { 0x16, 0x00 } -0x16
  52. *
  53. * @param string the byte array to be converted
  54. * @param offset the initial offset into the
  55. * byte array. it is assumed that string[ offset ] and string[ offset +
  56. * 1 ] contain the first 16-bit unicode character
  57. * @param len the length of the final string
  58. * @return the converted string, never {@code null}.
  59. * @throws ArrayIndexOutOfBoundsException if offset is out of bounds for
  60. * the byte array (i.e., is negative or is greater than or equal to
  61. * string.length)
  62. * @throws IllegalArgumentException if len is too large (i.e.,
  63. * there is not enough data in string to create a String of that
  64. * length)
  65. */
  66. public static String getFromUnicodeLE(
  67. final byte[] string,
  68. final int offset,
  69. final int len)
  70. throws ArrayIndexOutOfBoundsException, IllegalArgumentException {
  71. if (len == 0) {
  72. return "";
  73. }
  74. if ((offset < 0) || (offset >= string.length)) {
  75. throw new ArrayIndexOutOfBoundsException("Illegal offset " + offset + " (String data is of length " + string.length + ")");
  76. }
  77. if ((len < 0) || (((string.length - offset) / 2) < len)) {
  78. throw new IllegalArgumentException("Illegal length " + len);
  79. }
  80. return new String(string, offset, len * 2, UTF16LE);
  81. }
  82. /**
  83. * Given a byte array of 16-bit unicode characters in little endian
  84. * format (most important byte last), return a Java String representation
  85. * of it.
  86. * <p>
  87. * { 0x16, 0x00 } -0x16
  88. *
  89. * @param string the byte array to be converted
  90. * @return the converted string, never {@code null}
  91. */
  92. public static String getFromUnicodeLE(byte[] string) {
  93. if (string.length == 0) {
  94. return "";
  95. }
  96. return getFromUnicodeLE(string, 0, string.length / 2);
  97. }
  98. /**
  99. * Convert String to 16-bit unicode characters in little endian format
  100. *
  101. * @param string the string
  102. * @return the byte array of 16-bit unicode characters
  103. */
  104. public static byte[] getToUnicodeLE(String string) {
  105. return string.getBytes(UTF16LE);
  106. }
  107. /**
  108. * Read 8 bit data (in ISO-8859-1 codepage) into a (unicode) Java
  109. * String and return.
  110. * (In Excel terms, read compressed 8 bit unicode as a string)
  111. *
  112. * @param string byte array to read
  113. * @param offset offset to read byte array
  114. * @param len length to read byte array
  115. * @return String generated String instance by reading byte array
  116. */
  117. public static String getFromCompressedUnicode(
  118. final byte[] string,
  119. final int offset,
  120. final int len) {
  121. int len_to_use = Math.min(len, string.length - offset);
  122. return new String(string, offset, len_to_use, ISO_8859_1);
  123. }
  124. public static String readCompressedUnicode(LittleEndianInput in, int nChars) {
  125. byte[] buf = IOUtils.safelyAllocate(nChars, MAX_RECORD_LENGTH);
  126. in.readFully(buf);
  127. return new String(buf, ISO_8859_1);
  128. }
  129. /**
  130. * InputStream {@code in} is expected to contain:
  131. * <ol>
  132. * <li>ushort nChars</li>
  133. * <li>byte is16BitFlag</li>
  134. * <li>byte[]/char[] characterData</li>
  135. * </ol>
  136. * For this encoding, the is16BitFlag is always present even if nChars==0.
  137. * <p>
  138. * This structure is also known as a XLUnicodeString.
  139. */
  140. public static String readUnicodeString(LittleEndianInput in) {
  141. int nChars = in.readUShort();
  142. byte flag = in.readByte();
  143. if ((flag & 0x01) == 0) {
  144. return readCompressedUnicode(in, nChars);
  145. }
  146. return readUnicodeLE(in, nChars);
  147. }
  148. /**
  149. * InputStream {@code in} is expected to contain:
  150. * <ol>
  151. * <li>byte is16BitFlag</li>
  152. * <li>byte[]/char[] characterData</li>
  153. * </ol>
  154. * For this encoding, the is16BitFlag is always present even if nChars==0.
  155. * <br>
  156. * This method should be used when the nChars field is <em>not</em> stored
  157. * as a ushort immediately before the is16BitFlag. Otherwise, {@link
  158. * #readUnicodeString(LittleEndianInput)} can be used.
  159. */
  160. public static String readUnicodeString(LittleEndianInput in, int nChars) {
  161. byte is16Bit = in.readByte();
  162. if ((is16Bit & 0x01) == 0) {
  163. return readCompressedUnicode(in, nChars);
  164. }
  165. return readUnicodeLE(in, nChars);
  166. }
  167. /**
  168. * OutputStream {@code out} will get:
  169. * <ol>
  170. * <li>ushort nChars</li>
  171. * <li>byte is16BitFlag</li>
  172. * <li>byte[]/char[] characterData</li>
  173. * </ol>
  174. * For this encoding, the is16BitFlag is always present even if nChars==0.
  175. */
  176. public static void writeUnicodeString(LittleEndianOutput out, String value) {
  177. int nChars = value.length();
  178. out.writeShort(nChars);
  179. boolean is16Bit = hasMultibyte(value);
  180. out.writeByte(is16Bit ? 0x01 : 0x00);
  181. if (is16Bit) {
  182. putUnicodeLE(value, out);
  183. } else {
  184. putCompressedUnicode(value, out);
  185. }
  186. }
  187. /**
  188. * OutputStream {@code out} will get:
  189. * <ol>
  190. * <li>byte is16BitFlag</li>
  191. * <li>byte[]/char[] characterData</li>
  192. * </ol>
  193. * For this encoding, the is16BitFlag is always present even if nChars==0.
  194. * <br>
  195. * This method should be used when the nChars field is <em>not</em> stored
  196. * as a ushort immediately before the is16BitFlag. Otherwise, {@link
  197. * #writeUnicodeString(LittleEndianOutput, String)} can be used.
  198. */
  199. public static void writeUnicodeStringFlagAndData(LittleEndianOutput out, String value) {
  200. boolean is16Bit = hasMultibyte(value);
  201. out.writeByte(is16Bit ? 0x01 : 0x00);
  202. if (is16Bit) {
  203. putUnicodeLE(value, out);
  204. } else {
  205. putCompressedUnicode(value, out);
  206. }
  207. }
  208. /**
  209. * @return the number of bytes that would be written by {@link #writeUnicodeString(LittleEndianOutput, String)}
  210. */
  211. public static int getEncodedSize(String value) {
  212. int result = 2 + 1;
  213. result += value.length() * (StringUtil.hasMultibyte(value) ? 2 : 1);
  214. return result;
  215. }
  216. /**
  217. * Takes a unicode (java) string, and returns it as 8 bit data (in ISO-8859-1
  218. * codepage).
  219. * (In Excel terms, write compressed 8 bit unicode)
  220. *
  221. * @param input the String containing the data to be written
  222. * @param output the byte array to which the data is to be written
  223. * @param offset an offset into the byte arrat at which the data is start
  224. * when written
  225. */
  226. public static void putCompressedUnicode(String input, byte[] output, int offset) {
  227. byte[] bytes = input.getBytes(ISO_8859_1);
  228. System.arraycopy(bytes, 0, output, offset, bytes.length);
  229. }
  230. public static void putCompressedUnicode(String input, LittleEndianOutput out) {
  231. byte[] bytes = input.getBytes(ISO_8859_1);
  232. out.write(bytes);
  233. }
  234. /**
  235. * Takes a unicode string, and returns it as little endian (most
  236. * important byte last) bytes in the supplied byte array.
  237. * (In Excel terms, write uncompressed unicode)
  238. *
  239. * @param input the String containing the unicode data to be written
  240. * @param output the byte array to hold the uncompressed unicode, should be twice the length of the String
  241. * @param offset the offset to start writing into the byte array
  242. */
  243. public static void putUnicodeLE(String input, byte[] output, int offset) {
  244. byte[] bytes = input.getBytes(UTF16LE);
  245. System.arraycopy(bytes, 0, output, offset, bytes.length);
  246. }
  247. public static void putUnicodeLE(String input, LittleEndianOutput out) {
  248. byte[] bytes = input.getBytes(UTF16LE);
  249. out.write(bytes);
  250. }
  251. public static String readUnicodeLE(LittleEndianInput in, int nChars) {
  252. byte[] bytes = IOUtils.safelyAllocate(nChars * 2L, MAX_RECORD_LENGTH);
  253. in.readFully(bytes);
  254. return new String(bytes, UTF16LE);
  255. }
  256. /**
  257. * @return the encoding we want to use, currently hardcoded to ISO-8859-1
  258. */
  259. public static String getPreferredEncoding() {
  260. return ISO_8859_1.name();
  261. }
  262. /**
  263. * check the parameter has multibyte character
  264. *
  265. * @param value string to check
  266. * @return boolean result true:string has at least one multibyte character
  267. */
  268. public static boolean hasMultibyte(String value) {
  269. if (value == null) {
  270. return false;
  271. }
  272. for (char c : value.toCharArray()) {
  273. if (c > 0xFF) {
  274. return true;
  275. }
  276. }
  277. return false;
  278. }
  279. /**
  280. * Tests if the string starts with the specified prefix, ignoring case consideration.
  281. */
  282. public static boolean startsWithIgnoreCase(String haystack, String prefix) {
  283. return haystack.regionMatches(true, 0, prefix, 0, prefix.length());
  284. }
  285. /**
  286. * Tests if the string ends with the specified suffix, ignoring case consideration.
  287. */
  288. public static boolean endsWithIgnoreCase(String haystack, String suffix) {
  289. int length = suffix.length();
  290. int start = haystack.length() - length;
  291. return haystack.regionMatches(true, start, suffix, 0, length);
  292. }
  293. @Internal
  294. public static String toLowerCase(char c) {
  295. return Character.toString(c).toLowerCase(Locale.ROOT);
  296. }
  297. @Internal
  298. public static String toUpperCase(char c) {
  299. return Character.toString(c).toUpperCase(Locale.ROOT);
  300. }
  301. @Internal
  302. public static boolean isUpperCase(char c) {
  303. String s = Character.toString(c);
  304. return s.toUpperCase(Locale.ROOT).equals(s);
  305. }
  306. /**
  307. * Some strings may contain encoded characters of the unicode private use area.
  308. * Currently the characters of the symbol fonts are mapped to the corresponding
  309. * characters in the normal unicode range.
  310. *
  311. * @param string the original string
  312. * @return the string with mapped characters
  313. * @see <a href="http://www.alanwood.net/unicode/private_use_area.html#symbol">Private Use Area (symbol)</a>
  314. * @see <a href="http://www.alanwood.net/demos/symbol.html">Symbol font - Unicode alternatives for Greek and special characters in HTML</a>
  315. */
  316. public static String mapMsCodepointString(String string) {
  317. if (string == null || string.isEmpty()) {
  318. return string;
  319. }
  320. int[] cps = string.codePoints().map(StringUtil::mapMsCodepoint).toArray();
  321. return new String(cps, 0, cps.length);
  322. }
  323. private static int mapMsCodepoint(int cp) {
  324. if (0xf020 <= cp && cp <= 0xf07f) {
  325. return symbolMap_f020[cp - 0xf020];
  326. } else if (0xf0a0 <= cp && cp <= 0xf0ff) {
  327. return symbolMap_f0a0[cp - 0xf0a0];
  328. }
  329. return cp;
  330. }
  331. private static final int[] symbolMap_f020 = {
  332. ' ', // 0xf020 space
  333. '!', // 0xf021 exclam
  334. 8704, // 0xf022 universal
  335. '#', // 0xf023 numbersign
  336. 8707, // 0xf024 existential
  337. '%', // 0xf025 percent
  338. '&', // 0xf026 ampersand
  339. 8717, // 0xf027 suchthat
  340. '(', // 0xf028 parenleft
  341. ')', // 0xf029 parentright
  342. 8727, // 0xf02a asteriskmath
  343. '+', // 0xf02b plus
  344. ',', // 0xf02c comma
  345. 8722, // 0xf02d minus sign (long -)
  346. '.', // 0xf02e period
  347. '/', // 0xf02f slash
  348. '0', // 0xf030 0
  349. '1', // 0xf031 1
  350. '2', // 0xf032 2
  351. '3', // 0xf033 3
  352. '4', // 0xf034 4
  353. '5', // 0xf035 5
  354. '6', // 0xf036 6
  355. '7', // 0xf037 7
  356. '8', // 0xf038 8
  357. '9', // 0xf039 9
  358. ':', // 0xf03a colon
  359. ';', // 0xf03b semicolon
  360. '<', // 0xf03c less
  361. '=', // 0xf03d equal
  362. '>', // 0xf03e greater
  363. '?', // 0xf03f question
  364. 8773, // 0xf040 congruent
  365. 913, // 0xf041 alpha (upper)
  366. 914, // 0xf042 beta (upper)
  367. 935, // 0xf043 chi (upper)
  368. 916, // 0xf044 delta (upper)
  369. 917, // 0xf045 epsilon (upper)
  370. 934, // 0xf046 phi (upper)
  371. 915, // 0xf047 gamma (upper)
  372. 919, // 0xf048 eta (upper)
  373. 921, // 0xf049 iota (upper)
  374. 977, // 0xf04a theta1 (lower)
  375. 922, // 0xf04b kappa (upper)
  376. 923, // 0xf04c lambda (upper)
  377. 924, // 0xf04d mu (upper)
  378. 925, // 0xf04e nu (upper)
  379. 927, // 0xf04f omicron (upper)
  380. 928, // 0xf050 pi (upper)
  381. 920, // 0xf051 theta (upper)
  382. 929, // 0xf052 rho (upper)
  383. 931, // 0xf053 sigma (upper)
  384. 932, // 0xf054 tau (upper)
  385. 933, // 0xf055 upsilon (upper)
  386. 962, // 0xf056 simga1 (lower)
  387. 937, // 0xf057 omega (upper)
  388. 926, // 0xf058 xi (upper)
  389. 936, // 0xf059 psi (upper)
  390. 918, // 0xf05a zeta (upper)
  391. '[', // 0xf05b bracketleft
  392. 8765, // 0xf05c therefore
  393. ']', // 0xf05d bracketright
  394. 8869, // 0xf05e perpendicular
  395. '_', // 0xf05f underscore
  396. ' ', // 0xf060 radicalex (doesn't exist in unicode)
  397. 945, // 0xf061 alpha (lower)
  398. 946, // 0xf062 beta (lower)
  399. 967, // 0xf063 chi (lower)
  400. 948, // 0xf064 delta (lower)
  401. 949, // 0xf065 epsilon (lower)
  402. 966, // 0xf066 phi (lower)
  403. 947, // 0xf067 gamma (lower)
  404. 951, // 0xf068 eta (lower)
  405. 953, // 0xf069 iota (lower)
  406. 981, // 0xf06a phi1 (lower)
  407. 954, // 0xf06b kappa (lower)
  408. 955, // 0xf06c lambda (lower)
  409. 956, // 0xf06d mu (lower)
  410. 957, // 0xf06e nu (lower)
  411. 959, // 0xf06f omnicron (lower)
  412. 960, // 0xf070 pi (lower)
  413. 952, // 0xf071 theta (lower)
  414. 961, // 0xf072 rho (lower)
  415. 963, // 0xf073 sigma (lower)
  416. 964, // 0xf074 tau (lower)
  417. 965, // 0xf075 upsilon (lower)
  418. 982, // 0xf076 piv (lower)
  419. 969, // 0xf077 omega (lower)
  420. 958, // 0xf078 xi (lower)
  421. 968, // 0xf079 psi (lower)
  422. 950, // 0xf07a zeta (lower)
  423. '{', // 0xf07b braceleft
  424. '|', // 0xf07c bar
  425. '}', // 0xf07d braceright
  426. 8764, // 0xf07e similar '~'
  427. ' ', // 0xf07f not defined
  428. };
  429. private static final int[] symbolMap_f0a0 = {
  430. 8364, // 0xf0a0 not defined / euro symbol
  431. 978, // 0xf0a1 upsilon1 (upper)
  432. 8242, // 0xf0a2 minute
  433. 8804, // 0xf0a3 lessequal
  434. 8260, // 0xf0a4 fraction
  435. 8734, // 0xf0a5 infinity
  436. 402, // 0xf0a6 florin
  437. 9827, // 0xf0a7 club
  438. 9830, // 0xf0a8 diamond
  439. 9829, // 0xf0a9 heart
  440. 9824, // 0xf0aa spade
  441. 8596, // 0xf0ab arrowboth
  442. 8591, // 0xf0ac arrowleft
  443. 8593, // 0xf0ad arrowup
  444. 8594, // 0xf0ae arrowright
  445. 8595, // 0xf0af arrowdown
  446. 176, // 0xf0b0 degree
  447. 177, // 0xf0b1 plusminus
  448. 8243, // 0xf0b2 second
  449. 8805, // 0xf0b3 greaterequal
  450. 215, // 0xf0b4 multiply
  451. 181, // 0xf0b5 proportional
  452. 8706, // 0xf0b6 partialdiff
  453. 8729, // 0xf0b7 bullet
  454. 247, // 0xf0b8 divide
  455. 8800, // 0xf0b9 notequal
  456. 8801, // 0xf0ba equivalence
  457. 8776, // 0xf0bb approxequal
  458. 8230, // 0xf0bc ellipsis
  459. 9168, // 0xf0bd arrowvertex
  460. 9135, // 0xf0be arrowhorizex
  461. 8629, // 0xf0bf carriagereturn
  462. 8501, // 0xf0c0 aleph
  463. 8475, // 0xf0c1 Ifraktur
  464. 8476, // 0xf0c2 Rfraktur
  465. 8472, // 0xf0c3 weierstrass
  466. 8855, // 0xf0c4 circlemultiply
  467. 8853, // 0xf0c5 circleplus
  468. 8709, // 0xf0c6 emptyset
  469. 8745, // 0xf0c7 intersection
  470. 8746, // 0xf0c8 union
  471. 8835, // 0xf0c9 propersuperset
  472. 8839, // 0xf0ca reflexsuperset
  473. 8836, // 0xf0cb notsubset
  474. 8834, // 0xf0cc propersubset
  475. 8838, // 0xf0cd reflexsubset
  476. 8712, // 0xf0ce element
  477. 8713, // 0xf0cf notelement
  478. 8736, // 0xf0d0 angle
  479. 8711, // 0xf0d1 gradient
  480. 174, // 0xf0d2 registerserif
  481. 169, // 0xf0d3 copyrightserif
  482. 8482, // 0xf0d4 trademarkserif
  483. 8719, // 0xf0d5 product
  484. 8730, // 0xf0d6 radical
  485. 8901, // 0xf0d7 dotmath
  486. 172, // 0xf0d8 logicalnot
  487. 8743, // 0xf0d9 logicaland
  488. 8744, // 0xf0da logicalor
  489. 8660, // 0xf0db arrowdblboth
  490. 8656, // 0xf0dc arrowdblleft
  491. 8657, // 0xf0dd arrowdblup
  492. 8658, // 0xf0de arrowdblright
  493. 8659, // 0xf0df arrowdbldown
  494. 9674, // 0xf0e0 lozenge
  495. 9001, // 0xf0e1 angleleft
  496. 174, // 0xf0e2 registersans
  497. 169, // 0xf0e3 copyrightsans
  498. 8482, // 0xf0e4 trademarksans
  499. 8721, // 0xf0e5 summation
  500. 9115, // 0xf0e6 parenlefttp
  501. 9116, // 0xf0e7 parenleftex
  502. 9117, // 0xf0e8 parenleftbt
  503. 9121, // 0xf0e9 bracketlefttp
  504. 9122, // 0xf0ea bracketleftex
  505. 9123, // 0xf0eb bracketleftbt
  506. 9127, // 0xf0ec bracelefttp
  507. 9128, // 0xf0ed braceleftmid
  508. 9129, // 0xf0ee braceleftbt
  509. 9130, // 0xf0ef braceex
  510. ' ', // 0xf0f0 not defined
  511. 9002, // 0xf0f1 angleright
  512. 8747, // 0xf0f2 integral
  513. 8992, // 0xf0f3 integraltp
  514. 9134, // 0xf0f4 integralex
  515. 8993, // 0xf0f5 integralbt
  516. 9118, // 0xf0f6 parenrighttp
  517. 9119, // 0xf0f7 parenrightex
  518. 9120, // 0xf0f8 parenrightbt
  519. 9124, // 0xf0f9 bracketrighttp
  520. 9125, // 0xf0fa bracketrightex
  521. 9126, // 0xf0fb bracketrightbt
  522. 9131, // 0xf0fc bracerighttp
  523. 9132, // 0xf0fd bracerightmid
  524. 9133, // 0xf0fe bracerightbt
  525. ' ', // 0xf0ff not defined
  526. };
  527. // Could be replaced with org.apache.commons.lang3.StringUtils#join
  528. @Internal
  529. public static String join(Object[] array, String separator) {
  530. if (array == null || array.length == 0) {
  531. return "";
  532. }
  533. StringBuilder sb = new StringBuilder();
  534. sb.append(array[0]);
  535. for (int i = 1; i < array.length; i++) {
  536. sb.append(separator).append(array[i]);
  537. }
  538. return sb.toString();
  539. }
  540. @Internal
  541. public static String join(Object[] array) {
  542. if (array == null) {
  543. return "";
  544. }
  545. StringBuilder sb = new StringBuilder();
  546. for (Object o : array) {
  547. sb.append(o);
  548. }
  549. return sb.toString();
  550. }
  551. @Internal
  552. public static String join(String separator, Object... array) {
  553. return join(array, separator);
  554. }
  555. /**
  556. * Count number of occurrences of needle in haystack
  557. * Has same signature as org.apache.commons.lang3.StringUtils#countMatches
  558. *
  559. * @param haystack the CharSequence to check, may be null
  560. * @param needle the character to count the quantity of
  561. * @return the number of occurrences, 0 if the CharSequence is null
  562. */
  563. public static int countMatches(CharSequence haystack, char needle) {
  564. if (haystack == null) {
  565. return 0;
  566. }
  567. int count = 0;
  568. final int length = haystack.length();
  569. for (int i = 0; i < length; i++) {
  570. if (haystack.charAt(i) == needle) {
  571. count++;
  572. }
  573. }
  574. return count;
  575. }
  576. /**
  577. * Given a byte array of 16-bit unicode characters in Little Endian
  578. * format (most important byte last), return a Java String representation
  579. * of it.
  580. *
  581. * Scans the byte array for two continous 0 bytes and returns the string before.
  582. * <p>
  583. *
  584. * #61881: there seem to be programs out there, which write the 0-termination also
  585. * at the beginning of the string. Check if the next two bytes contain a valid ascii char
  586. * and correct the _recdata with a '?' char
  587. *
  588. *
  589. * @param string the byte array to be converted
  590. * @param offset the initial offset into the
  591. * byte array. it is assumed that string[ offset ] and string[ offset +
  592. * 1 ] contain the first 16-bit unicode character
  593. * @param len the max. length of the final string
  594. * @return the converted string, never {@code null}.
  595. * @throws ArrayIndexOutOfBoundsException if offset is out of bounds for
  596. * the byte array (i.e., is negative or is greater than or equal to
  597. * string.length)
  598. * @throws IllegalArgumentException if len is too large (i.e.,
  599. * there is not enough data in string to create a String of that
  600. * length)
  601. */
  602. public static String getFromUnicodeLE0Terminated(
  603. final byte[] string,
  604. final int offset,
  605. final int len)
  606. throws ArrayIndexOutOfBoundsException, IllegalArgumentException {
  607. if ((offset < 0) || (offset >= string.length)) {
  608. throw new ArrayIndexOutOfBoundsException("Illegal offset " + offset + " (String data is of length " + string.length + ")");
  609. }
  610. if ((len < 0) || (((string.length - offset) / 2) < len)) {
  611. throw new IllegalArgumentException("Illegal length " + len);
  612. }
  613. final int newOffset;
  614. final int newMaxLen;
  615. final String prefix;
  616. // #61881 - for now we only check the first char
  617. if (len > 0 && offset < (string.length - 1) && string[offset] == 0 && string[offset+1] == 0) {
  618. newOffset = offset+2;
  619. prefix = "?";
  620. // check if the next char is garbage and limit the len if necessary
  621. final int cp = (len > 1) ? LittleEndian.getShort(string, offset+2) : 0;
  622. newMaxLen = Character.isJavaIdentifierPart(cp) ? len-1 : 0;
  623. } else {
  624. newOffset = offset;
  625. prefix = "";
  626. newMaxLen = len;
  627. }
  628. int newLen = 0;
  629. // loop until we find a null-terminated end
  630. for(; newLen < newMaxLen; newLen++) {
  631. if (string[newOffset + newLen * 2] == 0 && string[newOffset + newLen * 2 + 1] == 0) {
  632. break;
  633. }
  634. }
  635. newLen = Math.min(newLen, newMaxLen);
  636. return prefix + ((newLen == 0) ? "" : new String(string, newOffset, newLen * 2, UTF16LE));
  637. }
  638. }