Update spike branch to latest
[jalview.git] / src / jalview / util / StringUtils.java
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.util.ArrayList;
24 import java.util.List;
25 import java.util.regex.Pattern;
26
27 public class StringUtils
28 {
29   private static final Pattern DELIMITERS_PATTERN = Pattern
30           .compile(".*='[^']*(?!')");
31
32   private static final boolean DEBUG = false;
33
34   /**
35    * Returns a new character array, after inserting characters into the given
36    * character array.
37    * 
38    * @param in
39    *          the character array to insert into
40    * @param position
41    *          the 0-based position for insertion
42    * @param count
43    *          the number of characters to insert
44    * @param ch
45    *          the character to insert
46    */
47   public static final char[] insertCharAt(char[] in, int position,
48           int count, char ch)
49   {
50     char[] tmp = new char[in.length + count];
51
52     if (position >= in.length)
53     {
54       System.arraycopy(in, 0, tmp, 0, in.length);
55       position = in.length;
56     }
57     else
58     {
59       System.arraycopy(in, 0, tmp, 0, position);
60     }
61
62     int index = position;
63     while (count > 0)
64     {
65       tmp[index++] = ch;
66       count--;
67     }
68
69     if (position < in.length)
70     {
71       System.arraycopy(in, position, tmp, index, in.length - position);
72     }
73
74     return tmp;
75   }
76
77   /**
78    * Delete
79    * 
80    * @param in
81    * @param from
82    * @param to
83    * @return
84    */
85   public static final char[] deleteChars(char[] in, int from, int to)
86   {
87     if (from >= in.length || from < 0)
88     {
89       return in;
90     }
91
92     char[] tmp;
93
94     if (to >= in.length)
95     {
96       tmp = new char[from];
97       System.arraycopy(in, 0, tmp, 0, from);
98       to = in.length;
99     }
100     else
101     {
102       tmp = new char[in.length - to + from];
103       System.arraycopy(in, 0, tmp, 0, from);
104       System.arraycopy(in, to, tmp, from, in.length - to);
105     }
106     return tmp;
107   }
108
109   /**
110    * Parses the input string into components separated by the delimiter. Unlike
111    * String.split(), this method will ignore occurrences of the delimiter which
112    * are nested within single quotes in name-value pair values, e.g. a='b,c'.
113    * 
114    * @param input
115    * @param delimiter
116    * @return elements separated by separator
117    */
118   public static String[] separatorListToArray(String input,
119           String delimiter)
120   {
121     int seplen = delimiter.length();
122     if (input == null || input.equals("") || input.equals(delimiter))
123     {
124       return null;
125     }
126     List<String> jv = new ArrayList<>();
127     int cp = 0, pos, escape;
128     boolean wasescaped = false, wasquoted = false;
129     String lstitem = null;
130     while ((pos = input.indexOf(delimiter, cp)) >= cp)
131     {
132       escape = (pos > 0 && input.charAt(pos - 1) == '\\') ? -1 : 0;
133       if (wasescaped || wasquoted)
134       {
135         // append to previous pos
136         jv.set(jv.size() - 1, lstitem = lstitem + delimiter
137                 + input.substring(cp, pos + escape));
138       }
139       else
140       {
141         jv.add(lstitem = input.substring(cp, pos + escape));
142       }
143       cp = pos + seplen;
144       wasescaped = escape == -1;
145       // last separator may be in an unmatched quote
146       wasquoted = DELIMITERS_PATTERN.matcher(lstitem).matches();
147     }
148     if (cp < input.length())
149     {
150       String c = input.substring(cp);
151       if (wasescaped || wasquoted)
152       {
153         // append final separator
154         jv.set(jv.size() - 1, lstitem + delimiter + c);
155       }
156       else
157       {
158         if (!c.equals(delimiter))
159         {
160           jv.add(c);
161         }
162       }
163     }
164     if (jv.size() > 0)
165     {
166       String[] v = jv.toArray(new String[jv.size()]);
167       jv.clear();
168       if (DEBUG)
169       {
170         System.err.println("Array from '" + delimiter
171                 + "' separated List:\n" + v.length);
172         for (int i = 0; i < v.length; i++)
173         {
174           System.err.println("item " + i + " '" + v[i] + "'");
175         }
176       }
177       return v;
178     }
179     if (DEBUG)
180     {
181       System.err.println(
182               "Empty Array from '" + delimiter + "' separated List");
183     }
184     return null;
185   }
186
187   /**
188    * Returns a string which contains the list elements delimited by the
189    * separator. Null items are ignored. If the input is null or has length zero,
190    * a single delimiter is returned.
191    * 
192    * @param list
193    * @param separator
194    * @return concatenated string
195    */
196   public static String arrayToSeparatorList(String[] list, String separator)
197   {
198     StringBuffer v = new StringBuffer();
199     if (list != null && list.length > 0)
200     {
201       for (int i = 0, iSize = list.length; i < iSize; i++)
202       {
203         if (list[i] != null)
204         {
205           if (v.length() > 0)
206           {
207             v.append(separator);
208           }
209           // TODO - escape any separator values in list[i]
210           v.append(list[i]);
211         }
212       }
213       if (DEBUG)
214       {
215         System.err
216                 .println("Returning '" + separator + "' separated List:\n");
217         System.err.println(v);
218       }
219       return v.toString();
220     }
221     if (DEBUG)
222     {
223       System.err.println(
224               "Returning empty '" + separator + "' separated List\n");
225     }
226     return "" + separator;
227   }
228
229   /**
230    * Converts a list to a string with a delimiter before each term except the
231    * first. Returns an empty string given a null or zero-length argument. This
232    * can be replaced with StringJoiner in Java 8.
233    * 
234    * @param terms
235    * @param delim
236    * @return
237    */
238   public static String listToDelimitedString(List<String> terms,
239           String delim)
240   {
241     StringBuilder sb = new StringBuilder(32);
242     if (terms != null && !terms.isEmpty())
243     {
244       boolean appended = false;
245       for (String term : terms)
246       {
247         if (appended)
248         {
249           sb.append(delim);
250         }
251         appended = true;
252         sb.append(term);
253       }
254     }
255     return sb.toString();
256   }
257
258   /**
259    * Convenience method to parse a string to an integer, returning 0 if the
260    * input is null or not a valid integer
261    * 
262    * @param s
263    * @return
264    */
265   public static int parseInt(String s)
266   {
267     int result = 0;
268     if (s != null && s.length() > 0)
269     {
270       try
271       {
272         result = Integer.parseInt(s);
273       } catch (NumberFormatException ex)
274       {
275       }
276     }
277     return result;
278   }
279
280   /**
281    * Compares two versions formatted as e.g. "3.4.5" and returns -1, 0 or 1 as
282    * the first version precedes, is equal to, or follows the second
283    * 
284    * @param v1
285    * @param v2
286    * @return
287    */
288   public static int compareVersions(String v1, String v2)
289   {
290     return compareVersions(v1, v2, null);
291   }
292
293   /**
294    * Compares two versions formatted as e.g. "3.4.5b1" and returns -1, 0 or 1 as
295    * the first version precedes, is equal to, or follows the second
296    * 
297    * @param v1
298    * @param v2
299    * @param pointSeparator
300    *          a string used to delimit point increments in sub-tokens of the
301    *          version
302    * @return
303    */
304   public static int compareVersions(String v1, String v2,
305           String pointSeparator)
306   {
307     if (v1 == null || v2 == null)
308     {
309       return 0;
310     }
311     String[] toks1 = v1.split("\\.");
312     String[] toks2 = v2.split("\\.");
313     int i = 0;
314     for (; i < toks1.length; i++)
315     {
316       if (i >= toks2.length)
317       {
318         /*
319          * extra tokens in v1
320          */
321         return 1;
322       }
323       String tok1 = toks1[i];
324       String tok2 = toks2[i];
325       if (pointSeparator != null)
326       {
327         /*
328          * convert e.g. 5b2 into decimal 5.2 for comparison purposes
329          */
330         tok1 = tok1.replace(pointSeparator, ".");
331         tok2 = tok2.replace(pointSeparator, ".");
332       }
333       try
334       {
335         float f1 = Float.valueOf(tok1);
336         float f2 = Float.valueOf(tok2);
337         int comp = Float.compare(f1, f2);
338         if (comp != 0)
339         {
340           return comp;
341         }
342       } catch (NumberFormatException e)
343       {
344         System.err
345                 .println("Invalid version format found: " + e.getMessage());
346         return 0;
347       }
348     }
349
350     if (i < toks2.length)
351     {
352       /*
353        * extra tokens in v2 
354        */
355       return -1;
356     }
357
358     /*
359      * same length, all tokens match
360      */
361     return 0;
362   }
363
364   /**
365    * Converts the string to all lower-case except the first character which is
366    * upper-cased
367    * 
368    * @param s
369    * @return
370    */
371   public static String toSentenceCase(String s)
372   {
373     if (s == null)
374     {
375       return s;
376     }
377     if (s.length() <= 1)
378     {
379       return s.toUpperCase();
380     }
381     return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
382   }
383
384   /**
385    * A helper method that strips off any leading or trailing html and body tags.
386    * If no html tag is found, then also html-encodes angle bracket characters.
387    * 
388    * @param text
389    * @return
390    */
391   public static String stripHtmlTags(String text)
392   {
393     if (text == null)
394     {
395       return null;
396     }
397     String tmp2up = text.toUpperCase();
398     int startTag = tmp2up.indexOf("<HTML>");
399     if (startTag > -1)
400     {
401       text = text.substring(startTag + 6);
402       tmp2up = tmp2up.substring(startTag + 6);
403     }
404     // is omission of "<BODY>" intentional here??
405     int endTag = tmp2up.indexOf("</BODY>");
406     if (endTag > -1)
407     {
408       text = text.substring(0, endTag);
409       tmp2up = tmp2up.substring(0, endTag);
410     }
411     endTag = tmp2up.indexOf("</HTML>");
412     if (endTag > -1)
413     {
414       text = text.substring(0, endTag);
415     }
416   
417     if (startTag == -1 && (text.contains("<") || text.contains(">")))
418     {
419       text = text.replaceAll("<", "&lt;");
420       text = text.replaceAll(">", "&gt;");
421     }
422     return text;
423   }
424 }