001/*
002 * JPPF.
003 * Copyright (C) 2005-2018 JPPF Team.
004 * http://www.jppf.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jppf.utils;
019
020import java.io.*;
021import java.net.*;
022import java.nio.charset.Charset;
023import java.text.*;
024import java.util.*;
025import java.util.concurrent.Callable;
026import java.util.regex.Pattern;
027
028
029/**
030 * This class provides a set of utility methods for manipulating strings.
031 * @author Laurent Cohen
032 */
033public final class StringUtils {
034  /**
035   * Charset instance for UTF-8 encoding.
036   */
037  public static final Charset UTF_8 = new Callable<Charset>() {
038    @Override public Charset call() {
039      try {
040        return Charset.forName("UTF-8");
041      } catch(@SuppressWarnings("unused") final Exception e) {
042        return null;
043      }
044    }
045  }.call();
046  /**
047   * Constant for an empty array of Objects.
048   */
049  public static final Object[] ZERO_OBJECT = new Object[0];
050  /**
051   * Constant for an empty array of URLs.
052   */
053  public static final URL[] ZERO_URL = new URL[0];
054  /**
055   * An array of char containing the hex digits in ascending order.
056   */
057  private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
058
059  /**
060   * Instantiation of this class is not permitted.
061   */
062  private StringUtils() {
063  }
064
065  /**
066   * Format a string so that it fits into a string of specified length.<br>
067   * If the string is longer than the specified length, then characters on the left are truncated, otherwise
068   * the specified character is appended to the result on the left  to obtain the appropriate length.
069   * @param source the string to format; if null, it is considered an empty string.
070   * @param padChar the character used to fill the result up to the specified length.
071   * @param maxLen the length of the formatted string.
072   * @return a string formatted to the specified length.
073   */
074  public static String padLeft(final String source, final char padChar, final int maxLen) {
075    final String src = (source == null) ? "" : source;
076    final int length = src.length();
077    if (length > maxLen) return source;
078    final StringBuilder sb = new StringBuilder();
079    for (int i=0; i<maxLen-length; i++) sb.append(padChar);
080    sb.append(src);
081    return sb.toString();
082  }
083
084  /**
085   * Format a string so that it fits into a string of specified length.<br>
086   * If the string is longer than the specified length, then characters on the left are truncated, otherwise
087   * the specified character is appended to the result on the left  to obtain the appropriate length.
088   * @param source the string to format; if null, it is considered an empty string.
089   * @param padChar the character used to fill the result up to the specified length.
090   * @param maxLen the length of the formatted string.
091   * @param truncate if <code>true</code>, then truncate the string if its length is greater than <code>maxLen</code>.
092   * @return a string formatted to the specified length.
093   */
094  public static String padLeft(final String source, final char padChar, final int maxLen, final boolean truncate) {
095    final String src = (source == null) ? "" : source;
096    final int length = src.length();
097    if (length > maxLen) return source;
098    final StringBuilder sb = new StringBuilder();
099    for (int i=0; i<maxLen-length; i++) sb.append(padChar);
100    sb.append(src);
101    return sb.toString();
102  }
103
104  /**
105   * Pads a string on the right side with a given character
106   * If the string is longer than the specified length, then characters on the right are truncated, otherwise
107   * the specified character is appended to the result on the right  to obtain the appropriate length.
108   * @param source the string to pad to the right
109   * @param padChar the character used for padding
110   * @param maxLen the length to pad the string up to
111   * if its length is greater than the padding length
112   * @return the padded (or truncated) string
113   */
114  public static String padRight(final String source, final char padChar, final int maxLen) {
115    return padRight(source, padChar, maxLen, true);
116  }
117
118  /**
119   * Pads a string on the right side with a given character
120   * If the string is longer than the specified length, then characters on the right are truncated, otherwise
121   * the specified character is appended to the result on the right  to obtain the appropriate length.
122   * @param source the string to pad to the right
123   * @param padChar the character used for padding
124   * @param maxLen the length to pad the string up to
125   * if its length is greater than the padding length
126   * @param truncate if <code>true</code>, then truncate the string if its length is greater than <code>maxLen</code>.
127   * @return the padded (or truncated) string
128   */
129  public static String padRight(final String source, final char padChar, final int maxLen, final boolean truncate) {
130    String s = source;
131    if (s == null) s = "";
132    if (s.length() > maxLen) return truncate ? s = s.substring(0, maxLen) : s;
133    final StringBuilder sb = new StringBuilder(s);
134    while (sb.length() < maxLen) sb.append(padChar);
135    return sb.toString();
136  }
137
138  /**
139   * Convert an array of bytes into a string of hexadecimal numbers.<br>
140   * @param bytes the array that contains the sequence of byte values to convert.
141   * @return the converted bytes as a string of space-separated hexadecimal numbers.
142   */
143  public static String toHexString(final byte[] bytes) {
144    return toHexString(bytes, 0, bytes.length, null);
145  }
146
147  /**
148   * Convert a part of an array of bytes, into a string of hexadecimal numbers.
149   * The hex numbers may or may not be separated, depending on the value of the <code>sep</code> parameter.<br>
150   * @param bytes the array that contains the sequence of byte values to convert.
151   * @param start the index to start at in the byte array.
152   * @param length the number of bytes to convert in the array.
153   * @param sep the separator between hexadecimal numbers in the resulting string. If null, then no separator is used.
154   * @return the converted bytes as a string of space-separated hexadecimal numbers.
155   */
156  public static String toHexString(final byte[] bytes, final int start, final int length, final String sep) {
157    final StringBuilder sb = new StringBuilder();
158    if (length >= 0) {
159      final boolean sepNotNull = sep != null;
160      for (int i=start; i<Math.min(bytes.length, start+length); i++) {
161        if (sepNotNull && (i > start)) sb.append(sep);
162        final byte b = bytes[i];
163        sb.append(HEX_DIGITS[(b & 0xF0) >> 4]);
164        sb.append(HEX_DIGITS[b & 0x0F]);
165      }
166    }
167    return sb.toString();
168  }
169
170  /**
171   * Transform a duration in milliseconds into a string with hours, minutes, seconds and milliseconds..
172   * @param duration the duration to transform, expressed in milliseconds.
173   * @return a string specifying the duration in terms of hours, minutes, seconds and milliseconds.
174   */
175  public static String toStringDuration(final long duration) {
176    long elapsed = duration;
177    final StringBuilder sb = new StringBuilder();
178    sb.append(padLeft("" + (elapsed / 3_600_000L), '0', 2)).append(':');
179    elapsed = elapsed % 3_600_000L;
180    sb.append(padLeft("" + (elapsed / 60_000L), '0', 2)).append(':');
181    elapsed = elapsed % 60_000L;
182    sb.append(padLeft("" + (elapsed / 1000L), '0', 2)).append('.');
183    sb.append(padLeft("" + (elapsed % 1000L), '0', 3));
184    return sb.toString();
185  }
186
187  /**
188   * Get a String representation of an array of any type.
189   * @param <T> the type of the array.
190   * @param array the array from which to build a string representation.
191   * @return the array's content as a string.
192   */
193  @SafeVarargs
194  public static <T> String arrayToString(final T...array) {
195    return arrayToString(",", "[", "]", array);
196  }
197
198  /**
199   * Get a String representation of an array of any type.
200   * @param <T> the type of the array.
201   * @param array the array from which to build a string representation.
202   * @param sep the separator to use for values. If null, no separator is used.
203   * @param prefix the prefix to use at the start of the resulting string. If null, no prefix is used.
204   * @param suffix the suffix to use at the end of the resulting string. If null, no suffix is used.
205   * @return the array's content as a string.
206   */
207  @SafeVarargs
208  public static <T> String arrayToString(final String sep, final String prefix, final String suffix, final T...array) {
209    final StringBuilder sb = new StringBuilder();
210    if (array == null) sb.append("null");
211    else {
212      if (prefix != null) sb.append(prefix);
213      for (int i=0; i<array.length; i++) {
214        if ((i > 0) && (sep != null)) sb.append(sep);
215        sb.append(array[i]);
216      }
217      if (suffix != null) sb.append(suffix);
218    }
219    return sb.toString();
220  }
221
222  /**
223   * Parse an array of port numbers from a string containing a list of space-separated port numbers.
224   * @param s list of space-separated port numbers
225   * @return an array of int port numbers.
226   */
227  public static int[] parseIntValues(final String s) {
228    final String[] strPorts = RegexUtils.SPACES_PATTERN.split(s);
229    final int[] ports = new int[strPorts.length];
230    for (int i=0; i<strPorts.length; i++) {
231      try {
232        ports[i] = Integer.valueOf(strPorts[i].trim());
233      } catch(@SuppressWarnings("unused") final NumberFormatException e) {
234        return null;
235      }
236    }
237    return ports;
238  }
239
240  /**
241   * Convert an array of int values into a space-separated string.
242   * @param ports list of port numbers
243   * @return a space-separated list of ports.
244   */
245  public static String buildString(final int[] ports) {
246    if ((ports == null) || (ports.length == 0)) return "";
247    final StringBuilder sb = new StringBuilder();
248    for (int i=0; i<ports.length; i++) {
249      if (i > 0) sb.append(' ');
250      sb.append(ports[i]);
251    }
252    return sb.toString();
253  }
254
255  /**
256   * Get a String representation of an array of any type.
257   * @param array the array from which to build a string representation.
258   * @param sep the separator to use for values. If null, no separator is used.
259   * @param prefix the prefix to use at the start of the resulting string. If null, no prefix is used.
260   * @param suffix the suffix to use at the end of the resulting string. If null, no suffix is used.
261   * @return the array's content as a string.
262   */
263  public static String buildString(final String sep, final String prefix, final String suffix, final int[] array) {
264    final StringBuilder sb = new StringBuilder();
265    if (array == null) sb.append("null");
266    else {
267      if (prefix != null) sb.append(prefix);
268      for (int i=0; i<array.length; i++) {
269        if ((i > 0) && (sep != null)) sb.append(sep);
270        sb.append(array[i]);
271      }
272      if (suffix != null) sb.append(suffix);
273    }
274    return sb.toString();
275  }
276
277  /**
278   * Build a string made of the specified tokens.
279   * @param args the tokens composing the string.
280   * @return the concatenation of the string values of the tokens.
281   */
282  public static String build(final Object...args) {
283    if (args == null) return null;
284    final StringBuilder sb = new StringBuilder();
285    for (Object o: args) sb.append(o);
286    return sb.toString();
287  }
288
289  /**
290   * Determine whether the specified source string starts with one of the specified values.
291   * @param source the string to match with the values.
292   * @param ignoreCase specifies whether case should be ignore in the string matching.
293   * @param values the values to match the source with.
294   * @return true if the source matches one of the values, false otherwise.
295   */
296  public static boolean startsWithOneOf(final String source, final boolean ignoreCase, final String...values) {
297    if ((source == null) || (values == null)) return false;
298    final String s = ignoreCase ? source.toLowerCase(): source;
299    for (String val: values) {
300      if (val == null) continue;
301      final String s2 = ignoreCase ? val.toLowerCase() : val;
302      if (s.startsWith(s2)) return true;
303    }
304    return false;
305  }
306
307  /**
308   * Determine whether the specified source string is equal to one of the specified values.
309   * @param source the string to match with the values.
310   * @param ignoreCase specifies whether case should be ignore in the string matching.
311   * @param values the values to match the source with.
312   * @return true if the source matches one of the values, false otherwise.
313   */
314  public static boolean isOneOf(final String source, final boolean ignoreCase, final String...values) {
315    if ((source == null) || (values == null)) return false;
316    final String s = ignoreCase ? source.toLowerCase(): source;
317    for (String val: values) {
318      if (val == null) continue;
319      final String s2 = ignoreCase ? val.toLowerCase() : val;
320      if (s.equals(s2)) return true;
321    }
322    return false;
323  }
324
325  /**
326   * Determine whether the specified source string contains one of the specified values.
327   * @param source the string to match with the values.
328   * @param ignoreCase specifies whether case should be ignore in the string matching.
329   * @param values the values to match the source with.
330   * @return true if the source matches one of the values, false otherwise.
331   */
332  public static boolean hasOneOf(final String source, final boolean ignoreCase, final String...values) {
333    if ((source == null) || (values == null)) return false;
334    final String s = ignoreCase ? source.toLowerCase(): source;
335    for (String val: values) {
336      if (val == null) continue;
337      final String s2 = ignoreCase ? val.toLowerCase() : val;
338      if (s.contains(s2)) return true;
339    }
340    return false;
341  }
342
343  /**
344   * Print a top-down representation of a class loader hierarchy into a string.
345   * @param leafClassLoader the class loader at the bottom of the hierarchy.
346   * @return a string representation of the class loader hierarchy.
347   */
348  public static String printClassLoaderHierarchy(final ClassLoader leafClassLoader) {
349    final StringBuilder sb = new StringBuilder();
350    ClassLoader cl = leafClassLoader;
351    if (cl != null) {
352      sb.append("class loader hierarchy:\n");
353      final Stack<String> stack = new Stack<>();
354      while (cl != null) {
355        if ("org.jppf.classloader.AbstractJPPFClassLoader".equals(cl.getClass().getName())) stack.push(cl.toString());
356        else if (cl instanceof URLClassLoader) stack.push(toString((URLClassLoader) cl));
357        else  stack.push(cl.toString());
358        cl = cl.getParent();
359      }
360      int count = 0;
361      while (!stack.isEmpty()) {
362        for (int i=0; i<2*count; i++) sb.append(' ');
363        sb.append(stack.pop());
364        if (!stack.isEmpty()) sb.append('\n');
365        count++;
366      }
367    }
368    return sb.toString();
369  }
370
371  /**
372   * Print a representation of a <code>URLClassLoader</code> into a string.
373   * The resulting string includes the class loader's classpath.
374   * @param cl the classloader to print.
375   * @return a string representation of the input class loader.
376   */
377  public static String toString(final URLClassLoader cl) {
378    final StringBuilder sb = new StringBuilder();
379    sb.append(cl.getClass().getSimpleName()).append("[classpath=");
380    final URL[] urls = cl.getURLs();
381    if ((urls != null) && (urls.length > 0)) {
382      for (int i=0; i<urls.length; i++) {
383        if (i > 0) sb.append(';');
384        sb.append(urls[i]);
385      }
386    }
387    return sb.append(']').toString();
388  }
389
390  /**
391   * Parse a Number from a String.
392   * @param source the string to parse.
393   * @param def the default value to return if the source cannot be parsed.
394   * @return the source parsed as a number or <code>def</code> if it could not be parsed as a number.
395   */
396  public static Number parseNumber(final String source, final Number def) {
397    if (source == null) return def;
398    final NumberFormat nf = NumberFormat.getInstance();
399    try {
400      return nf.parse(source);
401    } catch (@SuppressWarnings("unused") final ParseException ignore) {
402    }
403    return null;
404  }
405
406  /**
407   * Return a String in the format &lt;object class name&gt;@hashcode for the specified object.
408   * @param obj the object for which to get a string.
409   * @return an identity string, or "null" if the object is null.
410   */
411  public static String toIdentityString(final Object obj) {
412    if (obj == null) return "null";
413    return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj));
414  }
415
416  /**
417   * Parse the specified source string into a list of strings according to the specified separator.
418   * @param source the string to parse.
419   * @param separator the delimiter for the resulting strings; it can be a regex.
420   * @return a list of strings, possibly empty but never null;
421   */
422  public static List<String> parseStrings(final String source, final String separator) {
423    return parseStrings(source, separator, true);
424  }
425
426  /**
427   * Parse the specified source string into a list of strings according to the specified separator.
428   * @param source the string to parse.
429   * @param separator the delimiter for the resulting strings.
430   * @param regex if {@code true} then {@code separator} is interpreted as a regex, otherwise it is considered a string litteral.
431   * @return a list of strings, possibly empty but never null;
432   */
433  public static List<String> parseStrings(final String source, final String separator, final boolean regex) {
434    final List<String> list = new ArrayList<>();
435    if (source != null) {
436      if (separator == null) list.add(source);
437      else {
438        final Pattern pattern = regex ? Pattern.compile(separator) : Pattern.compile(separator, Pattern.LITERAL);
439        final String[] tokens = pattern.split(source);
440        for (String token: tokens) {
441          if (token == null) continue;
442          final String s = token.trim();
443          if (!s.isEmpty()) list.add(s);
444        }
445      }
446    }
447    return list;
448  }
449
450  /**
451   * Parse the specified source string into a list of strings according to the specified separator.
452   * @param source the string to parse.
453   * @param separator the delimiter for the resulting strings; it can be a regex.
454   * @return a list of strings, possibly empty but never null;
455   */
456  public static String[] parseStringArray(final String source, final String separator) {
457    return parseStringArray(source, separator, true);
458  }
459
460  /**
461   * Parse the specified source string into a list of strings according to the specified separator.
462   * @param source the string to parse.
463   * @param separator the delimiter for the resulting strings; it can be a regex.
464   * @param regex if {@code true} then {@code separator} is interpreted as a regex, otherwise it is considered a string litteral.
465   * @return a list of strings, possibly empty but never null;
466   */
467  public static String[] parseStringArray(final String source, final String separator, final boolean regex) {
468    final List<String> list = parseStrings(source, separator, regex);
469    return list.toArray(new String[list.size()]);
470  }
471
472  /**
473   * Transform a file path into a URL in non URL-ncoded form.
474   * @param url the URL to transform.
475   * @return a string representing the decoded URL.
476   */
477  public static String getDecodedURLPath(final URL url) {
478    try {
479      return URLDecoder.decode(url.getPath(), "UTF-8");
480    } catch (@SuppressWarnings("unused") final UnsupportedEncodingException ignore) {
481      return null;
482    }
483  }
484}