StringUtils.java

  1. /*
  2.  * Copyright (C) 2009-2022, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.util;

  11. import java.text.MessageFormat;
  12. import java.util.Collection;

  13. import org.eclipse.jgit.annotations.NonNull;
  14. import org.eclipse.jgit.internal.JGitText;
  15. import org.eclipse.jgit.lib.Constants;

  16. /**
  17.  * Miscellaneous string comparison utility methods.
  18.  */
  19. public final class StringUtils {

  20.     private static final long KiB = 1024;

  21.     private static final long MiB = 1024 * KiB;

  22.     private static final long GiB = 1024 * MiB;

  23.     private static final char[] LC;

  24.     static {
  25.         LC = new char['Z' + 1];
  26.         for (char c = 0; c < LC.length; c++)
  27.             LC[c] = c;
  28.         for (char c = 'A'; c <= 'Z'; c++)
  29.             LC[c] = (char) ('a' + (c - 'A'));
  30.     }

  31.     private StringUtils() {
  32.         // Do not create instances
  33.     }

  34.     /**
  35.      * Convert the input to lowercase.
  36.      * <p>
  37.      * This method does not honor the JVM locale, but instead always behaves as
  38.      * though it is in the US-ASCII locale. Only characters in the range 'A'
  39.      * through 'Z' are converted. All other characters are left as-is, even if
  40.      * they otherwise would have a lowercase character equivalent.
  41.      *
  42.      * @param c
  43.      *            the input character.
  44.      * @return lowercase version of the input.
  45.      */
  46.     public static char toLowerCase(char c) {
  47.         return c <= 'Z' ? LC[c] : c;
  48.     }

  49.     /**
  50.      * Convert the input string to lower case, according to the "C" locale.
  51.      * <p>
  52.      * This method does not honor the JVM locale, but instead always behaves as
  53.      * though it is in the US-ASCII locale. Only characters in the range 'A'
  54.      * through 'Z' are converted, all other characters are left as-is, even if
  55.      * they otherwise would have a lowercase character equivalent.
  56.      *
  57.      * @param in
  58.      *            the input string. Must not be null.
  59.      * @return a copy of the input string, after converting characters in the
  60.      *         range 'A'..'Z' to 'a'..'z'.
  61.      */
  62.     public static String toLowerCase(String in) {
  63.         final StringBuilder r = new StringBuilder(in.length());
  64.         for (int i = 0; i < in.length(); i++)
  65.             r.append(toLowerCase(in.charAt(i)));
  66.         return r.toString();
  67.     }


  68.     /**
  69.      * Borrowed from commons-lang <code>StringUtils.capitalize()</code> method.
  70.      *
  71.      * <p>
  72.      * Capitalizes a String changing the first letter to title case as per
  73.      * {@link java.lang.Character#toTitleCase(char)}. No other letters are
  74.      * changed.
  75.      * </p>
  76.      * <p>
  77.      * A <code>null</code> input String returns <code>null</code>.
  78.      * </p>
  79.      *
  80.      * @param str
  81.      *            the String to capitalize, may be null
  82.      * @return the capitalized String, <code>null</code> if null String input
  83.      * @since 4.0
  84.      */
  85.     public static String capitalize(String str) {
  86.         int strLen;
  87.         if (str == null || (strLen = str.length()) == 0) {
  88.             return str;
  89.         }
  90.         return new StringBuilder(strLen)
  91.                 .append(Character.toTitleCase(str.charAt(0)))
  92.                 .append(str.substring(1)).toString();
  93.     }

  94.     /**
  95.      * Test if two strings are equal, ignoring case.
  96.      * <p>
  97.      * This method does not honor the JVM locale, but instead always behaves as
  98.      * though it is in the US-ASCII locale.
  99.      *
  100.      * @param a
  101.      *            first string to compare.
  102.      * @param b
  103.      *            second string to compare.
  104.      * @return true if a equals b
  105.      */
  106.     public static boolean equalsIgnoreCase(String a, String b) {
  107.         if (References.isSameObject(a, b)) {
  108.             return true;
  109.         }
  110.         if (a.length() != b.length())
  111.             return false;
  112.         for (int i = 0; i < a.length(); i++) {
  113.             if (toLowerCase(a.charAt(i)) != toLowerCase(b.charAt(i)))
  114.                 return false;
  115.         }
  116.         return true;
  117.     }

  118.     /**
  119.      * Compare two strings, ignoring case.
  120.      * <p>
  121.      * This method does not honor the JVM locale, but instead always behaves as
  122.      * though it is in the US-ASCII locale.
  123.      *
  124.      * @param a
  125.      *            first string to compare.
  126.      * @param b
  127.      *            second string to compare.
  128.      * @since 2.0
  129.      * @return an int.
  130.      */
  131.     public static int compareIgnoreCase(String a, String b) {
  132.         for (int i = 0; i < a.length() && i < b.length(); i++) {
  133.             int d = toLowerCase(a.charAt(i)) - toLowerCase(b.charAt(i));
  134.             if (d != 0)
  135.                 return d;
  136.         }
  137.         return a.length() - b.length();
  138.     }

  139.     /**
  140.      * Compare two strings, honoring case.
  141.      * <p>
  142.      * This method does not honor the JVM locale, but instead always behaves as
  143.      * though it is in the US-ASCII locale.
  144.      *
  145.      * @param a
  146.      *            first string to compare.
  147.      * @param b
  148.      *            second string to compare.
  149.      * @since 2.0
  150.      * @return an int.
  151.      */
  152.     public static int compareWithCase(String a, String b) {
  153.         for (int i = 0; i < a.length() && i < b.length(); i++) {
  154.             int d = a.charAt(i) - b.charAt(i);
  155.             if (d != 0)
  156.                 return d;
  157.         }
  158.         return a.length() - b.length();
  159.     }

  160.     /**
  161.      * Parse a string as a standard Git boolean value. See
  162.      * {@link #toBooleanOrNull(String)}.
  163.      *
  164.      * @param stringValue
  165.      *            the string to parse.
  166.      * @return the boolean interpretation of {@code value}.
  167.      * @throws java.lang.IllegalArgumentException
  168.      *             if {@code value} is not recognized as one of the standard
  169.      *             boolean names.
  170.      */
  171.     public static boolean toBoolean(String stringValue) {
  172.         if (stringValue == null)
  173.             throw new NullPointerException(JGitText.get().expectedBooleanStringValue);

  174.         final Boolean bool = toBooleanOrNull(stringValue);
  175.         if (bool == null)
  176.             throw new IllegalArgumentException(MessageFormat.format(JGitText.get().notABoolean, stringValue));

  177.         return bool.booleanValue();
  178.     }

  179.     /**
  180.      * Parse a string as a standard Git boolean value.
  181.      * <p>
  182.      * The terms {@code yes}, {@code true}, {@code 1}, {@code on} can all be
  183.      * used to mean {@code true}.
  184.      * <p>
  185.      * The terms {@code no}, {@code false}, {@code 0}, {@code off} can all be
  186.      * used to mean {@code false}.
  187.      * <p>
  188.      * Comparisons ignore case, via {@link #equalsIgnoreCase(String, String)}.
  189.      *
  190.      * @param stringValue
  191.      *            the string to parse.
  192.      * @return the boolean interpretation of {@code value} or null in case the
  193.      *         string does not represent a boolean value
  194.      */
  195.     public static Boolean toBooleanOrNull(String stringValue) {
  196.         if (stringValue == null)
  197.             return null;

  198.         if (equalsIgnoreCase("yes", stringValue) //$NON-NLS-1$
  199.                 || equalsIgnoreCase("true", stringValue) //$NON-NLS-1$
  200.                 || equalsIgnoreCase("1", stringValue) //$NON-NLS-1$
  201.                 || equalsIgnoreCase("on", stringValue)) //$NON-NLS-1$
  202.             return Boolean.TRUE;
  203.         else if (equalsIgnoreCase("no", stringValue) //$NON-NLS-1$
  204.                 || equalsIgnoreCase("false", stringValue) //$NON-NLS-1$
  205.                 || equalsIgnoreCase("0", stringValue) //$NON-NLS-1$
  206.                 || equalsIgnoreCase("off", stringValue)) //$NON-NLS-1$
  207.             return Boolean.FALSE;
  208.         else
  209.             return null;
  210.     }

  211.     /**
  212.      * Join a collection of Strings together using the specified separator.
  213.      *
  214.      * @param parts
  215.      *            Strings to join
  216.      * @param separator
  217.      *            used to join
  218.      * @return a String with all the joined parts
  219.      */
  220.     public static String join(Collection<String> parts, String separator) {
  221.         return StringUtils.join(parts, separator, separator);
  222.     }

  223.     /**
  224.      * Join a collection of Strings together using the specified separator and a
  225.      * lastSeparator which is used for joining the second last and the last
  226.      * part.
  227.      *
  228.      * @param parts
  229.      *            Strings to join
  230.      * @param separator
  231.      *            separator used to join all but the two last elements
  232.      * @param lastSeparator
  233.      *            separator to use for joining the last two elements
  234.      * @return a String with all the joined parts
  235.      */
  236.     public static String join(Collection<String> parts, String separator,
  237.             String lastSeparator) {
  238.         StringBuilder sb = new StringBuilder();
  239.         int i = 0;
  240.         int lastIndex = parts.size() - 1;
  241.         for (String part : parts) {
  242.             sb.append(part);
  243.             if (i == lastIndex - 1) {
  244.                 sb.append(lastSeparator);
  245.             } else if (i != lastIndex) {
  246.                 sb.append(separator);
  247.             }
  248.             i++;
  249.         }
  250.         return sb.toString();
  251.     }

  252.     /**
  253.      * Appends {@link Constants#DOT_GIT_EXT} unless the given name already ends
  254.      * with that suffix.
  255.      *
  256.      * @param name
  257.      *            to complete
  258.      * @return the name ending with {@link Constants#DOT_GIT_EXT}
  259.      * @since 6.1
  260.      */
  261.     public static String nameWithDotGit(String name) {
  262.         if (name.endsWith(Constants.DOT_GIT_EXT)) {
  263.             return name;
  264.         }
  265.         return name + Constants.DOT_GIT_EXT;
  266.     }

  267.     /**
  268.      * Test if a string is empty or null.
  269.      *
  270.      * @param stringValue
  271.      *            the string to check
  272.      * @return <code>true</code> if the string is <code>null</code> or empty
  273.      */
  274.     public static boolean isEmptyOrNull(String stringValue) {
  275.         return stringValue == null || stringValue.length() == 0;
  276.     }

  277.     /**
  278.      * Replace CRLF, CR or LF with a single space.
  279.      *
  280.      * @param in
  281.      *            A string with line breaks
  282.      * @return in without line breaks
  283.      * @since 3.1
  284.      */
  285.     public static String replaceLineBreaksWithSpace(String in) {
  286.         char[] buf = new char[in.length()];
  287.         int o = 0;
  288.         for (int i = 0; i < buf.length; ++i) {
  289.             char ch = in.charAt(i);
  290.             switch (ch) {
  291.             case '\r':
  292.                 if (i + 1 < buf.length && in.charAt(i + 1) == '\n') {
  293.                     buf[o++] = ' ';
  294.                     ++i;
  295.                 } else
  296.                     buf[o++] = ' ';
  297.                 break;
  298.             case '\n':
  299.                 buf[o++] = ' ';
  300.                 break;
  301.             default:
  302.                 buf[o++] = ch;
  303.                 break;
  304.             }
  305.         }
  306.         return new String(buf, 0, o);
  307.     }

  308.     /**
  309.      * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g'
  310.      * indicating KiB, MiB, and GiB, respectively. The suffix may follow the
  311.      * number with optional separation by one or more blanks.
  312.      *
  313.      * @param value
  314.      *            {@link String} to parse; with leading and trailing whitespace
  315.      *            ignored
  316.      * @param positiveOnly
  317.      *            {@code true} to only accept positive numbers, {@code false} to
  318.      *            allow negative numbers, too
  319.      * @return the value parsed
  320.      * @throws NumberFormatException
  321.      *             if the {@value} is not parseable, or beyond the range of
  322.      *             {@link Long}
  323.      * @throws StringIndexOutOfBoundsException
  324.      *             if the string is empty or contains only whitespace, or
  325.      *             contains only the letter 'k', 'm', or 'g'
  326.      * @since 6.0
  327.      */
  328.     public static long parseLongWithSuffix(@NonNull String value,
  329.             boolean positiveOnly)
  330.             throws NumberFormatException, StringIndexOutOfBoundsException {
  331.         String n = value.strip();
  332.         if (n.isEmpty()) {
  333.             throw new StringIndexOutOfBoundsException();
  334.         }
  335.         long mul = 1;
  336.         switch (n.charAt(n.length() - 1)) {
  337.         case 'g':
  338.         case 'G':
  339.             mul = GiB;
  340.             break;
  341.         case 'm':
  342.         case 'M':
  343.             mul = MiB;
  344.             break;
  345.         case 'k':
  346.         case 'K':
  347.             mul = KiB;
  348.             break;
  349.         default:
  350.             break;
  351.         }
  352.         if (mul > 1) {
  353.             n = n.substring(0, n.length() - 1).trim();
  354.         }
  355.         if (n.isEmpty()) {
  356.             throw new StringIndexOutOfBoundsException();
  357.         }
  358.         long number;
  359.         if (positiveOnly) {
  360.             number = Long.parseUnsignedLong(n);
  361.             if (number < 0) {
  362.                 throw new NumberFormatException(
  363.                         MessageFormat.format(JGitText.get().valueExceedsRange,
  364.                                 value, Long.class.getSimpleName()));
  365.             }
  366.         } else {
  367.             number = Long.parseLong(n);
  368.         }
  369.         if (mul == 1) {
  370.             return number;
  371.         }
  372.         try {
  373.             return Math.multiplyExact(mul, number);
  374.         } catch (ArithmeticException e) {
  375.             NumberFormatException nfe = new NumberFormatException(
  376.                     e.getLocalizedMessage());
  377.             nfe.initCause(e);
  378.             throw nfe;
  379.         }
  380.     }

  381.     /**
  382.      * Parses a number with optional case-insensitive suffix 'k', 'm', or 'g'
  383.      * indicating KiB, MiB, and GiB, respectively. The suffix may follow the
  384.      * number with optional separation by blanks.
  385.      *
  386.      * @param value
  387.      *            {@link String} to parse; with leading and trailing whitespace
  388.      *            ignored
  389.      * @param positiveOnly
  390.      *            {@code true} to only accept positive numbers, {@code false} to
  391.      *            allow negative numbers, too
  392.      * @return the value parsed
  393.      * @throws NumberFormatException
  394.      *             if the {@value} is not parseable or beyond the range of
  395.      *             {@link Integer}
  396.      * @throws StringIndexOutOfBoundsException
  397.      *             if the string is empty or contains only whitespace, or
  398.      *             contains only the letter 'k', 'm', or 'g'
  399.      * @since 6.0
  400.      */
  401.     public static int parseIntWithSuffix(@NonNull String value,
  402.             boolean positiveOnly)
  403.             throws NumberFormatException, StringIndexOutOfBoundsException {
  404.         try {
  405.             return Math.toIntExact(parseLongWithSuffix(value, positiveOnly));
  406.         } catch (ArithmeticException e) {
  407.             NumberFormatException nfe = new NumberFormatException(
  408.                     MessageFormat.format(JGitText.get().valueExceedsRange,
  409.                             value, Integer.class.getSimpleName()));
  410.             nfe.initCause(e);
  411.             throw nfe;
  412.         }
  413.     }

  414.     /**
  415.      * Formats an integral value as a decimal number with 'k', 'm', or 'g'
  416.      * suffix if it is an exact multiple of 1024, otherwise returns the value
  417.      * representation as a decimal number without suffix.
  418.      *
  419.      * @param value
  420.      *            Value to format
  421.      * @return the value's String representation
  422.      * @since 6.0
  423.      */
  424.     public static String formatWithSuffix(long value) {
  425.         if (value >= GiB && (value % GiB) == 0) {
  426.             return String.valueOf(value / GiB) + 'g';
  427.         }
  428.         if (value >= MiB && (value % MiB) == 0) {
  429.             return String.valueOf(value / MiB) + 'm';
  430.         }
  431.         if (value >= KiB && (value % KiB) == 0) {
  432.             return String.valueOf(value / KiB) + 'k';
  433.         }
  434.         return String.valueOf(value);
  435.     }
  436. }