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 * http://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>'/' → File.separatorChar</li> 044 * <li>'\\' → 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.indexOf(DOUBLE_QUOTE) > -1) { 090 if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1) { 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.indexOf(SINGLE_QUOTE) > -1 || cleanedArgument.indexOf(" ") > -1) { 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}