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    public static String removeQuotes(String s) {
072        if (ObjectHelper.isEmpty(s)) {
073            return s;
074        }
075
076        s = replaceAll(s, "'", "");
077        s = replaceAll(s, "\"", "");
078        return s;
079    }
080
081    public static String removeLeadingAndEndingQuotes(String s) {
082        if (ObjectHelper.isEmpty(s)) {
083            return s;
084        }
085
086        String copy = s.trim();
087        if (copy.startsWith("'") && copy.endsWith("'")) {
088            return copy.substring(1, copy.length() - 1);
089        }
090        if (copy.startsWith("\"") && copy.endsWith("\"")) {
091            return copy.substring(1, copy.length() - 1);
092        }
093
094        // no quotes, so return as-is
095        return s;
096    }
097
098    public static boolean isQuoted(String s) {
099        if (ObjectHelper.isEmpty(s)) {
100            return false;
101        }
102
103        if (s.startsWith("'") && s.endsWith("'")) {
104            return true;
105        }
106        if (s.startsWith("\"") && s.endsWith("\"")) {
107            return true;
108        }
109
110        return false;
111    }
112
113    /**
114     * Encodes the text into safe XML by replacing < > and & with XML tokens
115     *
116     * @param text  the text
117     * @return the encoded text
118     */
119    public static String xmlEncode(String text) {
120        if (text == null) {
121            return "";
122        }
123        // must replace amp first, so we dont replace &lt; to amp later
124        text = replaceAll(text, "&", "&amp;");
125        text = replaceAll(text, "\"", "&quot;");
126        text = replaceAll(text, "<", "&lt;");
127        text = replaceAll(text, ">", "&gt;");
128        return text;
129    }
130
131    /**
132     * Determines if the string has at least one letter in upper case
133     * @param text the text
134     * @return <tt>true</tt> if at least one letter is upper case, <tt>false</tt> otherwise
135     */
136    public static boolean hasUpperCase(String text) {
137        if (text == null) {
138            return false;
139        }
140
141        for (int i = 0; i < text.length(); i++) {
142            char ch = text.charAt(i);
143            if (Character.isUpperCase(ch)) {
144                return true;
145            }
146        }
147
148        return false;
149    }
150
151    /**
152     * Determines if the string is a fully qualified class name
153     */
154    public static boolean isClassName(String text) {
155        boolean result = false;
156        if (text != null) {
157            String[] split = text.split("\\.");
158            if (split.length > 0) {
159                String lastToken = split[split.length - 1];
160                if (lastToken.length() > 0) {
161                    result = Character.isUpperCase(lastToken.charAt(0));
162                }
163            }
164        }
165        return result;
166    }
167
168    /**
169     * Does the expression have the language start token?
170     *
171     * @param expression the expression
172     * @param language the name of the language, such as simple
173     * @return <tt>true</tt> if the expression contains the start token, <tt>false</tt> otherwise
174     */
175    public static boolean hasStartToken(String expression, String language) {
176        if (expression == null) {
177            return false;
178        }
179
180        // for the simple language the expression start token could be "${"
181        if ("simple".equalsIgnoreCase(language) && expression.contains("${")) {
182            return true;
183        }
184
185        if (language != null && expression.contains("$" + language + "{")) {
186            return true;
187        }
188
189        return false;
190    }
191
192    /**
193     * Replaces all the from tokens in the given input string.
194     * <p/>
195     * This implementation is not recursive, not does it check for tokens in the replacement string.
196     *
197     * @param input  the input string
198     * @param from   the from string, must <b>not</b> be <tt>null</tt> or empty
199     * @param to     the replacement string, must <b>not</b> be empty
200     * @return the replaced string, or the input string if no replacement was needed
201     * @throws IllegalArgumentException if the input arguments is invalid
202     */
203    public static String replaceAll(String input, String from, String to) {
204        if (ObjectHelper.isEmpty(input)) {
205            return input;
206        }
207        if (from == null) {
208            throw new IllegalArgumentException("from cannot be null");
209        }
210        if (to == null) {
211            // to can be empty, so only check for null
212            throw new IllegalArgumentException("to cannot be null");
213        }
214
215        // fast check if there is any from at all
216        if (!input.contains(from)) {
217            return input;
218        }
219
220        final int len = from.length();
221        final int max = input.length();
222        StringBuilder sb = new StringBuilder(max);
223        for (int i = 0; i < max;) {
224            if (i + len <= max) {
225                String token = input.substring(i, i + len);
226                if (from.equals(token)) {
227                    sb.append(to);
228                    // fast forward
229                    i = i + len;
230                    continue;
231                }
232            }
233
234            // append single char
235            sb.append(input.charAt(i));
236            // forward to next
237            i++;
238        }
239        return sb.toString();
240    }
241
242    /**
243     * Creates a json tuple with the given name/value pair.
244     *
245     * @param name  the name
246     * @param value the value
247     * @param isMap whether the tuple should be map
248     * @return the json
249     */
250    public static String toJson(String name, String value, boolean isMap) {
251        if (isMap) {
252            return "{ " + doubleQuote(name) + ": " + doubleQuote(value) + " }";
253        } else {
254            return doubleQuote(name) + ": " + doubleQuote(value);
255        }
256    }
257
258}