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 */ 018 019package org.apache.commons.exec.util; 020 021 022import java.io.File; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Map; 026import java.util.StringTokenizer; 027 028/** 029 * Supplement of commons-lang, the stringSubstitution() was in a simpler 030 * implementation available in an older commons-lang implementation. 031 * 032 * This class is not part of the public API and could change without 033 * warning. 034 * 035 * @version $Id: StringUtils.java 1636204 2014-11-02 22:30:31Z ggregory $ 036 */ 037public class StringUtils { 038 039 private static final String SINGLE_QUOTE = "\'"; 040 private static final String DOUBLE_QUOTE = "\""; 041 private static final char SLASH_CHAR = '/'; 042 private static final char BACKSLASH_CHAR = '\\'; 043 044 /** 045 * Perform a series of substitutions. 046 * <p> 047 * The substitutions are performed by replacing ${variable} in the target string with the value of provided by the 048 * key "variable" in the provided hash table. 049 * </p> 050 * <p> 051 * A key consists of the following characters: 052 * </p> 053 * <ul> 054 * <li>letter 055 * <li>digit 056 * <li>dot character 057 * <li>hyphen character 058 * <li>plus character 059 * <li>underscore character 060 * </ul> 061 * 062 * @param argStr 063 * the argument string to be processed 064 * @param vars 065 * name/value pairs used for substitution 066 * @param isLenient 067 * ignore a key not found in vars or throw a RuntimeException? 068 * @return String target string with replacements. 069 */ 070 public static StringBuffer stringSubstitution(final String argStr, final Map<? super String, ?> vars, final boolean isLenient) { 071 072 final StringBuffer argBuf = new StringBuffer(); 073 074 if (argStr == null || argStr.length() == 0) { 075 return argBuf; 076 } 077 078 if (vars == null || vars.size() == 0) { 079 return argBuf.append(argStr); 080 } 081 082 final int argStrLength = argStr.length(); 083 084 for (int cIdx = 0; cIdx < argStrLength;) { 085 086 char ch = argStr.charAt(cIdx); 087 char del = ' '; 088 089 switch (ch) { 090 091 case '$': 092 final StringBuilder nameBuf = new StringBuilder(); 093 del = argStr.charAt(cIdx + 1); 094 if (del == '{') { 095 cIdx++; 096 097 for (++cIdx; cIdx < argStr.length(); ++cIdx) { 098 ch = argStr.charAt(cIdx); 099 if (ch == '_' || ch == '.' || ch == '-' || ch == '+' || Character.isLetterOrDigit(ch)) { 100 nameBuf.append(ch); 101 } else { 102 break; 103 } 104 } 105 106 if (nameBuf.length() >= 0) { 107 108 String value; 109 final Object temp = vars.get(nameBuf.toString()); 110 111 if (temp instanceof File) { 112 // for a file we have to fix the separator chars to allow 113 // cross-platform compatibility 114 value = fixFileSeparatorChar(((File) temp).getAbsolutePath()); 115 } 116 else { 117 value = temp != null ? temp.toString() : null; 118 } 119 120 if (value != null) { 121 argBuf.append(value); 122 } else { 123 if (isLenient) { 124 // just append the unresolved variable declaration 125 argBuf.append("${").append(nameBuf.toString()).append("}"); 126 } else { 127 // complain that no variable was found 128 throw new RuntimeException("No value found for : " + nameBuf); 129 } 130 } 131 132 del = argStr.charAt(cIdx); 133 134 if (del != '}') { 135 throw new RuntimeException("Delimiter not found for : " + nameBuf); 136 } 137 } 138 139 cIdx++; 140 } 141 else { 142 argBuf.append(ch); 143 ++cIdx; 144 } 145 146 break; 147 148 default: 149 argBuf.append(ch); 150 ++cIdx; 151 break; 152 } 153 } 154 155 return argBuf; 156 } 157 158 /** 159 * Split a string into an array of strings based 160 * on a separator. 161 * 162 * @param input what to split 163 * @param splitChar what to split on 164 * @return the array of strings 165 */ 166 public static String[] split(final String input, final String splitChar) { 167 final StringTokenizer tokens = new StringTokenizer(input, splitChar); 168 final List<String> strList = new ArrayList<String>(); 169 while (tokens.hasMoreTokens()) { 170 strList.add(tokens.nextToken()); 171 } 172 return strList.toArray(new String[strList.size()]); 173 } 174 175 /** 176 * Fixes the file separator char for the target platform 177 * using the following replacement. 178 * 179 * <ul> 180 * <li>'/' → File.separatorChar</li> 181 * <li>'\\' → File.separatorChar</li> 182 * </ul> 183 * 184 * @param arg the argument to fix 185 * @return the transformed argument 186 */ 187 public static String fixFileSeparatorChar(final String arg) { 188 return arg.replace(SLASH_CHAR, File.separatorChar).replace( 189 BACKSLASH_CHAR, File.separatorChar); 190 } 191 192 /** 193 * Concatenates an array of string using a separator. 194 * 195 * @param strings the strings to concatenate 196 * @param separator the separator between two strings 197 * @return the concatenated strings 198 */ 199 public static String toString(final String[] strings, final String separator) { 200 final StringBuilder sb = new StringBuilder(); 201 for (int i = 0; i < strings.length; i++) { 202 if (i > 0) { 203 sb.append(separator); 204 } 205 sb.append(strings[i]); 206 } 207 return sb.toString(); 208 } 209 210 /** 211 * Put quotes around the given String if necessary. 212 * <p> 213 * If the argument doesn't include spaces or quotes, return it as is. If it 214 * contains double quotes, use single quotes - else surround the argument by 215 * double quotes. 216 * </p> 217 * 218 * @param argument the argument to be quoted 219 * @return the quoted argument 220 * @throws IllegalArgumentException If argument contains both types of quotes 221 */ 222 public static String quoteArgument(final String argument) { 223 224 String cleanedArgument = argument.trim(); 225 226 // strip the quotes from both ends 227 while (cleanedArgument.startsWith(SINGLE_QUOTE) || cleanedArgument.startsWith(DOUBLE_QUOTE)) { 228 cleanedArgument = cleanedArgument.substring(1); 229 } 230 231 while (cleanedArgument.endsWith(SINGLE_QUOTE) || cleanedArgument.endsWith(DOUBLE_QUOTE)) { 232 cleanedArgument = cleanedArgument.substring(0, cleanedArgument.length() - 1); 233 } 234 235 final StringBuilder buf = new StringBuilder(); 236 if (cleanedArgument.indexOf(DOUBLE_QUOTE) > -1) { 237 if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1) { 238 throw new IllegalArgumentException( 239 "Can't handle single and double quotes in same argument"); 240 } 241 return buf.append(SINGLE_QUOTE).append(cleanedArgument).append( 242 SINGLE_QUOTE).toString(); 243 } else if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1 244 || cleanedArgument.indexOf(" ") > -1) { 245 return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append( 246 DOUBLE_QUOTE).toString(); 247 } else { 248 return cleanedArgument; 249 } 250 } 251 252 /** 253 * Determines if this is a quoted argument - either single or 254 * double quoted. 255 * 256 * @param argument the argument to check 257 * @return true when the argument is quoted 258 */ 259 public static boolean isQuoted(final String argument) { 260 return argument.startsWith(SINGLE_QUOTE) && argument.endsWith(SINGLE_QUOTE) 261 || argument.startsWith(DOUBLE_QUOTE) && argument.endsWith(DOUBLE_QUOTE); 262 } 263}