View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.commons.exec.util;
19  
20  import java.io.File;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Objects;
25  import java.util.StringTokenizer;
26  
27  /**
28   * Supplement of commons-lang, the stringSubstitution() was in a simpler implementation available in an older commons-lang implementation.
29   *
30   * This class is not part of the public API and could change without warning.
31   */
32  public class StringUtils {
33  
34      private static final String[] EMPTY_STRING_ARRAY = {};
35      private static final String SINGLE_QUOTE = "\'";
36      private static final String DOUBLE_QUOTE = "\"";
37      private static final char SLASH_CHAR = '/';
38      private static final char BACKSLASH_CHAR = '\\';
39  
40      /**
41       * Fixes the file separator char for the target platform using the following replacement.
42       * <ul>
43       * <li>'/' &#x2192; File.separatorChar</li>
44       * <li>'\\' &#x2192; File.separatorChar</li>
45       * </ul>
46       *
47       * @param arg the argument to fix.
48       * @return the transformed argument.
49       */
50      public static String fixFileSeparatorChar(final String arg) {
51          return arg.replace(SLASH_CHAR, File.separatorChar).replace(BACKSLASH_CHAR, File.separatorChar);
52      }
53  
54      /**
55       * Determines if this is a quoted argument - either single or double quoted.
56       *
57       * @param argument the argument to check.
58       * @return true when the argument is quoted.
59       */
60      public static boolean isQuoted(final String argument) {
61          return argument.startsWith(SINGLE_QUOTE) && argument.endsWith(SINGLE_QUOTE) || argument.startsWith(DOUBLE_QUOTE) && argument.endsWith(DOUBLE_QUOTE);
62      }
63  
64      /**
65       * Put quotes around the given String if necessary.
66       * <p>
67       * 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
68       * quotes.
69       * </p>
70       *
71       * @param argument the argument to be quoted.
72       * @return the quoted argument.
73       * @throws IllegalArgumentException If argument contains both types of quotes.
74       */
75      public static String quoteArgument(final String argument) {
76  
77          String cleanedArgument = argument.trim();
78  
79          // strip the quotes from both ends
80          while (cleanedArgument.startsWith(SINGLE_QUOTE) || cleanedArgument.startsWith(DOUBLE_QUOTE)) {
81              cleanedArgument = cleanedArgument.substring(1);
82          }
83  
84          while (cleanedArgument.endsWith(SINGLE_QUOTE) || cleanedArgument.endsWith(DOUBLE_QUOTE)) {
85              cleanedArgument = cleanedArgument.substring(0, cleanedArgument.length() - 1);
86          }
87  
88          final StringBuilder buf = new StringBuilder();
89          if (cleanedArgument.indexOf(DOUBLE_QUOTE) > -1) {
90              if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1) {
91                  throw new IllegalArgumentException("Can't handle single and double quotes in same argument");
92              }
93              return buf.append(SINGLE_QUOTE).append(cleanedArgument).append(SINGLE_QUOTE).toString();
94          }
95          if (cleanedArgument.indexOf(SINGLE_QUOTE) > -1 || cleanedArgument.indexOf(" ") > -1) {
96              return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append(DOUBLE_QUOTE).toString();
97          }
98          return cleanedArgument;
99      }
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 }