StringUtils.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */

  17. package org.apache.commons.exec.util;

  18. import java.io.File;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.Objects;
  23. import java.util.StringTokenizer;

  24. /**
  25.  * Supplement of commons-lang, the stringSubstitution() was in a simpler implementation available in an older commons-lang implementation.
  26.  *
  27.  * This class is not part of the public API and could change without warning.
  28.  */
  29. public class StringUtils {

  30.     private static final String[] EMPTY_STRING_ARRAY = {};
  31.     private static final String SINGLE_QUOTE = "\'";
  32.     private static final String DOUBLE_QUOTE = "\"";
  33.     private static final char SLASH_CHAR = '/';
  34.     private static final char BACKSLASH_CHAR = '\\';

  35.     /**
  36.      * Fixes the file separator char for the target platform using the following replacement.
  37.      * <ul>
  38.      * <li>'/' &#x2192; File.separatorChar</li>
  39.      * <li>'\\' &#x2192; File.separatorChar</li>
  40.      * </ul>
  41.      *
  42.      * @param arg the argument to fix.
  43.      * @return the transformed argument.
  44.      */
  45.     public static String fixFileSeparatorChar(final String arg) {
  46.         return arg.replace(SLASH_CHAR, File.separatorChar).replace(BACKSLASH_CHAR, File.separatorChar);
  47.     }

  48.     /**
  49.      * Determines if this is a quoted argument - either single or double quoted.
  50.      *
  51.      * @param argument the argument to check.
  52.      * @return true when the argument is quoted.
  53.      */
  54.     public static boolean isQuoted(final String argument) {
  55.         return argument.startsWith(SINGLE_QUOTE) && argument.endsWith(SINGLE_QUOTE) || argument.startsWith(DOUBLE_QUOTE) && argument.endsWith(DOUBLE_QUOTE);
  56.     }

  57.     /**
  58.      * Put quotes around the given String if necessary.
  59.      * <p>
  60.      * If the argument doesn't include spaces or quotes, return it as is. If it contains double quotes, use single quotes - else surround the argument by double
  61.      * quotes.
  62.      * </p>
  63.      *
  64.      * @param argument the argument to be quoted.
  65.      * @return the quoted argument.
  66.      * @throws IllegalArgumentException If argument contains both types of quotes.
  67.      */
  68.     public static String quoteArgument(final String argument) {

  69.         String cleanedArgument = argument.trim();

  70.         // strip the quotes from both ends
  71.         while (cleanedArgument.startsWith(SINGLE_QUOTE) || cleanedArgument.startsWith(DOUBLE_QUOTE)) {
  72.             cleanedArgument = cleanedArgument.substring(1);
  73.         }

  74.         while (cleanedArgument.endsWith(SINGLE_QUOTE) || cleanedArgument.endsWith(DOUBLE_QUOTE)) {
  75.             cleanedArgument = cleanedArgument.substring(0, cleanedArgument.length() - 1);
  76.         }

  77.         final StringBuilder buf = new StringBuilder();
  78.         if (cleanedArgument.contains(DOUBLE_QUOTE)) {
  79.             if (cleanedArgument.contains(SINGLE_QUOTE)) {
  80.                 throw new IllegalArgumentException("Can't handle single and double quotes in same argument");
  81.             }
  82.             return buf.append(SINGLE_QUOTE).append(cleanedArgument).append(SINGLE_QUOTE).toString();
  83.         }
  84.         if (cleanedArgument.contains(SINGLE_QUOTE) || cleanedArgument.contains(" ")) {
  85.             return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append(DOUBLE_QUOTE).toString();
  86.         }
  87.         return cleanedArgument;
  88.     }

  89.     /**
  90.      * Split a string into an array of strings based on a separator.
  91.      *
  92.      * @param input     what to split.
  93.      * @param splitChar what to split on.
  94.      * @return the array of strings.
  95.      */
  96.     public static String[] split(final String input, final String splitChar) {
  97.         final StringTokenizer tokens = new StringTokenizer(input, splitChar);
  98.         final List<String> strList = new ArrayList<>();
  99.         while (tokens.hasMoreTokens()) {
  100.             strList.add(tokens.nextToken());
  101.         }
  102.         return strList.toArray(EMPTY_STRING_ARRAY);
  103.     }

  104.     /**
  105.      * Perform a series of substitutions.
  106.      * <p>
  107.      * The substitutions are performed by replacing ${variable} in the target string with the value of provided by the key "variable" in the provided hash
  108.      * table.
  109.      * </p>
  110.      * <p>
  111.      * A key consists of the following characters:
  112.      * </p>
  113.      * <ul>
  114.      * <li>letter
  115.      * <li>digit
  116.      * <li>dot character
  117.      * <li>hyphen character
  118.      * <li>plus character
  119.      * <li>underscore character
  120.      * </ul>
  121.      *
  122.      * @param argStr    the argument string to be processed.
  123.      * @param vars      name/value pairs used for substitution.
  124.      * @param isLenient ignore a key not found in vars or throw a RuntimeException?
  125.      * @return String target string with replacements.
  126.      */
  127.     public static StringBuffer stringSubstitution(final String argStr, final Map<? super String, ?> vars, final boolean isLenient) {

  128.         final StringBuffer argBuf = new StringBuffer();

  129.         if (argStr == null || argStr.isEmpty()) {
  130.             return argBuf;
  131.         }

  132.         if (vars == null || vars.isEmpty()) {
  133.             return argBuf.append(argStr);
  134.         }

  135.         final int argStrLength = argStr.length();

  136.         for (int cIdx = 0; cIdx < argStrLength;) {

  137.             char ch = argStr.charAt(cIdx);
  138.             char del = ' ';

  139.             switch (ch) {

  140.             case '$':
  141.                 final StringBuilder nameBuf = new StringBuilder();
  142.                 del = argStr.charAt(cIdx + 1);
  143.                 if (del == '{') {
  144.                     cIdx++;

  145.                     for (++cIdx; cIdx < argStr.length(); ++cIdx) {
  146.                         ch = argStr.charAt(cIdx);
  147.                         if (ch != '_' && ch != '.' && ch != '-' && ch != '+' && !Character.isLetterOrDigit(ch)) {
  148.                             break;
  149.                         }
  150.                         nameBuf.append(ch);
  151.                     }

  152.                     if (nameBuf.length() >= 0) {

  153.                         String value;
  154.                         final Object temp = vars.get(nameBuf.toString());

  155.                         if (temp instanceof File) {
  156.                             // for a file we have to fix the separator chars to allow
  157.                             // cross-platform compatibility
  158.                             value = fixFileSeparatorChar(((File) temp).getAbsolutePath());
  159.                         } else {
  160.                             value = Objects.toString(temp, null);
  161.                         }

  162.                         if (value != null) {
  163.                             argBuf.append(value);
  164.                         } else {
  165.                             if (!isLenient) {
  166.                                 // complain that no variable was found
  167.                                 throw new IllegalArgumentException("No value found for : " + nameBuf);
  168.                             }
  169.                             // just append the unresolved variable declaration
  170.                             argBuf.append("${").append(nameBuf.toString()).append("}");
  171.                         }

  172.                         del = argStr.charAt(cIdx);

  173.                         if (del != '}') {
  174.                             throw new IllegalArgumentException("Delimiter not found for : " + nameBuf);
  175.                         }
  176.                     }
  177.                 } else {
  178.                     argBuf.append(ch);
  179.                 }
  180.                 cIdx++;

  181.                 break;

  182.             default:
  183.                 argBuf.append(ch);
  184.                 ++cIdx;
  185.                 break;
  186.             }
  187.         }

  188.         return argBuf;
  189.     }

  190.     /**
  191.      * Concatenates an array of string using a separator.
  192.      *
  193.      * @param strings   the strings to concatenate.
  194.      * @param separator the separator between two strings.
  195.      * @return the concatenated strings.
  196.      * @deprecated Use {@link String#join(CharSequence, CharSequence...)}.
  197.      */
  198.     @Deprecated
  199.     public static String toString(final String[] strings, final String separator) {
  200.         return String.join(separator, strings);
  201.     }

  202.     /**
  203.      * Constructs a new instance.
  204.      *
  205.      * @deprecated Will be private in the next major version.
  206.      */
  207.     @Deprecated
  208.     public StringUtils() {
  209.         // empty
  210.     }
  211. }