View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.base;
33  
34  
35  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36  import java.io.IOException;
37  import java.io.UncheckedIOException;
38  import java.nio.ByteBuffer;
39  import java.nio.CharBuffer;
40  import java.nio.charset.CharacterCodingException;
41  import java.nio.charset.CharsetDecoder;
42  import java.nio.charset.CharsetEncoder;
43  import java.nio.charset.CoderResult;
44  import java.nio.charset.CodingErrorAction;
45  import javax.annotation.Nonnull;
46  //CHECKSTYLE:OFF
47  import sun.nio.cs.ArrayDecoder;
48  import sun.nio.cs.ArrayEncoder;
49  //CHECKSTYLE:ON
50  import java.nio.charset.StandardCharsets;
51  
52  /**
53   *
54   * @author zoly
55   */
56  @SuppressFBWarnings("IICU_INCORRECT_INTERNAL_CLASS_USE")
57  public final class Strings {
58  
59  
60    public static final String EOL = System.getProperty("line.separator", "\n");
61  
62  
63    private static final boolean LENIENT_CODING = Boolean.getBoolean("spf4j.encoding.lenient");
64  
65    private static final ThreadLocal<CharsetDecoder> UTF8_DECODER = new ThreadLocal<CharsetDecoder>() {
66  
67      @Override
68      protected CharsetDecoder initialValue() {
69        return createUtf8Decoder();
70      }
71  
72    };
73  
74    private static final ThreadLocal<CharsetEncoder> UTF8_ENCODER = new ThreadLocal<CharsetEncoder>() {
75  
76      @Override
77      protected CharsetEncoder initialValue() {
78        return createUtf8Encoder();
79      }
80  
81    };
82  
83    private Strings() {
84    }
85  
86    /**
87     * @deprecated use CharSequences.distance instead.
88     */
89    @Deprecated
90    public static int distance(@Nonnull final String s1, @Nonnull final String s2) {
91      return CharSequences.distance(s1, s2);
92    }
93  
94  
95  
96    /**
97     * @deprecated use containsAnyChars instead.
98     */
99    @Deprecated
100   public static boolean contains(final String string, final char... chars) {
101     return containsAnyChars(string, chars);
102   }
103 
104   public static boolean containsAnyChars(final String string, final char... chars) {
105     for (char c : chars) {
106       if (string.indexOf(c) >= 0) {
107         return true;
108       }
109     }
110     return false;
111   }
112 
113   /**
114    * @deprecated use CharSequences.containsAnyChar instead.
115    */
116   @Deprecated
117   public static boolean contains(final CharSequence string, final char... chars) {
118     return CharSequences.containsAnyChar(string, chars);
119   }
120 
121   public static String withFirstCharLower(final String str) {
122     if (str.isEmpty()) {
123       return str;
124     }
125     char fc = str.charAt(0);
126     if (Character.isLowerCase(fc)) {
127       return str;
128     }
129     int l = str.length();
130     StringBuilder result = new StringBuilder(l);
131     result.append(Character.toLowerCase(fc));
132     for (int i = 1; i < l; i++) {
133       result.append(str.charAt(i));
134     }
135     return result.toString();
136   }
137 
138   /**
139    * A get/set/is prefixed method name to attribute name converter.
140    * @param prefix
141    * @param str
142    * @return
143    */
144   public static String methodToAttribute(final String prefix, final String str) {
145     int length = str.length();
146     int pl = prefix.length();
147     StringBuilder result = new StringBuilder(length - pl);
148     char fc = str.charAt(pl);
149     if (Character.isLowerCase(fc)) {
150       result.append(str, pl, length);
151     } else {
152       result.append(Character.toLowerCase(fc));
153       result.append(str, pl + 1, length);
154     }
155     return result.toString();
156   }
157 
158   /**
159    * A attribute to get/set/is method name converter.
160    * @param prefix
161    * @param str
162    * @return
163    */
164   public static String attributeToMethod(final String prefix, final String str) {
165     int length = str.length();
166     StringBuilder result = new StringBuilder(length + prefix.length());
167     result.append(prefix);
168     char fc = str.charAt(0);
169     if (Character.isLowerCase(fc)) {
170       result.append(Character.toUpperCase(fc));
171       result.append(str, 1, length);
172     } else {
173       result.append(str);
174     }
175     return result.toString();
176   }
177 
178 
179 
180   public static void writeReplaceWhitespaces(final String str, final char replacement, final Appendable writer)
181           throws IOException {
182     for (char c : steal(str)) {
183       if (Character.isWhitespace(c)) {
184         writer.append(replacement);
185       } else {
186         writer.append(c);
187       }
188     }
189   }
190 
191 
192   /**
193    * Steal the underlying character array of a String.
194    *
195    * @param str
196    * @return
197    */
198   public static char[] steal(final String str) {
199     return UnsafeString.steal(str);
200   }
201 
202   /**
203    * Create a String based on the provided character array. No copy of the array is made.
204    *
205    * @param chars
206    * @return
207    */
208   public static String wrap(final char[] chars) {
209     return UnsafeString.wrap(chars);
210   }
211 
212   public static CharsetEncoder createUtf8Encoder() {
213     if (LENIENT_CODING) {
214       return StandardCharsets.UTF_8.newEncoder().onMalformedInput(CodingErrorAction.REPLACE)
215               .onUnmappableCharacter(CodingErrorAction.REPLACE);
216     } else {
217       return StandardCharsets.UTF_8.newEncoder().onMalformedInput(CodingErrorAction.REPORT)
218               .onUnmappableCharacter(CodingErrorAction.REPORT);
219     }
220   }
221 
222 
223   public static CharsetDecoder createUtf8Decoder() {
224     if (LENIENT_CODING) {
225       return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPLACE)
226               .onUnmappableCharacter(CodingErrorAction.REPLACE);
227     } else {
228       return StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT)
229               .onUnmappableCharacter(CodingErrorAction.REPORT);
230     }
231   }
232 
233   public static CharsetEncoder getUTF8CharsetEncoder() {
234     return UTF8_ENCODER.get();
235   }
236 
237   public static CharsetDecoder getUTF8CharsetDecoder() {
238     return UTF8_DECODER.get();
239   }
240 
241   @SuppressFBWarnings("SUA_SUSPICIOUS_UNINITIALIZED_ARRAY")
242   public static byte[] encode(final CharsetEncoder ce, final char[] ca, final int off, final int len) {
243     if (len == 0) {
244       return Arrays.EMPTY_BYTE_ARRAY;
245     }
246     byte[] ba = TLScratch.getBytesTmp(getmaxNrBytes(ce, len));
247     int nrBytes = encode(ce, ca, off, len, ba);
248     return java.util.Arrays.copyOf(ba, nrBytes);
249   }
250 
251   public static int getmaxNrBytes(final CharsetEncoder ce, final int nrChars) {
252     return (int) (nrChars * (double) ce.maxBytesPerChar());
253   }
254 
255   public static int encode(final CharsetEncoder ce, final char[] ca, final int off, final int len,
256           final byte[] targetArray) {
257     if (len == 0) {
258       return 0;
259     }
260     if (ce instanceof ArrayEncoder) {
261       return ((ArrayEncoder) ce).encode(ca, off, len, targetArray);
262     } else {
263       ce.reset();
264       ByteBuffer bb = ByteBuffer.wrap(targetArray);
265       CharBuffer cb = CharBuffer.wrap(ca, off, len);
266       try {
267         CoderResult cr = ce.encode(cb, bb, true);
268         if (!cr.isUnderflow()) {
269           cr.throwException();
270         }
271         cr = ce.flush(bb);
272         if (!cr.isUnderflow()) {
273           cr.throwException();
274         }
275       } catch (CharacterCodingException x) {
276         throw new InternalError("Should never throw a CharacterCodingException, probably a JVM issue", x);
277       }
278       return bb.position();
279     }
280   }
281 
282   @SuppressFBWarnings("SUA_SUSPICIOUS_UNINITIALIZED_ARRAY")
283   public static String decode(final CharsetDecoder cd, final byte[] ba, final int off, final int len) {
284     if (len == 0) {
285       return "";
286     }
287     int en = (int) (len * (double) cd.maxCharsPerByte());
288     char[] ca = TLScratch.getCharsTmp(en);
289     if (cd instanceof ArrayDecoder) {
290       int clen = ((ArrayDecoder) cd).decode(ba, off, len, ca);
291       return new String(ca, 0, clen);
292     }
293     cd.reset();
294     ByteBuffer bb = ByteBuffer.wrap(ba, off, len);
295     CharBuffer cb = CharBuffer.wrap(ca);
296     try {
297       CoderResult cr = cd.decode(bb, cb, true);
298       if (!cr.isUnderflow()) {
299         cr.throwException();
300       }
301       cr = cd.flush(cb);
302       if (!cr.isUnderflow()) {
303         cr.throwException();
304       }
305     } catch (CharacterCodingException x) {
306       throw new UncheckedIOException(x);
307     }
308     return new String(ca, 0, cb.position());
309   }
310 
311   /**
312    * Optimized UTF8 decoder.
313    *
314    * Here is a benchmark comparison with the JDK implementation (see EncodingBenchmark.java in the benchmark project):
315    *
316    * EncodingBenchmark.stringDecode thrpt 10 16759798.463 ± 343505.144 ops/s EncodingBenchmark.fastStringDecode thrpt 10
317    * 17413298.464 ± 301756.867 ops/s
318    *
319    *
320    * @param bytes
321    * @return
322    */
323   public static String fromUtf8(final byte[] bytes) {
324     return decode(UTF8_DECODER.get(), bytes, 0, bytes.length);
325   }
326 
327   public static String fromUtf8(final byte[] bytes, final int startIdx, final int length) {
328     return decode(UTF8_DECODER.get(), bytes, startIdx, length);
329   }
330 
331   /**
332    * Optimized UTF8 string encoder.
333    *
334    * comparison with the stock JDK implementation (see EncodingBenchmark.java in the benchmark project):
335    *
336    * EncodingBenchmark.stringEncode thrpt 10 9481668.776 ± 252543.135 ops/s EncodingBenchmark.fastStringEncode thrpt 10
337    * 22469383.612 ± 898677.892 ops/s
338    *
339    * @param str
340    * @return
341    */
342   public static byte[] toUtf8(final String str) {
343     final char[] chars = steal(str);
344     return encode(UTF8_ENCODER.get(), chars, 0, chars.length);
345   }
346 
347   /**
348    * @deprecated use CharSequences.compare
349    */
350   @Deprecated
351   public static int compareTo(@Nonnull final CharSequence s, @Nonnull final CharSequence t) {
352     return CharSequences.compareTo(s, t);
353   }
354 
355   @Deprecated
356   public static boolean equals(@Nonnull final CharSequence s, @Nonnull final CharSequence t) {
357     return CharSequences.equals(s, t);
358   }
359 
360   @Deprecated
361   public static int hashcode(final CharSequence cs) {
362     return CharSequences.hashcode(cs);
363   }
364 
365   @Deprecated
366   public static CharSequence subSequence(final CharSequence seq, final int startIdx, final int endIdx) {
367     return CharSequences.subSequence(seq, startIdx, endIdx);
368   }
369 
370   @Deprecated
371   public static boolean endsWith(final CharSequence qc, final CharSequence with) {
372     return CharSequences.endsWith(qc, with);
373   }
374 
375   /**
376    * Utility method to escape java strings to json strings.
377    *
378    * @param toEscape - the java string to escape.
379    * @param jsonString - the destination json String builder.
380    */
381   @Deprecated
382   public static void escapeJsonString(@Nonnull final String toEscape, final StringBuilder jsonString) {
383     AppendableUtils.escapeJsonString(toEscape, jsonString);
384   }
385 
386   @Deprecated
387   public static void escapeJsonString(@Nonnull final String toEscape, final Appendable jsonString)
388           throws IOException {
389     AppendableUtils.escapeJsonString(toEscape, jsonString);
390   }
391 
392   @Deprecated
393   public static void appendJsonStringEscapedChar(final char c, final StringBuilder jsonString) {
394     AppendableUtils.appendJsonStringEscapedChar(c, jsonString);
395   }
396 
397   @Deprecated
398   public static void appendJsonStringEscapedChar(final char c, final Appendable jsonString) throws IOException {
399     AppendableUtils.appendJsonStringEscapedChar(c, jsonString);
400   }
401 
402   @Deprecated
403   public static void appendUnsignedString(final StringBuilder sb, final long nr, final int shift) {
404     AppendableUtils.appendUnsignedString(sb, nr, shift);
405   }
406 
407   @Deprecated
408   public static void appendUnsignedString(final StringBuilder sb, final int nr, final int shift) {
409     AppendableUtils.appendUnsignedString(sb, nr, shift);
410   }
411 
412   @Deprecated
413   public static void appendUnsignedStringPadded(final StringBuilder sb, final int nr, final int shift,
414           final int padTo) {
415     AppendableUtils.appendUnsignedStringPadded(sb, nr, shift, padTo);
416   }
417 
418   @Deprecated
419   public static void appendUnsignedStringPadded(final Appendable sb, final int nr, final int shift,
420           final int padTo) throws IOException {
421     AppendableUtils.appendUnsignedStringPadded(sb, nr, shift, padTo);
422   }
423 
424   @Deprecated
425   public static void appendSpaces(final Appendable to, final int nrSpaces) throws IOException {
426     AppendableUtils.appendSpaces(to, nrSpaces);
427   }
428 
429   @Deprecated
430   public static void appendSpaces(final StringBuilder to, final int nrSpaces) {
431     AppendableUtils.appendSpaces(to, nrSpaces);
432   }
433 
434   /**
435    * @deprecated use CharSequences.regionMatches.
436    */
437   @Deprecated
438   public static boolean regionMatches(final CharSequence t, final int toffset,
439           final CharSequence other, final int ooffset, final int plen) {
440     return CharSequences.regionMatches(t, toffset, other, ooffset, plen);
441   }
442 
443   public static String truncate(@Nonnull final String value, final int length) {
444     if (value.length() > length) {
445       return value.substring(0, length);
446     } else {
447       return value;
448     }
449   }
450 
451   @Nonnull
452   public static String commonPrefix(@Nonnull final CharSequence... strs) {
453     if (strs.length <= 0) {
454       throw new IllegalArgumentException("Must have at least 1 string " + java.util.Arrays.toString(strs));
455      }
456     CharSequence common = strs[0];
457     for (int i = 1; i < strs.length; i++) {
458       common = com.google.common.base.Strings.commonPrefix(common, strs[i]);
459     }
460     return common.toString();
461   }
462 
463 }