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>'/' → File.separatorChar</li> 055 * <li>'\\' → 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}