OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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