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