Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 20:42:03 GMT
  2. Package jalview.util

File StringUtils.java

 

Coverage histogram

../../img/srcFileCovDistChart8.png
21% of files have more coverage

Code metrics

116
212
22
1
695
466
102
0.48
9.64
22
4.64

Classes

Class Line # Actions
StringUtils 36 212 102
0.7676%
 

Contributing tests

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