OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

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