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