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 */
017package org.apache.camel.util;
018
019import static org.apache.camel.util.StringQuoteHelper.doubleQuote;
020
021/**
022 * Helper methods for working with Strings.
023 */
024public final class StringHelper {
025
026    /**
027     * Constructor of utility class should be private.
028     */
029    private StringHelper() {
030    }
031
032    /**
033     * Ensures that <code>s</code> is friendly for a URL or file system.
034     *
035     * @param s String to be sanitized.
036     * @return sanitized version of <code>s</code>.
037     * @throws NullPointerException if <code>s</code> is <code>null</code>.
038     */
039    public static String sanitize(String s) {
040        return s
041            .replace(':', '-')
042            .replace('_', '-')
043            .replace('.', '-')
044            .replace('/', '-')
045            .replace('\\', '-');
046    }
047
048    /**
049     * Counts the number of times the given char is in the string
050     *
051     * @param s  the string
052     * @param ch the char
053     * @return number of times char is located in the string
054     */
055    public static int countChar(String s, char ch) {
056        if (ObjectHelper.isEmpty(s)) {
057            return 0;
058        }
059
060        int matches = 0;
061        for (int i = 0; i < s.length(); i++) {
062            char c = s.charAt(i);
063            if (ch == c) {
064                matches++;
065            }
066        }
067
068        return matches;
069    }
070
071    /**
072     * Limits the length of a string
073     * 
074     * @param s the string
075     * @param maxLength the maximum length of the returned string
076     * @return s if the length of s is less than maxLength or the first maxLength characters of s
077     */
078    public static String limitLenght(String s, int maxLength) {
079        if (ObjectHelper.isEmpty(s)) {
080            return s;
081        }
082        return s.length() <= maxLength ? s : s.substring(0, maxLength);
083    }
084
085    public static String removeQuotes(String s) {
086        if (ObjectHelper.isEmpty(s)) {
087            return s;
088        }
089
090        s = replaceAll(s, "'", "");
091        s = replaceAll(s, "\"", "");
092        return s;
093    }
094
095    public static String removeLeadingAndEndingQuotes(String s) {
096        if (ObjectHelper.isEmpty(s)) {
097            return s;
098        }
099
100        String copy = s.trim();
101        if (copy.startsWith("'") && copy.endsWith("'")) {
102            return copy.substring(1, copy.length() - 1);
103        }
104        if (copy.startsWith("\"") && copy.endsWith("\"")) {
105            return copy.substring(1, copy.length() - 1);
106        }
107
108        // no quotes, so return as-is
109        return s;
110    }
111
112    public static boolean isQuoted(String s) {
113        if (ObjectHelper.isEmpty(s)) {
114            return false;
115        }
116
117        if (s.startsWith("'") && s.endsWith("'")) {
118            return true;
119        }
120        if (s.startsWith("\"") && s.endsWith("\"")) {
121            return true;
122        }
123
124        return false;
125    }
126
127    /**
128     * Encodes the text into safe XML by replacing < > and & with XML tokens
129     *
130     * @param text  the text
131     * @return the encoded text
132     */
133    public static String xmlEncode(String text) {
134        if (text == null) {
135            return "";
136        }
137        // must replace amp first, so we dont replace &lt; to amp later
138        text = replaceAll(text, "&", "&amp;");
139        text = replaceAll(text, "\"", "&quot;");
140        text = replaceAll(text, "<", "&lt;");
141        text = replaceAll(text, ">", "&gt;");
142        return text;
143    }
144
145    /**
146     * Determines if the string has at least one letter in upper case
147     * @param text the text
148     * @return <tt>true</tt> if at least one letter is upper case, <tt>false</tt> otherwise
149     */
150    public static boolean hasUpperCase(String text) {
151        if (text == null) {
152            return false;
153        }
154
155        for (int i = 0; i < text.length(); i++) {
156            char ch = text.charAt(i);
157            if (Character.isUpperCase(ch)) {
158                return true;
159            }
160        }
161
162        return false;
163    }
164
165    /**
166     * Determines if the string is a fully qualified class name
167     */
168    public static boolean isClassName(String text) {
169        boolean result = false;
170        if (text != null) {
171            String[] split = text.split("\\.");
172            if (split.length > 0) {
173                String lastToken = split[split.length - 1];
174                if (lastToken.length() > 0) {
175                    result = Character.isUpperCase(lastToken.charAt(0));
176                }
177            }
178        }
179        return result;
180    }
181
182    /**
183     * Does the expression have the language start token?
184     *
185     * @param expression the expression
186     * @param language the name of the language, such as simple
187     * @return <tt>true</tt> if the expression contains the start token, <tt>false</tt> otherwise
188     */
189    public static boolean hasStartToken(String expression, String language) {
190        if (expression == null) {
191            return false;
192        }
193
194        // for the simple language the expression start token could be "${"
195        if ("simple".equalsIgnoreCase(language) && expression.contains("${")) {
196            return true;
197        }
198
199        if (language != null && expression.contains("$" + language + "{")) {
200            return true;
201        }
202
203        return false;
204    }
205
206    /**
207     * Replaces all the from tokens in the given input string.
208     * <p/>
209     * This implementation is not recursive, not does it check for tokens in the replacement string.
210     *
211     * @param input  the input string
212     * @param from   the from string, must <b>not</b> be <tt>null</tt> or empty
213     * @param to     the replacement string, must <b>not</b> be empty
214     * @return the replaced string, or the input string if no replacement was needed
215     * @throws IllegalArgumentException if the input arguments is invalid
216     */
217    public static String replaceAll(String input, String from, String to) {
218        if (ObjectHelper.isEmpty(input)) {
219            return input;
220        }
221        if (from == null) {
222            throw new IllegalArgumentException("from cannot be null");
223        }
224        if (to == null) {
225            // to can be empty, so only check for null
226            throw new IllegalArgumentException("to cannot be null");
227        }
228
229        // fast check if there is any from at all
230        if (!input.contains(from)) {
231            return input;
232        }
233
234        final int len = from.length();
235        final int max = input.length();
236        StringBuilder sb = new StringBuilder(max);
237        for (int i = 0; i < max;) {
238            if (i + len <= max) {
239                String token = input.substring(i, i + len);
240                if (from.equals(token)) {
241                    sb.append(to);
242                    // fast forward
243                    i = i + len;
244                    continue;
245                }
246            }
247
248            // append single char
249            sb.append(input.charAt(i));
250            // forward to next
251            i++;
252        }
253        return sb.toString();
254    }
255
256    /**
257     * Creates a json tuple with the given name/value pair.
258     *
259     * @param name  the name
260     * @param value the value
261     * @param isMap whether the tuple should be map
262     * @return the json
263     */
264    public static String toJson(String name, String value, boolean isMap) {
265        if (isMap) {
266            return "{ " + doubleQuote(name) + ": " + doubleQuote(value) + " }";
267        } else {
268            return doubleQuote(name) + ": " + doubleQuote(value);
269        }
270    }
271
272}