Clover icon

Coverage Report

  1. Project Clover database Wed Dec 3 2025 16:47:11 GMT
  2. Package jalview.util

File StringUtils.java

 

Coverage histogram

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

Code metrics

114
202
23
1
687
453
101
0.5
8.78
23
4.39

Classes

Class Line # Actions
StringUtils 33 202 101
0.82005982%
 

Contributing tests

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