    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017
    018package org.apache.commons.exec.util;
    019
    020import java.io.File;
    021import java.util.ArrayList;
    022import java.util.List;
    023import java.util.Map;
    024import java.util.Objects;
    025import java.util.StringTokenizer;
    026
    027/**
    028 * Supplement of commons-lang, the stringSubstitution() was in a simpler implementation available in an older commons-lang implementation.
    029 *
    030 * This class is not part of the public API and could change without warning.
    031 */
    032public class StringUtils {
    033
    034    private static final String[] EMPTY_STRING_ARRAY = {};
    035    private static final String SINGLE_QUOTE = "\'";
    036    private static final String DOUBLE_QUOTE = "\"";
    037    private static final char SLASH_CHAR = '/';
    038    private static final char BACKSLASH_CHAR = '\\';
    039
    040    /**
    041     * Fixes the file separator char for the target platform using the following replacement.
    042     * <ul>
    043     * <li>'/' &#x2192; File.separatorChar</li>
    044     * <li>'\\' &#x2192; File.separatorChar</li>
    045     * </ul>
    046     *
    047     * @param arg the argument to fix.
    048     * @return the transformed argument.
    049     */
    050    public static String fixFileSeparatorChar(final String arg) {
    051        return arg.replace(SLASH_CHAR, File.separatorChar).replace(BACKSLASH_CHAR, File.separatorChar);
    052    }
    053
    054    /**
    055     * Determines if this is a quoted argument - either single or double quoted.
    056     *
    057     * @param argument the argument to check.
    058     * @return true when the argument is quoted.
    059     */
    060    public static boolean isQuoted(final String argument) {
    061        return argument.startsWith(SINGLE_QUOTE) && argument.endsWith(SINGLE_QUOTE) || argument.startsWith(DOUBLE_QUOTE) && argument.endsWith(DOUBLE_QUOTE);
    062    }
    063
    064    /**
    065     * Put quotes around the given String if necessary.
    066     * <p>
    067     * 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
    068     * quotes.
    069     * </p>
    070     *
    071     * @param argument the argument to be quoted.
    072     * @return the quoted argument.
    073     * @throws IllegalArgumentException If argument contains both types of quotes.
    074     */
    075    public static String quoteArgument(final String argument) {
    076
    077        String cleanedArgument = argument.trim();
    078
    079        // strip the quotes from both ends
    080        while (cleanedArgument.startsWith(SINGLE_QUOTE) || cleanedArgument.startsWith(DOUBLE_QUOTE)) {
    081            cleanedArgument = cleanedArgument.substring(1);
    082        }
    083
    084        while (cleanedArgument.endsWith(SINGLE_QUOTE) || cleanedArgument.endsWith(DOUBLE_QUOTE)) {
    085            cleanedArgument = cleanedArgument.substring(0, cleanedArgument.length() - 1);
    086        }
    087
    088        final StringBuilder buf = new StringBuilder();
    089        if (cleanedArgument.contains(DOUBLE_QUOTE)) {
    090            if (cleanedArgument.contains(SINGLE_QUOTE)) {
    091                throw new IllegalArgumentException("Can't handle single and double quotes in same argument");
    092            }
    093            return buf.append(SINGLE_QUOTE).append(cleanedArgument).append(SINGLE_QUOTE).toString();
    094        }
    095        if (cleanedArgument.contains(SINGLE_QUOTE) || cleanedArgument.contains(" ")) {
    096            return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append(DOUBLE_QUOTE).toString();
    097        }
    098        return cleanedArgument;
    099    }
    100
    101    /**
    102     * Split a string into an array of strings based on a separator.
    103     *
    104     * @param input     what to split.
    105     * @param splitChar what to split on.
    106     * @return the array of strings.
    107     */
    108    public static String[] split(final String input, final String splitChar) {
    109        final StringTokenizer tokens = new StringTokenizer(input, splitChar);
    110        final List<String> strList = new ArrayList<>();
    111        while (tokens.hasMoreTokens()) {
    112            strList.add(tokens.nextToken());
    113        }
    114        return strList.toArray(EMPTY_STRING_ARRAY);
    115    }
    116
    117    /**
    118     * Perform a series of substitutions.
    119     * <p>
    120     * The substitutions are performed by replacing ${variable} in the target string with the value of provided by the key "variable" in the provided hash
    121     * table.
    122     * </p>
    123     * <p>
    124     * A key consists of the following characters:
    125     * </p>
    126     * <ul>
    127     * <li>letter
    128     * <li>digit
    129     * <li>dot character
    130     * <li>hyphen character
    131     * <li>plus character
    132     * <li>underscore character
    133     * </ul>
    134     *
    135     * @param argStr    the argument string to be processed.
    136     * @param vars      name/value pairs used for substitution.
    137     * @param isLenient ignore a key not found in vars or throw a RuntimeException?
    138     * @return String target string with replacements.
    139     */
    140    public static StringBuffer stringSubstitution(final String argStr, final Map<? super String, ?> vars, final boolean isLenient) {
    141
    142        final StringBuffer argBuf = new StringBuffer();
    143
    144        if (argStr == null || argStr.isEmpty()) {
    145            return argBuf;
    146        }
    147
    148        if (vars == null || vars.isEmpty()) {
    149            return argBuf.append(argStr);
    150        }
    151
    152        final int argStrLength = argStr.length();
    153
    154        for (int cIdx = 0; cIdx < argStrLength;) {
    155
    156            char ch = argStr.charAt(cIdx);
    157            char del = ' ';
    158
    159            switch (ch) {
    160
    161            case '$':
    162                final StringBuilder nameBuf = new StringBuilder();
    163                del = argStr.charAt(cIdx + 1);
    164                if (del == '{') {
    165                    cIdx++;
    166
    167                    for (++cIdx; cIdx < argStr.length(); ++cIdx) {
    168                        ch = argStr.charAt(cIdx);
    169                        if (ch != '_' && ch != '.' && ch != '-' && ch != '+' && !Character.isLetterOrDigit(ch)) {
    170                            break;
    171                        }
    172                        nameBuf.append(ch);
    173                    }
    174
    175                    if (nameBuf.length() >= 0) {
    176
    177                        String value;
    178                        final Object temp = vars.get(nameBuf.toString());
    179
    180                        if (temp instanceof File) {
    181                            // for a file we have to fix the separator chars to allow
    182                            // cross-platform compatibility
    183                            value = fixFileSeparatorChar(((File) temp).getAbsolutePath());
    184                        } else {
    185                            value = Objects.toString(temp, null);
    186                        }
    187
    188                        if (value != null) {
    189                            argBuf.append(value);
    190                        } else {
    191                            if (!isLenient) {
    192                                // complain that no variable was found
    193                                throw new IllegalArgumentException("No value found for : " + nameBuf);
    194                            }
    195                            // just append the unresolved variable declaration
    196                            argBuf.append("${").append(nameBuf.toString()).append("}");
    197                        }
    198
    199                        del = argStr.charAt(cIdx);
    200
    201                        if (del != '}') {
    202                            throw new IllegalArgumentException("Delimiter not found for : " + nameBuf);
    203                        }
    204                    }
    205                } else {
    206                    argBuf.append(ch);
    207                }
    208                cIdx++;
    209
    210                break;
    211
    212            default:
    213                argBuf.append(ch);
    214                ++cIdx;
    215                break;
    216            }
    217        }
    218
    219        return argBuf;
    220    }
    221
    222    /**
    223     * Concatenates an array of string using a separator.
    224     *
    225     * @param strings   the strings to concatenate.
    226     * @param separator the separator between two strings.
    227     * @return the concatenated strings.
    228     * @deprecated Use {@link String#join(CharSequence, CharSequence...)}.
    229     */
    230    @Deprecated
    231    public static String toString(final String[] strings, final String separator) {
    232        return String.join(separator, strings);
    233    }
    234
    235    /**
    236     * Constructs a new instance.
    237     *
    238     * @deprecated Will be private in the next major version.
    239     */
    240    @Deprecated
    241    public StringUtils() {
    242        // empty
    243    }
    244}