Clover icon

Coverage Report

  1. Project Clover database Mon Sep 2 2024 17:57:51 BST
  2. Package jalview.util

File StringUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart9.png
13% of files have more coverage

Code metrics

112
193
20
1
647
426
96
0.5
9.65
20
4.8

Classes

Class Line # Actions
StringUtils 31 193 96
0.8184615481.8%
 

Contributing tests

This file is covered by 333 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.util;
22   
23    import java.io.UnsupportedEncodingException;
24    import java.net.URLEncoder;
25    import java.util.ArrayList;
26    import java.util.List;
27    import java.util.Locale;
28    import java.util.regex.Matcher;
29    import java.util.regex.Pattern;
30   
 
31    public class StringUtils
32    {
33    private static final Pattern DELIMITERS_PATTERN = Pattern
34    .compile(".*='[^']*(?!')");
35   
36    private static final char PERCENT = '%';
37   
38    private static final boolean DEBUG = false;
39   
40    /*
41    * URL encoded characters, indexed by char value
42    * e.g. urlEncodings['='] = urlEncodings[61] = "%3D"
43    */
44    private static String[] urlEncodings = new String[255];
45   
46    /**
47    * Returns a new character array, after inserting characters into the given
48    * character array.
49    *
50    * @param in
51    * the character array to insert into
52    * @param position
53    * the 0-based position for insertion
54    * @param count
55    * the number of characters to insert
56    * @param ch
57    * the character to insert
58    */
 
59  21 toggle public static final char[] insertCharAt(char[] in, int position,
60    int count, char ch)
61    {
62  21 char[] tmp = new char[in.length + count];
63   
64  21 if (position >= in.length)
65    {
66  5 System.arraycopy(in, 0, tmp, 0, in.length);
67  5 position = in.length;
68    }
69    else
70    {
71  16 System.arraycopy(in, 0, tmp, 0, position);
72    }
73   
74  21 int index = position;
75  59 while (count > 0)
76    {
77  38 tmp[index++] = ch;
78  38 count--;
79    }
80   
81  21 if (position < in.length)
82    {
83  16 System.arraycopy(in, position, tmp, index, in.length - position);
84    }
85   
86  21 return tmp;
87    }
88   
89    /**
90    * Delete
91    *
92    * @param in
93    * @param from
94    * @param to
95    * @return
96    */
 
97  183 toggle public static final char[] deleteChars(char[] in, int from, int to)
98    {
99  183 if (from >= in.length || from < 0)
100    {
101  1 return in;
102    }
103   
104  182 char[] tmp;
105   
106  182 if (to >= in.length)
107    {
108  27 tmp = new char[from];
109  27 System.arraycopy(in, 0, tmp, 0, from);
110  27 to = in.length;
111    }
112    else
113    {
114  155 tmp = new char[in.length - to + from];
115  155 System.arraycopy(in, 0, tmp, 0, from);
116  155 System.arraycopy(in, to, tmp, from, in.length - to);
117    }
118  182 return tmp;
119    }
120   
121    /**
122    * Returns the last part of 'input' after the last occurrence of 'token'. For
123    * example to extract only the filename from a full path or URL.
124    *
125    * @param input
126    * @param token
127    * a delimiter which must be in regular expression format
128    * @return
129    */
 
130  6 toggle public static String getLastToken(String input, String token)
131    {
132  6 if (input == null)
133    {
134  2 return null;
135    }
136  4 if (token == null)
137    {
138  1 return input;
139    }
140  3 String[] st = input.split(token);
141  3 return st[st.length - 1];
142    }
143   
144    /**
145    * Parses the input string into components separated by the delimiter. Unlike
146    * String.split(), this method will ignore occurrences of the delimiter which
147    * are nested within single quotes in name-value pair values, e.g. a='b,c'.
148    *
149    * @param input
150    * @param delimiter
151    * @return elements separated by separator
152    */
 
153  13895 toggle public static String[] separatorListToArray(String input,
154    String delimiter)
155    {
156  13895 int seplen = delimiter.length();
157  13895 if (input == null || input.equals("") || input.equals(delimiter))
158    {
159  0 return null;
160    }
161  13895 List<String> jv = new ArrayList<>();
162  13895 int cp = 0, pos, escape;
163  13895 boolean wasescaped = false, wasquoted = false;
164  13895 String lstitem = null;
165  ? while ((pos = input.indexOf(delimiter, cp)) >= cp)
166    {
167  38215 escape = (pos > 0 && input.charAt(pos - 1) == '\\') ? -1 : 0;
168  38215 if (wasescaped || wasquoted)
169    {
170    // append to previous pos
171  4 jv.set(jv.size() - 1, lstitem = lstitem + delimiter
172    + input.substring(cp, pos + escape));
173    }
174    else
175    {
176  38211 jv.add(lstitem = input.substring(cp, pos + escape));
177    }
178  38215 cp = pos + seplen;
179  38215 wasescaped = escape == -1;
180    // last separator may be in an unmatched quote
181  38215 wasquoted = DELIMITERS_PATTERN.matcher(lstitem).matches();
182    }
183  13895 if (cp < input.length())
184    {
185  13895 String c = input.substring(cp);
186  13895 if (wasescaped || wasquoted)
187    {
188    // append final separator
189  2 jv.set(jv.size() - 1, lstitem + delimiter + c);
190    }
191    else
192    {
193  13893 if (!c.equals(delimiter))
194    {
195  13893 jv.add(c);
196    }
197    }
198    }
199  13895 if (jv.size() > 0)
200    {
201  13895 String[] v = jv.toArray(new String[jv.size()]);
202  13895 jv.clear();
203  13895 if (DEBUG)
204    {
205  0 ErrorLog.errPrintln("Array from '" + delimiter
206    + "' separated List:\n" + v.length);
207  0 for (int i = 0; i < v.length; i++)
208    {
209  0 ErrorLog.errPrintln("item " + i + " '" + v[i] + "'");
210    }
211    }
212  13895 return v;
213    }
214  0 if (DEBUG)
215    {
216  0 ErrorLog.errPrintln(
217    "Empty Array from '" + delimiter + "' separated List");
218    }
219  0 return null;
220    }
221   
222    /**
223    * Returns a string which contains the list elements delimited by the
224    * separator. Null items are ignored. If the input is null or has length zero,
225    * a single delimiter is returned.
226    *
227    * @param list
228    * @param separator
229    * @return concatenated string
230    */
 
231  51 toggle public static String arrayToSeparatorList(String[] list, String separator)
232    {
233  51 StringBuffer v = new StringBuffer();
234  51 if (list != null && list.length > 0)
235    {
236  181 for (int i = 0, iSize = list.length; i < iSize; i++)
237    {
238  132 if (list[i] != null)
239    {
240  131 if (v.length() > 0)
241    {
242  82 v.append(separator);
243    }
244    // TODO - escape any separator values in list[i]
245  131 v.append(list[i]);
246    }
247    }
248  49 if (DEBUG)
249    {
250  0 System.err
251    .println("Returning '" + separator + "' separated List:\n");
252  0 ErrorLog.errPrintln(v.toString());
253    }
254  49 return v.toString();
255    }
256  2 if (DEBUG)
257    {
258  0 ErrorLog.errPrintln(
259    "Returning empty '" + separator + "' separated List\n");
260    }
261  2 return "" + separator;
262    }
263   
264    /**
265    * Converts a list to a string with a delimiter before each term except the
266    * first. Returns an empty string given a null or zero-length argument. This
267    * can be replaced with StringJoiner in Java 8.
268    *
269    * @param terms
270    * @param delim
271    * @return
272    */
 
273  56 toggle public static String listToDelimitedString(List<String> terms,
274    String delim)
275    {
276  56 StringBuilder sb = new StringBuilder(32);
277  56 if (terms != null && !terms.isEmpty())
278    {
279  54 boolean appended = false;
280  54 for (String term : terms)
281    {
282  71 if (appended)
283    {
284  17 sb.append(delim);
285    }
286  71 appended = true;
287  71 sb.append(term);
288    }
289    }
290  56 return sb.toString();
291    }
292   
293    /**
294    * Convenience method to parse a string to an integer, returning 0 if the
295    * input is null or not a valid integer
296    *
297    * @param s
298    * @return
299    */
 
300  8 toggle public static int parseInt(String s)
301    {
302  8 int result = 0;
303  8 if (s != null && s.length() > 0)
304    {
305  6 try
306    {
307  6 result = Integer.parseInt(s);
308    } catch (NumberFormatException ex)
309    {
310    }
311    }
312  8 return result;
313    }
314   
315    /**
316    * Compares two versions formatted as e.g. "3.4.5" and returns -1, 0 or 1 as
317    * the first version precedes, is equal to, or follows the second
318    *
319    * @param v1
320    * @param v2
321    * @return
322    */
 
323  15 toggle public static int compareVersions(String v1, String v2)
324    {
325  15 return compareVersions(v1, v2, null);
326    }
327   
328    /**
329    * Compares two versions formatted as e.g. "3.4.5b1" and returns -1, 0 or 1 as
330    * the first version precedes, is equal to, or follows the second
331    *
332    * @param v1
333    * @param v2
334    * @param pointSeparator
335    * a string used to delimit point increments in sub-tokens of the
336    * version
337    * @return
338    */
 
339  172 toggle public static int compareVersions(String v1, String v2,
340    String pointSeparator)
341    {
342  172 if (v1 == null || v2 == null)
343    {
344  2 return 0;
345    }
346  170 String[] toks1 = v1.split("\\.");
347  170 String[] toks2 = v2.split("\\.");
348  170 int i = 0;
349  379 for (; i < toks1.length; i++)
350    {
351  370 if (i >= toks2.length)
352    {
353    /*
354    * extra tokens in v1
355    */
356  3 return 1;
357    }
358  367 String tok1 = toks1[i];
359  367 String tok2 = toks2[i];
360  367 if (pointSeparator != null)
361    {
362    /*
363    * convert e.g. 5b2 into decimal 5.2 for comparison purposes
364    */
365  340 tok1 = tok1.replace(pointSeparator, ".");
366  340 tok2 = tok2.replace(pointSeparator, ".");
367    }
368  367 try
369    {
370  367 float f1 = Float.valueOf(tok1);
371  365 float f2 = Float.valueOf(tok2);
372  363 int comp = Float.compare(f1, f2);
373  363 if (comp != 0)
374    {
375  154 return comp;
376    }
377    } catch (NumberFormatException e)
378    {
379  4 System.err
380    .println("Invalid version format found: " + e.getMessage());
381  4 return 0;
382    }
383    }
384   
385  9 if (i < toks2.length)
386    {
387    /*
388    * extra tokens in v2
389    */
390  3 return -1;
391    }
392   
393    /*
394    * same length, all tokens match
395    */
396  6 return 0;
397    }
398   
399    /**
400    * Converts the string to all lower-case except the first character which is
401    * upper-cased
402    *
403    * @param s
404    * @return
405    */
 
406  75 toggle public static String toSentenceCase(String s)
407    {
408  75 if (s == null)
409    {
410  1 return s;
411    }
412  74 if (s.length() <= 1)
413    {
414  2 return s.toUpperCase(Locale.ROOT);
415    }
416  72 return s.substring(0, 1).toUpperCase(Locale.ROOT)
417    + s.substring(1).toLowerCase(Locale.ROOT);
418    }
419   
420    /**
421    * A helper method that strips off any leading or trailing html and body tags.
422    * If no html tag is found, then also html-encodes angle bracket characters.
423    *
424    * @param text
425    * @return
426    */
 
427  53 toggle public static String stripHtmlTags(String text)
428    {
429  53 if (text == null)
430    {
431  1 return null;
432    }
433  52 String tmp2up = text.toUpperCase(Locale.ROOT);
434  52 int startTag = tmp2up.indexOf("<HTML>");
435  52 if (startTag > -1)
436    {
437  4 text = text.substring(startTag + 6);
438  4 tmp2up = tmp2up.substring(startTag + 6);
439    }
440    // is omission of "<BODY>" intentional here??
441  52 int endTag = tmp2up.indexOf("</BODY>");
442  52 if (endTag > -1)
443    {
444  2 text = text.substring(0, endTag);
445  2 tmp2up = tmp2up.substring(0, endTag);
446    }
447  52 endTag = tmp2up.indexOf("</HTML>");
448  52 if (endTag > -1)
449    {
450  2 text = text.substring(0, endTag);
451    }
452   
453  52 if (startTag == -1 && (text.contains("<") || text.contains(">")))
454    {
455  3 text = text.replaceAll("<", "&lt;");
456  3 text = text.replaceAll(">", "&gt;");
457    }
458  52 return text;
459    }
460   
461    /**
462    * Answers the input string with any occurrences of the 'encodeable'
463    * characters replaced by their URL encoding
464    *
465    * @param s
466    * @param encodable
467    * @return
468    */
 
469  32 toggle public static String urlEncode(String s, String encodable)
470    {
471  32 if (s == null || s.isEmpty())
472    {
473  3 return s;
474    }
475   
476    /*
477    * do % encoding first, as otherwise it may double-encode!
478    */
479  29 if (encodable.indexOf(PERCENT) != -1)
480    {
481  18 s = urlEncode(s, PERCENT);
482    }
483   
484  29 for (char c : encodable.toCharArray())
485    {
486  109 if (c != PERCENT)
487    {
488  91 s = urlEncode(s, c);
489    }
490    }
491  29 return s;
492    }
493   
494    /**
495    * Answers the input string with any occurrences of {@code c} replaced with
496    * their url encoding. Answers the input string if it is unchanged.
497    *
498    * @param s
499    * @param c
500    * @return
501    */
 
502  109 toggle static String urlEncode(String s, char c)
503    {
504  109 String decoded = String.valueOf(c);
505  109 if (s.indexOf(decoded) != -1)
506    {
507  21 String encoded = getUrlEncoding(c);
508  21 if (!encoded.equals(decoded))
509    {
510  19 s = s.replace(decoded, encoded);
511    }
512    }
513  109 return s;
514    }
515   
516    /**
517    * Answers the input string with any occurrences of the specified (unencoded)
518    * characters replaced by their URL decoding.
519    * <p>
520    * Example: {@code urlDecode("a%3Db%3Bc", "-;=,")} should answer
521    * {@code "a=b;c"}.
522    *
523    * @param s
524    * @param encodable
525    * @return
526    */
 
527  451 toggle public static String urlDecode(String s, String encodable)
528    {
529  451 if (s == null || s.isEmpty())
530    {
531  5 return s;
532    }
533   
534  446 for (char c : encodable.toCharArray())
535    {
536  2222 String encoded = getUrlEncoding(c);
537  2222 if (s.indexOf(encoded) != -1)
538    {
539  21 String decoded = String.valueOf(c);
540  21 s = s.replace(encoded, decoded);
541    }
542    }
543  446 return s;
544    }
545   
546    /**
547    * Does a lazy lookup of the url encoding of the given character, saving the
548    * value for repeat lookups
549    *
550    * @param c
551    * @return
552    */
 
553  2243 toggle private static String getUrlEncoding(char c)
554    {
555  2243 if (c < 0 || c >= urlEncodings.length)
556    {
557  0 return String.valueOf(c);
558    }
559   
560  2243 String enc = urlEncodings[c];
561  2243 if (enc == null)
562    {
563  8 try
564    {
565  8 enc = urlEncodings[c] = URLEncoder.encode(String.valueOf(c),
566    "UTF-8");
567    } catch (UnsupportedEncodingException e)
568    {
569  0 enc = urlEncodings[c] = String.valueOf(c);
570    }
571    }
572  2243 return enc;
573    }
574   
 
575  0 toggle public static int firstCharPosIgnoreCase(String text, String chars)
576    {
577  0 int min = text.length() + 1;
578  0 for (char c : chars.toLowerCase(Locale.ROOT).toCharArray())
579    {
580  0 int i = text.toLowerCase(Locale.ROOT).indexOf(c);
581  0 if (0 <= i && i < min)
582    {
583  0 min = i;
584    }
585    }
586  0 return min < text.length() + 1 ? min : -1;
587    }
588   
 
589  69 toggle public static boolean equalsIgnoreCase(String s1, String s2)
590    {
591  69 if (s1 == null || s2 == null)
592    {
593  0 return s1 == s2;
594    }
595  69 return s1.toLowerCase(Locale.ROOT).equals(s2.toLowerCase(Locale.ROOT));
596    }
597   
 
598  3322 toggle public static int indexOfFirstWhitespace(String text)
599    {
600  3322 int index = -1;
601  3322 Pattern pat = Pattern.compile("\\s");
602  3322 Matcher m = pat.matcher(text);
603  3322 if (m.find())
604    {
605  2724 index = m.start();
606    }
607  3322 return index;
608    }
609   
610    /*
611    * implementation of String.replaceLast.
612    * Replaces only the last occurrence of toReplace in string with replacement.
613    */
 
614  0 toggle public static String replaceLast(String string, String toReplace,
615    String replacement)
616    {
617  0 int pos = string.lastIndexOf(toReplace);
618  0 if (pos > -1)
619    {
620  0 return new StringBuilder().append(string.substring(0, pos))
621    .append(replacement)
622    .append(string.substring(pos + toReplace.length()))
623    .toString();
624    }
625    else
626    {
627  0 return string;
628    }
629   
630    }
631   
632    /*
633    * return the maximum length of a List of Strings
634    */
 
635  0 toggle public static int maxLength(List<String> l)
636    {
637  0 int max = 0;
638  0 for (String s : l)
639    {
640  0 if (s == null)
641  0 continue;
642  0 if (s.length() > max)
643  0 max = s.length();
644    }
645  0 return max;
646    }
647    }