Clover icon

Coverage Report

  1. Project Clover database Wed Sep 17 2025 10:52:37 BST
  2. Package jalview.util

File StringUtils.java

 

Coverage histogram

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

Code metrics

112
207
23
1
688
459
102
0.49
9
23
4.43

Classes

Class Line # Actions
StringUtils 35 207 102
0.818713481.9%
 

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