OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Rev 182 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 /*
15
 * Créé le 3 mars 2005
16
 */
17
package org.openconcerto.utils;
18
 
83 ilm 19
import java.awt.FontMetrics;
20
import java.math.BigDecimal;
19 ilm 21
import java.nio.charset.Charset;
177 ilm 22
import java.text.Normalizer;
23
import java.text.Normalizer.Form;
17 ilm 24
import java.util.ArrayList;
25
import java.util.Collections;
26
import java.util.HashMap;
27
import java.util.HashSet;
83 ilm 28
import java.util.Iterator;
17 ilm 29
import java.util.LinkedHashMap;
30
import java.util.List;
177 ilm 31
import java.util.Locale;
17 ilm 32
import java.util.Map;
33
import java.util.Set;
174 ilm 34
import java.util.regex.Matcher;
28 ilm 35
import java.util.regex.Pattern;
17 ilm 36
 
177 ilm 37
import com.ibm.icu.lang.UCharacter;
38
 
17 ilm 39
/**
40
 * @author Sylvain CUAZ
41
 */
42
public class StringUtils {
43
 
80 ilm 44
    // required encoding see Charset
45
    public static final Charset UTF8 = Charset.forName("UTF-8");
46
    public static final Charset UTF16 = Charset.forName("UTF-16");
47
    public static final Charset ASCII = Charset.forName("US-ASCII");
48
    public static final Charset ISO8859_1 = Charset.forName("ISO-8859-1");
49
    // included in rt.jar see
28 ilm 50
    // http://docs.oracle.com/javase/7/docs/technotes/guides/intl/encoding.doc.html
80 ilm 51
    public static final Charset ISO8859_15 = Charset.forName("ISO-8859-15");
28 ilm 52
    public static final Charset Cp1252 = Charset.forName("Cp1252");
53
    public static final Charset Cp850 = Charset.forName("Cp850");
19 ilm 54
 
144 ilm 55
    public static final char BOM = '\ufeff';
56
 
93 ilm 57
    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
58
 
17 ilm 59
    /**
60
     * Retourne la chaine avec la première lettre en majuscule et le reste en minuscule.
61
     *
62
     * @param s la chaîne à transformer.
63
     * @return la chaine avec la première lettre en majuscule et le reste en minuscule.
64
     */
65
    public static String firstUpThenLow(String s) {
66
        if (s.length() == 0) {
67
            return s;
68
        }
69
        if (s.length() == 1) {
70
            return s.toUpperCase();
71
        }
72
        return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
73
    }
74
 
75
    public static String firstUp(String s) {
76
        if (s.length() == 0) {
77
            return s;
78
        }
79
        if (s.length() == 1) {
80
            return s.toUpperCase();
81
        }
82
        return s.substring(0, 1).toUpperCase() + s.substring(1);
83
    }
84
 
19 ilm 85
    static public abstract class Shortener {
86
 
87
        private final int hashSize;
88
        private final int hashPartSize;
89
        private final String prefix;
90
        private final String suffix;
91
        private final int minStringLength;
92
 
93
        protected Shortener(int hashSize, String prefix, String suffix, int minCharsBeforeAndAfter) {
94
            super();
95
            this.hashSize = hashSize;
96
            this.prefix = prefix;
97
            this.suffix = suffix;
98
            this.hashPartSize = this.hashSize + this.prefix.length() + this.suffix.length();
99
            if (minCharsBeforeAndAfter < 1)
100
                throw new IllegalArgumentException("minCharsBeforeAndAfter must be at least 1: " + minCharsBeforeAndAfter);
101
            this.minStringLength = this.hashPartSize + minCharsBeforeAndAfter * 2;
102
        }
103
 
104
        public final int getMinStringLength() {
105
            return this.minStringLength;
106
        }
107
 
108
        public final String getBoundedLengthString(final String s, final int maxLength) {
109
            // don't test first for s.length, it's more predictable
110
            // (otherwise boundedString("a", 2) would succeed)
111
            if (maxLength < this.getMinStringLength())
112
                throw new IllegalArgumentException("Maximum too low : " + maxLength + "<" + getMinStringLength());
113
            if (s.length() <= maxLength)
114
                return s;
115
            else
116
                return this.shorten(s, maxLength);
117
        }
118
 
119
        final String shorten(final String s, final int maxLength) {
120
            assert s.length() >= this.getMinStringLength();
121
            final int toRemoveLength = s.length() - maxLength + this.hashPartSize;
122
            // remove the middle part of encoded
123
            final int toRemoveStartIndex = s.length() / 2 - toRemoveLength / 2;
124
            final String toHash = s.substring(toRemoveStartIndex, toRemoveStartIndex + toRemoveLength);
125
 
126
            final String hash = shorten(toHash);
127
            assert this.hashSize == hash.length();
128
 
129
            final String res = s.substring(0, toRemoveStartIndex) + this.prefix + hash + this.suffix + s.substring(toRemoveStartIndex + toRemoveLength);
130
            assert res.length() == maxLength;
131
            return res;
132
        }
133
 
134
        protected abstract String shorten(String s);
135
 
136
        static public final Shortener Ellipsis = new Shortener(1, "", "", 1) {
137
            @Override
138
            protected String shorten(String s) {
139
                return "…";
140
            }
141
        };
142
 
143
        // String.hashCode() is an int written in hex
144
        static public final Shortener JavaHashCode = new Shortener(Integer.SIZE / 8 * 2, "#", "#", 3) {
145
            @Override
146
            protected String shorten(String s) {
147
                return MessageDigestUtils.asHex(MessageDigestUtils.int2bytes(s.hashCode()));
148
            }
149
        };
150
 
151
        // 128 bits written in hex
152
        static public final Shortener MD5 = new Shortener(128 / 8 * 2, "#", "#", 11) {
153
            @Override
154
            protected String shorten(String s) {
155
                return MessageDigestUtils.getHashString(MessageDigestUtils.getMD5(), s.getBytes(UTF8));
156
            }
157
        };
158
 
159
        // order descendant by getMinStringLength()
160
        static final Shortener[] ORDERED = new Shortener[] { MD5, JavaHashCode, Ellipsis };
161
    }
162
 
163
    /**
164
     * The minimum value for {@link #getBoundedLengthString(String, int)}.
165
     *
166
     * @return the minimum value for <code>maxLength</code>.
167
     */
168
    public static final int getLeastMaximum() {
169
        return Shortener.ORDERED[Shortener.ORDERED.length - 1].getMinStringLength();
170
    }
171
 
172
    private static final Shortener getShortener(final int l) {
173
        for (final Shortener sh : Shortener.ORDERED) {
174
            if (l >= sh.getMinStringLength())
175
                return sh;
176
        }
177
        return null;
178
    }
179
 
180
    /**
181
     * Return a string built from <code>s</code> that is at most <code>maxLength</code> long.
182
     *
183
     * @param s the string to bound.
184
     * @param maxLength the maximum length the result must have.
185
     * @return a string built from <code>s</code>.
186
     * @throws IllegalArgumentException if <code>maxLength</code> is too small.
187
     * @see #getLeastMaximum()
188
     * @see Shortener#getBoundedLengthString(String, int)
189
     */
190
    public static final String getBoundedLengthString(final String s, final int maxLength) throws IllegalArgumentException {
191
        // don't test first for s.length, it's more predictable
192
        // (otherwise boundedString("a", 2) would succeed)
193
        if (maxLength < getLeastMaximum())
194
            throw new IllegalArgumentException("Maximum too low : " + maxLength + "<" + getLeastMaximum());
195
 
196
        final String res;
197
        if (s.length() <= maxLength) {
198
            res = s;
199
        } else {
200
            // use maxLength to choose the shortener since it's generally a constant
201
            // and thus the strings returned by this method have the same pattern
202
            res = getShortener(maxLength).shorten(s, maxLength);
203
        }
204
        return res;
205
    }
206
 
80 ilm 207
    static public enum Side {
208
        LEFT, RIGHT
209
    }
210
 
211
    public static String getFixedWidthString(final String s, final int width, final Side align) {
212
        return getFixedWidthString(s, width, align, false);
213
    }
214
 
132 ilm 215
    public static String getFixedWidthString(final String s, final int width, final Side align, final boolean allowShorten) {
80 ilm 216
        final int length = s.length();
217
        final String res;
218
        if (length == width) {
219
            res = s;
220
        } else if (length < width) {
132 ilm 221
            // we already tested length, so no need to allow shorten
222
            res = appendFixedWidthString(new StringBuilder(width), s, width, align, ' ', false).toString();
223
        } else {
224
            res = getTooWideString(s, width, allowShorten);
225
        }
226
        assert res.length() == width;
227
        return res;
228
    }
229
 
230
    private static String getTooWideString(final String s, final int width, final boolean allowShorten) {
231
        assert s.length() > width;
232
        if (!allowShorten)
233
            throw new IllegalArgumentException("Too wide : " + s.length() + " > " + width);
234
        return getBoundedLengthString(s, width);
235
    }
236
 
237
    public static StringBuilder appendFixedWidthString(final StringBuilder sb, final String s, final int width, final Side align, final char filler, final boolean allowShorten) {
238
        final int origBuilderLen = sb.length();
239
        final int length = s.length();
240
        if (length <= width) {
241
            sb.ensureCapacity(origBuilderLen + width);
80 ilm 242
            if (align == Side.LEFT)
243
                sb.append(s);
132 ilm 244
            for (int i = length; i < width; i++) {
245
                sb.append(filler);
80 ilm 246
            }
247
            if (align == Side.RIGHT)
248
                sb.append(s);
249
        } else {
132 ilm 250
            sb.append(getTooWideString(s, width, allowShorten));
80 ilm 251
        }
132 ilm 252
        assert sb.length() == origBuilderLen + width;
253
        return sb;
80 ilm 254
    }
255
 
17 ilm 256
    public static final List<String> fastSplit(final String string, final char sep) {
257
        final List<String> l = new ArrayList<String>();
258
        final int length = string.length();
259
        final char[] cars = string.toCharArray();
260
        int rfirst = 0;
261
 
262
        for (int i = 0; i < length; i++) {
263
            if (cars[i] == sep) {
264
                l.add(new String(cars, rfirst, i - rfirst));
265
                rfirst = i + 1;
266
            }
267
        }
268
 
269
        if (rfirst < length) {
270
            l.add(new String(cars, rfirst, length - rfirst));
271
        }
272
        return l;
273
    }
274
 
83 ilm 275
    public static final List<String> fastSplitTrimmed(final String string, final char sep) {
276
        final List<String> l = new ArrayList<String>();
277
        final int length = string.length();
278
        final char[] cars = string.toCharArray();
279
        int rfirst = 0;
280
 
281
        for (int i = 0; i < length; i++) {
282
            if (cars[i] == sep) {
283
                l.add(new String(cars, rfirst, i - rfirst).trim());
284
                rfirst = i + 1;
285
            }
286
        }
287
 
288
        if (rfirst < length) {
289
            l.add(new String(cars, rfirst, length - rfirst).trim());
290
        }
291
        return l;
292
    }
293
 
19 ilm 294
    /**
295
     * Split une string s tous les nbCharMaxLine
296
     *
297
     * @param s
298
     * @param nbCharMaxLine
299
     * @return
300
     */
301
    public static String splitString(String s, int nbCharMaxLine) {
302
 
303
        if (s == null) {
304
            return s;
305
        }
306
 
307
        if (s.trim().length() < nbCharMaxLine) {
308
            return s;
309
        }
310
        StringBuffer lastString = new StringBuffer();
311
        StringBuffer result = new StringBuffer();
312
        for (int i = 0; i < s.length(); i++) {
313
 
314
            if (lastString.length() == nbCharMaxLine) {
315
                int esp = lastString.lastIndexOf(" ");
25 ilm 316
                if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') {
19 ilm 317
                    result.append("\n");
318
                }
319
                if (esp > 0) {
320
                    result.append(lastString.substring(0, esp).toString().trim());
321
                    lastString = new StringBuffer(lastString.substring(esp, lastString.length()));
322
                } else {
323
                    result.append(lastString.toString().trim());
324
                    lastString = new StringBuffer();
325
                }
25 ilm 326
                result.append("\n");
19 ilm 327
            }
328
 
329
            char charAt = s.charAt(i);
330
            if (charAt == '\n') {
331
                lastString.append(charAt);
21 ilm 332
                result.append(lastString);
19 ilm 333
                lastString = new StringBuffer();
334
            } else {
335
                lastString.append(charAt);
336
            }
337
        }
338
 
25 ilm 339
        if (result.length() > 0 && result.charAt(result.length() - 1) != '\n') {
19 ilm 340
            result.append("\n");
341
        }
342
 
343
        result.append(lastString.toString().trim());
344
 
345
        return result.toString();
346
    }
347
 
83 ilm 348
    static public int firstIndexOf(final String s, final char[] chars) {
349
        return firstIndexOf(s, 0, chars);
350
    }
351
 
352
    static public int firstIndexOf(final String s, final int offset, final char[] chars) {
353
        int res = -1;
354
        for (final char c : chars) {
355
            final int index = s.indexOf(c, offset);
356
            if (index >= 0 && (res == -1 || index < res))
357
                res = index;
358
        }
359
        return res;
360
    }
361
 
61 ilm 362
    static private final Pattern quotePatrn = Pattern.compile("\"", Pattern.LITERAL);
363
    static private final Pattern slashPatrn = Pattern.compile("(\\\\+)");
28 ilm 364
 
365
    static public String doubleQuote(String s) {
142 ilm 366
        return doubleQuote(s, true);
367
    }
368
 
369
    static public String doubleQuote(String s, final boolean escapeEscapeChar) {
61 ilm 370
        // http://developer.apple.com/library/mac/#documentation/applescript/conceptual/applescriptlangguide/reference/ASLR_classes.html#//apple_ref/doc/uid/TP40000983-CH1g-SW6
371
        // http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.5
372
        // https://developer.mozilla.org/en/JavaScript/Guide/Values%2C_Variables%2C_and_Literals#Escaping_characters
28 ilm 373
        if (s.length() > 0) {
142 ilm 374
            if (escapeEscapeChar)
375
                s = slashPatrn.matcher(s).replaceAll("$1$1");
61 ilm 376
            s = quotePatrn.matcher(s).replaceAll("\\\\\"");
28 ilm 377
        }
378
        return '"' + s + '"';
379
    }
380
 
83 ilm 381
    /**
382
     * Unquote a double quoted string.
383
     *
384
     * @param s the string to unquote, e.g. "foo\\bar".
385
     * @return the unquoted form, e.g. foo\bar.
386
     * @throws IllegalArgumentException if the string is not quoted, or if there's some extra
387
     *         content at the end.
388
     */
389
    static public String unDoubleQuote(String s) {
390
        final Tuple2<String, Integer> res = unDoubleQuote(s, 0);
391
        if (res.get1().intValue() != s.length())
392
            throw new IllegalArgumentException("Extra content at the end : " + s.substring(res.get1()));
393
        return res.get0();
394
    }
395
 
396
    /**
397
     * Unquote part of a double quoted string.
398
     *
399
     * @param s the string to unquote, e.g. pre"foo\\bar"post.
400
     * @param offset the start index of the quotes, e.g. 3.
401
     * @return the unquoted form and the index after the end quote, e.g. foo\bar and 13.
402
     * @throws IllegalArgumentException if the string is not quoted.
403
     */
404
    static public Tuple2<String, Integer> unDoubleQuote(String s, int offset) {
405
        if (s.charAt(offset) != '"')
406
            throw new IllegalArgumentException("Expected quote but got : " + s.charAt(offset));
407
        final int l = s.length();
408
        if (offset + 1 < l && s.charAt(offset + 1) == '"')
409
            return Tuple2.create("", offset + 2);
410
 
411
        offset++;
412
        final char[] chars = new char[] { '"', '\\' };
413
        final StringBuilder sb = new StringBuilder(512);
414
        boolean foundEnd = false;
415
        while (offset < l && !foundEnd) {
416
            final int index = firstIndexOf(s, offset, chars);
417
            if (index < 0)
418
                throw new IllegalArgumentException("End quote not found after " + offset);
419
            sb.append(s.substring(offset, index));
420
            if (s.charAt(index) == '"') {
421
                offset = index + 1;
422
                foundEnd = true;
423
            } else {
424
                assert s.charAt(index) == '\\';
425
                sb.append(s.charAt(index + 1));
426
                offset = index + 2;
427
            }
428
        }
429
        if (!foundEnd)
430
            throw new IllegalArgumentException("End quote not found after " + offset);
431
        return Tuple2.create(sb.toString(), offset);
432
    }
433
 
17 ilm 434
    public static final class Escaper {
435
 
436
        // eg '
437
        private final char esc;
438
 
439
        // eg { '=> S, " => D}
440
        private final Map<Character, Character> substitution;
441
        private final Map<Character, Character> inv;
442
 
443
        /**
444
         * A new escaper that will have <code>esc</code> as escape character.
445
         *
446
         * @param esc the escape character, eg '
447
         * @param name the character that will be appended to <code>esc</code>, eg with S all
448
         *        occurrences of ' will be replaced by 'S
449
         */
450
        public Escaper(char esc, char name) {
451
            super();
452
            this.esc = esc;
453
            this.substitution = new LinkedHashMap<Character, Character>();
454
            this.inv = new HashMap<Character, Character>();
455
            this.add(esc, name);
456
        }
457
 
458
        public Escaper add(char toRemove, char escapedName) {
459
            if (this.inv.containsKey(escapedName))
460
                throw new IllegalArgumentException(escapedName + " already replaces " + this.inv.get(escapedName));
461
            this.substitution.put(toRemove, escapedName);
462
            this.inv.put(escapedName, toRemove);
463
            return this;
464
        }
465
 
466
        public final Set<Character> getEscapedChars() {
467
            final Set<Character> res = new HashSet<Character>(this.substitution.keySet());
468
            res.remove(this.esc);
469
            return res;
470
        }
471
 
472
        /**
473
         * Escape <code>s</code>, so that the resulting string has none of
474
         * {@link #getEscapedChars()}.
475
         *
476
         * @param s a string to escape.
477
         * @return the escaped form.
478
         */
479
        public final String escape(String s) {
480
            String res = s;
481
            // this.esc en premier
482
            for (final Character toEsc : this.substitution.keySet()) {
483
                // use Pattern.LITERAL to avoid interpretion
484
                res = res.replace(toEsc + "", getEscaped(toEsc));
485
            }
486
            return res;
487
        }
488
 
489
        private String getEscaped(final Character toEsc) {
490
            return this.esc + "" + this.substitution.get(toEsc);
491
        }
492
 
493
        public final String unescape(String escaped) {
494
            String res = escaped;
495
            final List<Character> toEscs = new ArrayList<Character>(this.substitution.keySet());
496
            Collections.reverse(toEscs);
497
            for (final Character toEsc : toEscs) {
498
                res = res.replaceAll(getEscaped(toEsc), toEsc + "");
499
            }
500
            return res;
501
        }
502
 
503
        @Override
504
        public boolean equals(Object obj) {
505
            if (obj instanceof Escaper) {
506
                final Escaper o = (Escaper) obj;
507
                return this.esc == o.esc && this.substitution.equals(o.substitution);
508
            } else
509
                return false;
510
        }
511
 
512
        @Override
513
        public int hashCode() {
514
            return this.esc + this.substitution.hashCode();
515
        }
516
    }
517
 
25 ilm 518
    public static String rightAlign(String s, int width) {
519
        String r = s;
520
        int n = width - s.length();
521
        for (int i = 0; i < n; i++) {
522
            r = ' ' + r;
523
        }
524
        return r;
525
    }
526
 
527
    public static String leftAlign(String s, int width) {
528
        String r = s;
529
        int n = width - s.length();
530
        for (int i = 0; i < n; i++) {
531
            r += ' ';
532
        }
533
        return r;
534
    }
73 ilm 535
 
536
    public static String trim(final String s, final boolean leading) {
537
        // from String.trim()
538
        int end = s.length();
539
        int st = 0;
540
 
541
        if (leading) {
542
            while ((st < end) && (s.charAt(st) <= ' ')) {
543
                st++;
544
            }
545
        } else {
546
            while ((st < end) && (s.charAt(end - 1) <= ' ')) {
547
                end--;
548
            }
549
        }
550
        return ((st > 0) || (end < s.length())) ? s.substring(st, end) : s;
551
    }
80 ilm 552
 
553
    public static String limitLength(String s, int maxLength) {
554
        if (s.length() <= maxLength) {
555
            return s;
556
        }
557
        return s.substring(0, maxLength);
558
    }
559
 
560
    public static String removeAllSpaces(String text) {
561
        final int length = text.length();
562
        final StringBuilder builder = new StringBuilder(length);
563
        for (int i = 0; i < length; i++) {
564
            char c = text.charAt(i);
565
            if (c <= ' ' && c != 160) {
566
                // remove non printable chars
567
                // spaces
568
                // non breakable space (160)
569
                builder.append(c);
570
            }
571
        }
572
        return builder.toString();
573
    }
574
 
575
    public static String removeNonDecimalChars(String text) {
576
        final int length = text.length();
577
        final StringBuilder builder = new StringBuilder(length);
578
        for (int i = 0; i < length; i++) {
579
            char c = text.charAt(i);
580
            if (Character.isDigit(c) || c == '.' || c == '+' || c == '-') {
581
                builder.append(c);
582
            }
583
        }
584
        return builder.toString();
585
    }
83 ilm 586
 
587
    public static BigDecimal getBigDecimalFromUserText(String text) {
588
        text = text.trim();
589
        if (text.isEmpty() || text.equals("-")) {
590
            return BigDecimal.ZERO;
591
        }
592
        text = removeNonDecimalChars(text);
593
        BigDecimal result = null;
594
        try {
595
            result = new BigDecimal(text);
596
        } catch (Exception e) {
597
            Log.get().info(text + " is not a valid decimal");
598
        }
599
        return result;
600
    }
601
 
602
    /**
603
     * Returns an array of strings, one for each line in the string after it has been wrapped to fit
604
     * lines of <var>maxWidth</var>. Lines end with any of cr, lf, or cr lf. A line ending at the
605
     * end of the string will not output a further, empty string.
606
     * <p>
607
     * This code assumes <var>str</var> is not <code>null</code>.
608
     *
609
     * @param str the string to split
610
     * @param fm needed for string width calculations
611
     * @param maxWidth the max line width, in points
612
     * @return a non-empty list of strings
613
     */
614
    public static List<String> wrap(String str, FontMetrics fm, int maxWidth) {
615
        List<String> lines = splitIntoLines(str);
616
        if (lines.size() == 0)
617
            return lines;
618
 
619
        List<String> strings = new ArrayList<String>();
620
        for (Iterator<String> iter = lines.iterator(); iter.hasNext();) {
621
            wrapLineInto(iter.next(), strings, fm, maxWidth);
622
        }
623
        return strings;
624
    }
625
 
626
    /**
627
     * Given a line of text and font metrics information, wrap the line and add the new line(s) to
628
     * <var>list</var>.
629
     *
630
     * @param line a line of text
631
     * @param list an output list of strings
632
     * @param fm font metrics
633
     * @param maxWidth maximum width of the line(s)
634
     */
635
    public static void wrapLineInto(String line, List<String> list, FontMetrics fm, int maxWidth) {
636
        int len = line.length();
637
        int width;
638
        while (len > 0 && (width = fm.stringWidth(line)) > maxWidth) {
639
            // Guess where to split the line. Look for the next space before
640
            // or after the guess.
641
            int guess = len * maxWidth / width;
642
            String before = line.substring(0, guess).trim();
643
 
644
            width = fm.stringWidth(before);
645
            int pos;
646
            if (width > maxWidth) // Too long
647
                pos = findBreakBefore(line, guess);
648
            else { // Too short or possibly just right
649
                pos = findBreakAfter(line, guess);
650
                if (pos != -1) { // Make sure this doesn't make us too long
651
                    before = line.substring(0, pos).trim();
652
                    if (fm.stringWidth(before) > maxWidth)
653
                        pos = findBreakBefore(line, guess);
654
                }
655
            }
656
            if (pos == -1)
657
                pos = guess; // Split in the middle of the word
658
 
659
            list.add(line.substring(0, pos).trim());
660
            line = line.substring(pos).trim();
661
            len = line.length();
662
        }
663
        if (len > 0) {
664
            list.add(line);
665
        }
666
    }
667
 
668
    /**
669
     * Returns the index of the first whitespace character or '-' in <var>line</var> that is at or
670
     * before <var>start</var>. Returns -1 if no such character is found.
671
     *
672
     * @param line a string
673
     * @param start where to star looking
674
     */
675
    public static int findBreakBefore(String line, int start) {
676
        for (int i = start; i >= 0; --i) {
677
            char c = line.charAt(i);
678
            if (Character.isWhitespace(c) || c == '-')
679
                return i;
680
        }
681
        return -1;
682
    }
683
 
684
    /**
685
     * Returns the index of the first whitespace character or '-' in <var>line</var> that is at or
686
     * after <var>start</var>. Returns -1 if no such character is found.
687
     *
688
     * @param line a string
689
     * @param start where to star looking
690
     */
691
    public static int findBreakAfter(String line, int start) {
692
        int len = line.length();
693
        for (int i = start; i < len; ++i) {
694
            char c = line.charAt(i);
695
            if (Character.isWhitespace(c) || c == '-')
696
                return i;
697
        }
698
        return -1;
699
    }
700
 
701
    /**
702
     * Returns an array of strings, one for each line in the string. Lines end with any of cr, lf,
703
     * or cr lf. A line ending at the end of the string will not output a further, empty string.
704
     * <p>
705
     * This code assumes <var>str</var> is not <code>null</code>.
706
     *
707
     * @param str the string to split
708
     * @return a non-empty list of strings
709
     */
710
    public static List<String> splitIntoLines(String str) {
711
        List<String> strings = new ArrayList<String>();
712
 
713
        int len = str.length();
714
        if (len == 0) {
715
            strings.add("");
716
            return strings;
717
        }
718
 
719
        int lineStart = 0;
720
 
721
        for (int i = 0; i < len; ++i) {
722
            char c = str.charAt(i);
723
            if (c == '\r') {
724
                int newlineLength = 1;
725
                if ((i + 1) < len && str.charAt(i + 1) == '\n')
726
                    newlineLength = 2;
727
                strings.add(str.substring(lineStart, i));
728
                lineStart = i + newlineLength;
729
                if (newlineLength == 2) // skip \n next time through loop
730
                    ++i;
731
            } else if (c == '\n') {
732
                strings.add(str.substring(lineStart, i));
733
                lineStart = i + 1;
734
            }
735
        }
736
        if (lineStart < len)
737
            strings.add(str.substring(lineStart));
738
 
739
        return strings;
740
    }
741
 
93 ilm 742
    /**
743
     * convert a byte array to its hexa representation
132 ilm 744
     */
93 ilm 745
    public static String bytesToHexString(byte[] bytes) {
746
        final int length = bytes.length;
747
        char[] hexChars = new char[length * 2];
748
        for (int j = 0; j < length; j++) {
749
            int v = bytes[j] & 0xFF;
750
            hexChars[j * 2] = hexArray[v >>> 4];
751
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
752
        }
753
        return new String(hexChars);
754
    }
142 ilm 755
 
756
    /**
757
     * Whether the parameter is empty.
758
     *
759
     * @param s the string to test.
760
     * @return <code>true</code> if <code>null</code> or {@link String#isEmpty() empty}.
761
     */
762
    public static boolean isEmpty(final String s) {
763
        return isEmpty(s, false);
764
    }
765
 
766
    public static boolean isEmpty(final String s, final boolean trim) {
767
        return s == null || (trim ? s.trim() : s).isEmpty();
768
    }
769
 
770
    /**
771
     * Return the first parameter that is non-empty.
772
     *
773
     * @param s1 the string to test.
774
     * @param s2 the alternate value.
775
     * @return <code>s1</code> if not <code>null</code> and not {@link String#isEmpty() empty},
776
     *         <code>s2</code> otherwise.
777
     */
778
    public static String coalesce(String s1, String s2) {
779
        return isEmpty(s1) ? s2 : s1;
780
    }
781
 
782
    /**
783
     * Return the first value that is non-empty.
784
     *
785
     * @param values values to test for emptiness.
786
     * @return the first value that is neither <code>null</code> nor {@link String#isEmpty() empty}.
787
     */
788
    public static String coalesce(String... values) {
789
        return coalesce(false, values);
790
    }
791
 
792
    public static String coalesce(final boolean trim, String... values) {
793
        for (final String s : values)
794
            if (!isEmpty(s, trim))
795
                return s;
796
        return null;
797
    }
149 ilm 798
 
799
    public static String toAsciiString(String str) {
800
        if (str == null) {
801
            return null;
802
        }
803
        final int length = str.length();
804
        final StringBuilder b = new StringBuilder(length);
805
        for (int i = 0; i < length; i++) {
806
            final char c = str.charAt(i);
807
            final char newChar;
808
            if (c < 128) {
809
                newChar = c;
810
            } else if (c == 'é' || c == 'è' || c == 'ê') {
811
                newChar = 'e';
812
            } else if (c == 'â' || c == 'à') {
813
                newChar = 'a';
814
            } else if (c == 'î') {
815
                newChar = 'i';
816
            } else if (c == 'ù' || c == 'û') {
817
                newChar = 'u';
818
            } else if (c == 'ô') {
819
                newChar = 'o';
820
            } else if (c == 'ç') {
821
                newChar = 'c';
822
            } else {
823
                newChar = ' ';
824
            }
825
            b.append(newChar);
826
        }
827
        return b.toString();
828
    }
174 ilm 829
 
830
    public static final Matcher findFirstContaining(final String s, final Pattern... patterns) {
831
        for (final Pattern p : patterns) {
832
            final Matcher matcher = p.matcher(s);
833
            if (matcher.find())
834
                return matcher;
835
        }
836
        return null;
837
    }
177 ilm 838
 
839
    static public interface Search {
840
        public boolean equals(String str, String anotherString);
841
 
842
        public boolean startsWith(String str, String prefix);
843
 
844
        public boolean endsWith(String str, String suffix);
845
 
846
        public boolean contains(String str, String anotherString);
847
    }
848
 
849
    static public final Search EXACT_SEARCH = new Search() {
850
        @Override
851
        public boolean startsWith(String str, String prefix) {
852
            return str.startsWith(prefix);
853
        }
854
 
855
        @Override
856
        public boolean equals(String str, String anotherString) {
857
            return str.equals(anotherString);
858
        }
859
 
860
        @Override
861
        public boolean endsWith(String str, String suffix) {
862
            return str.endsWith(suffix);
863
        }
864
 
865
        @Override
866
        public boolean contains(String str, String searchStr) {
867
            return str.contains(searchStr);
868
        }
869
    };
870
 
871
    // Simple (Single-Character) Case Mapping
872
    static public final Search SIMPLE_CASE_MAPPING_SEARCH = new Search() {
873
        @Override
874
        public boolean startsWith(String str, String prefix) {
875
            return str.regionMatches(true, 0, prefix, 0, prefix.length());
876
        }
877
 
878
        @Override
879
        public boolean equals(String str, String anotherString) {
880
            return str.equalsIgnoreCase(anotherString);
881
        }
882
 
883
        @Override
884
        public boolean endsWith(String str, String suffix) {
885
            final int suffixLength = suffix.length();
886
            return str.regionMatches(true, str.length() - suffixLength, suffix, 0, suffixLength);
887
        }
888
 
889
        @Override
890
        public boolean contains(String str, String searchStr) {
891
            final int length = searchStr.length();
892
            if (length == 0)
893
                return true;
894
 
895
            for (int i = str.length() - length; i >= 0; i--) {
896
                if (str.regionMatches(true, i, searchStr, 0, length))
897
                    return true;
898
            }
899
            return false;
900
        }
901
    };
902
 
903
    static public abstract class NormalizeSearch implements Search {
904
 
905
        protected abstract String normalize(String s);
906
 
907
        @Override
908
        public boolean startsWith(String str, String prefix) {
909
            return normalize(str).startsWith(normalize(prefix));
910
        }
911
 
912
        @Override
913
        public boolean equals(String str, String anotherString) {
914
            return normalize(str).equals(normalize(anotherString));
915
        }
916
 
917
        @Override
918
        public boolean endsWith(String str, String suffix) {
919
            return normalize(str).endsWith(normalize(suffix));
920
        }
921
 
922
        @Override
923
        public boolean contains(String str, String searchStr) {
924
            return normalize(str).contains(normalize(searchStr));
925
        }
926
    };
927
 
928
    // Fixed Locale
929
    static public final class LowerCaseMappingSearch extends NormalizeSearch {
930
 
931
        private final Locale locale;
932
 
933
        public LowerCaseMappingSearch(Locale locale) {
934
            super();
935
            this.locale = locale;
936
        }
937
 
938
        public final Locale getLocale() {
939
            return this.locale;
940
        }
941
 
942
        @Override
943
        protected String normalize(String s) {
944
            return s.toLowerCase(this.getLocale());
945
        }
946
    };
947
 
948
    // Locale.getDefault() at the time of the call
949
    static public final Search DEFAULT_LOWERCASE_MAPPING_SEARCH = new NormalizeSearch() {
950
        @Override
951
        protected String normalize(String s) {
952
            return s.toLowerCase();
953
        }
954
    };
955
 
956
    static public final Search CASE_FOLDING_SEARCH = new NormalizeSearch() {
957
        @Override
958
        protected String normalize(String s) {
959
            return normalizeCase(s);
960
        }
961
    };
962
 
963
    static private final Pattern BLANK_PATTERN = Pattern.compile("\\p{Blank}+");
964
 
965
    // replace tabs and multiple spaces by one space
966
    static public final String normalizeBlanks(String s) {
967
        return BLANK_PATTERN.matcher(s.trim()).replaceAll(" ");
968
    }
969
 
970
    static private final Pattern DIACRITICAL_PATTERN = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
971
 
972
    static public String removeDiacritical(final String s) {
973
        return DIACRITICAL_PATTERN.matcher(Normalizer.normalize(s, Form.NFD)).replaceAll("");
974
    }
975
 
976
    static public final String normalizeCase(final String s) {
977
        // the JRE only does Single-Character Case Mapping so it can't handle Tschüß/TSCHÜSS
978
        // http://userguide.icu-project.org/transforms/casemappings
979
        // https://unicode.org/faq/casemap_charprop.html#casemap
980
        return UCharacter.foldCase(s, true);
981
    }
982
 
983
    static public final String normalizeForSearch(final String s) {
984
        return normalizeCase(removeDiacritical(normalizeBlanks(s)));
985
    }
17 ilm 986
}