JAL-2089 patch broken merge to master for Release 2.10.0b1
[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    * Returns the last part of 'input' after the last occurrence of 'token'. For
111    * example to extract only the filename from a full path or URL.
112    * 
113    * @param input
114    * @param token
115    *          a delimiter which must be in regular expression format
116    * @return
117    */
118   public static String getLastToken(String input, String token)
119   {
120     if (input == null)
121     {
122       return null;
123     }
124     if (token == null)
125     {
126       return input;
127     }
128     String[] st = input.split(token);
129     return st[st.length - 1];
130   }
131
132   /**
133    * Parses the input string into components separated by the delimiter. Unlike
134    * String.split(), this method will ignore occurrences of the delimiter which
135    * are nested within single quotes in name-value pair values, e.g. a='b,c'.
136    * 
137    * @param input
138    * @param delimiter
139    * @return elements separated by separator
140    */
141   public static String[] separatorListToArray(String input, String delimiter)
142   {
143     int seplen = delimiter.length();
144     if (input == null || input.equals("") || input.equals(delimiter))
145     {
146       return null;
147     }
148     List<String> jv = new ArrayList<String>();
149     int cp = 0, pos, escape;
150     boolean wasescaped = false, wasquoted = false;
151     String lstitem = null;
152     while ((pos = input.indexOf(delimiter, cp)) >= cp)
153     {
154       escape = (pos > 0 && input.charAt(pos - 1) == '\\') ? -1 : 0;
155       if (wasescaped || wasquoted)
156       {
157         // append to previous pos
158         jv.set(jv.size() - 1,
159                 lstitem = lstitem + delimiter
160                         + input.substring(cp, pos + escape));
161       }
162       else
163       {
164         jv.add(lstitem = input.substring(cp, pos + escape));
165       }
166       cp = pos + seplen;
167       wasescaped = escape == -1;
168       // last separator may be in an unmatched quote
169       wasquoted = DELIMITERS_PATTERN.matcher(lstitem).matches();
170     }
171     if (cp < input.length())
172     {
173       String c = input.substring(cp);
174       if (wasescaped || wasquoted)
175       {
176         // append final separator
177         jv.set(jv.size() - 1, lstitem + delimiter + c);
178       }
179       else
180       {
181         if (!c.equals(delimiter))
182         {
183           jv.add(c);
184         }
185       }
186     }
187     if (jv.size() > 0)
188     {
189       String[] v = jv.toArray(new String[jv.size()]);
190       jv.clear();
191       if (DEBUG)
192       {
193         System.err.println("Array from '" + delimiter
194                 + "' separated List:\n" + v.length);
195         for (int i = 0; i < v.length; i++)
196         {
197           System.err.println("item " + i + " '" + v[i] + "'");
198         }
199       }
200       return v;
201     }
202     if (DEBUG)
203     {
204       System.err.println("Empty Array from '" + delimiter
205               + "' separated List");
206     }
207     return null;
208   }
209
210   /**
211    * Returns a string which contains the list elements delimited by the
212    * separator. Null items are ignored. If the input is null or has length zero,
213    * a single delimiter is returned.
214    * 
215    * @param list
216    * @param separator
217    * @return concatenated string
218    */
219   public static String arrayToSeparatorList(String[] list, String separator)
220   {
221     StringBuffer v = new StringBuffer();
222     if (list != null && list.length > 0)
223     {
224       for (int i = 0, iSize = list.length; i < iSize; i++)
225       {
226         if (list[i] != null)
227         {
228           if (v.length() > 0)
229           {
230             v.append(separator);
231           }
232           // TODO - escape any separator values in list[i]
233           v.append(list[i]);
234         }
235       }
236       if (DEBUG)
237       {
238         System.err.println("Returning '" + separator
239                 + "' separated List:\n");
240         System.err.println(v);
241       }
242       return v.toString();
243     }
244     if (DEBUG)
245     {
246       System.err.println("Returning empty '" + separator
247               + "' separated List\n");
248     }
249     return "" + separator;
250   }
251
252   /**
253    * Converts a list to a string with a delimiter before each term except the
254    * first. Returns an empty string given a null or zero-length argument. This
255    * can be replaced with StringJoiner in Java 8.
256    * 
257    * @param terms
258    * @param delim
259    * @return
260    */
261   public static String listToDelimitedString(List<String> terms,
262           String delim)
263   {
264     StringBuilder sb = new StringBuilder(32);
265     if (terms != null && !terms.isEmpty())
266     {
267       boolean appended = false;
268       for (String term : terms)
269       {
270         if (appended)
271         {
272           sb.append(delim);
273         }
274         appended = true;
275         sb.append(term);
276       }
277     }
278     return sb.toString();
279   }
280
281   /**
282    * Convenience method to parse a string to an integer, returning 0 if the
283    * input is null or not a valid integer
284    * 
285    * @param s
286    * @return
287    */
288   public static int parseInt(String s)
289   {
290     int result = 0;
291     if (s != null && s.length() > 0)
292     {
293       try
294       {
295         result = Integer.parseInt(s);
296       } catch (NumberFormatException ex)
297       {
298       }
299     }
300     return result;
301   }
302
303   /**
304    * Compares two versions formatted as e.g. "3.4.5" and returns -1, 0 or 1 as
305    * the first version precedes, is equal to, or follows the second
306    * 
307    * @param v1
308    * @param v2
309    * @return
310    */
311   public static int compareVersions(String v1, String v2)
312   {
313     return compareVersions(v1, v2, null);
314   }
315
316   /**
317    * Compares two versions formatted as e.g. "3.4.5b1" and returns -1, 0 or 1 as
318    * the first version precedes, is equal to, or follows the second
319    * 
320    * @param v1
321    * @param v2
322    * @param pointSeparator
323    *          a string used to delimit point increments in sub-tokens of the
324    *          version
325    * @return
326    */
327   public static int compareVersions(String v1, String v2,
328           String pointSeparator)
329   {
330     if (v1 == null || v2 == null)
331     {
332       return 0;
333     }
334     String[] toks1 = v1.split("\\.");
335     String[] toks2 = v2.split("\\.");
336     int i = 0;
337     for (; i < toks1.length; i++)
338     {
339       if (i >= toks2.length)
340       {
341         /*
342          * extra tokens in v1
343          */
344         return 1;
345       }
346       String tok1 = toks1[i];
347       String tok2 = toks2[i];
348       if (pointSeparator != null)
349       {
350         /*
351          * convert e.g. 5b2 into decimal 5.2 for comparison purposes
352          */
353         tok1 = tok1.replace(pointSeparator, ".");
354         tok2 = tok2.replace(pointSeparator, ".");
355       }
356       try
357       {
358         float f1 = Float.valueOf(tok1);
359         float f2 = Float.valueOf(tok2);
360         int comp = Float.compare(f1, f2);
361         if (comp != 0)
362         {
363           return comp;
364         }
365       } catch (NumberFormatException e)
366       {
367         System.err.println("Invalid version format found: "
368                 + e.getMessage());
369         return 0;
370       }
371     }
372
373     if (i < toks2.length)
374     {
375       /*
376        * extra tokens in v2 
377        */
378       return -1;
379     }
380
381     /*
382      * same length, all tokens match
383      */
384     return 0;
385   }
386
387   /**
388    * Converts the string to all lower-case except the first character which is
389    * upper-cased
390    * 
391    * @param s
392    * @return
393    */
394   public static String toSentenceCase(String s)
395   {
396     if (s == null)
397     {
398       return s;
399     }
400     if (s.length() <= 1)
401     {
402       return s.toUpperCase();
403     }
404     return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
405   }
406 }