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    }