001/**
002 * Copyright 2010-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.common.util;
017
018import static org.apache.commons.lang3.StringUtils.replace;
019import static org.apache.commons.lang3.StringUtils.trim;
020import static org.kuali.common.util.Encodings.ASCII;
021
022import java.io.UnsupportedEncodingException;
023import java.util.List;
024
025import org.apache.commons.lang3.StringUtils;
026
027/**
028 * Operations on <code>String</code> that are <code>null</code> safe
029 */
030public class Str {
031
032        public static final String EMPTY_STRING = "";
033        public static final String UTF8 = Encodings.UTF8;
034        public static final String COMMA = ",";
035        public static final String SPACE = " ";
036        public static final String CR = "\r";
037        public static final String LF = "\n";
038        public static final String DOT = ".";
039        public static final String COLON = ":";
040        public static final String FORWARD_SLASH = "/";
041        public static final char DOUBLE_QUOTE = '"';
042        public static final String CDATA_PREFIX = "<![CDATA[";
043        public static final String CDATA_SUFFIX = "]]>";
044        public static final String[] EMPTY_ARRAY = new String[0];
045
046        private static final String CONCEALED_PREFIX = "cnc--";
047
048        /**
049         * <p>
050         * A trivial way to conceal <code>text</code>. Can be reversed using <code>reveal()</code>. Do <b>NOT</b> use this method in an attempt to obscure sensitive data. The algorithm
051         * is completely trivial and exceedingly simple to reverse engineer. Not to mention, the <code>reveal()</code> method can reproduce the original string without requiring any
052         * secret knowledge.
053         * </p>
054         * 
055         * <p>
056         * The use case here is to help prevent someone with otherwise mostly good intentions from altering a piece of information in a way they should not. This is <b>NOT</b> intended
057         * to defeat any serious attempt at discovering the original text.
058         * </p>
059         * 
060         * <p>
061         * Think a hungry sales or marketing rep who stumbles across a config file with the entry <code>vending.machine.refill.day=wed</code> in it and tries to change that to
062         * <code>mon</code> in order to beat a case of the munchies. :)
063         * </p>
064         * 
065         * <p>
066         * If the entry says <code>vending.machine.refill.day=cnc--jrq</code> instead of <code>vending.machine.refill.day=wed</code> they are far more likely to ask around before they
067         * change it <b>OR</b> just give up and head out to lunch instead.
068         * </p>
069         * 
070         * @see reveal
071         */
072        public static final String conceal(String text) {
073                if (text == null) {
074                        return null;
075                }
076                Assert.noBlanks(text);
077                if (isConcealed(text)) {
078                        return text;
079                }
080                Assert.notConcealed(text);
081                char[] chars = text.toCharArray();
082                StringBuilder sb = new StringBuilder();
083                sb.append(CONCEALED_PREFIX);
084                for (char c : chars) {
085                        sb.append(Ascii.flip(c));
086                }
087                return sb.toString();
088        }
089
090        /**
091         * Reveal the original contents of a string concealed by the <code>conceal</code> method.
092         * 
093         * @see conceal
094         */
095        public static final String reveal(String text) {
096                if (text == null) {
097                        return null;
098                }
099                Assert.noBlanks(text);
100                if (!isConcealed(text)) {
101                        return text;
102                }
103                Assert.concealed(text);
104                String substring = removePrefix(text, CONCEALED_PREFIX);
105                char[] chars = substring.toCharArray();
106                StringBuilder sb = new StringBuilder();
107                for (char c : chars) {
108                        sb.append(Ascii.flip(c));
109                }
110                return sb.toString();
111        }
112
113        /**
114         * Return true if <code>text</code> is concealed
115         */
116        public static final boolean isConcealed(String text) {
117                return StringUtils.startsWith(text, CONCEALED_PREFIX);
118        }
119
120        /**
121         * If <code>strings</code> are <code>null</code> return <code>EMPTY_ARRAY</code>, otherwise return <code>strings</code>.
122         */
123        public static final String[] toEmptyArray(String[] strings) {
124                if (strings == null) {
125                        return EMPTY_ARRAY;
126                } else {
127                        return strings;
128                }
129        }
130
131        /**
132         * Convert the tokens into a string delimited by the colon "<code>:</code>" character
133         * 
134         * <pre>
135         *   "foo","bar" ,"baz"  -> foo:bar:baz
136         *   "foo", null ,"baz"  -> foo::baz
137         *   "foo", ""   ,"baz"  -> foo::baz
138         *   "foo", null , null  -> foo::
139         *    null,"bar" , null  -> :bar:
140         * </pre>
141         */
142        public static final String getId(String... tokens) {
143                if (tokens == null) {
144                        return null;
145                }
146                StringBuilder sb = new StringBuilder();
147                for (int i = 0; i < tokens.length; i++) {
148                        if (i != 0) {
149                                sb.append(COLON);
150                        }
151                        sb.append(StringUtils.trimToEmpty(tokens[i]));
152                }
153                return sb.toString();
154        }
155
156        /**
157         * Turn the string into CDATA - http://en.wikipedia.org/wiki/CDATA
158         */
159        public static final String cdata(String s) {
160                if (s == null) {
161                        return null;
162                } else {
163                        return CDATA_PREFIX + s + CDATA_SUFFIX;
164                }
165        }
166
167        /**
168         * If <code>s</code> ends with <code>suffix</code>, remove it
169         */
170        public static final String removeSuffix(String s, String suffix) {
171                if (StringUtils.endsWith(s, suffix)) {
172                        int end = StringUtils.length(s) - StringUtils.length(suffix);
173                        return StringUtils.substring(s, 0, end);
174                } else {
175                        return s;
176                }
177        }
178
179        /**
180         * If <code>s</code> starts with <code>prefix</code>, remove it
181         */
182        public static final String removePrefix(String s, String prefix) {
183                if (StringUtils.startsWith(s, prefix)) {
184                        int beginIndex = StringUtils.length(prefix);
185                        return StringUtils.substring(s, beginIndex);
186                } else {
187                        return s;
188                }
189        }
190
191        /**
192         * Return true if <code>s</code> starts with <code>prefix</code> and ends with <code>suffix</code>
193         */
194        public static final boolean matches(String s, String prefix, String suffix) {
195                return StringUtils.startsWith(s, prefix) && StringUtils.endsWith(s, suffix);
196        }
197
198        public static final String remove(String s, String prefix, String suffix) {
199                String returnValue = s;
200                returnValue = removePrefix(returnValue, prefix);
201                returnValue = removeSuffix(returnValue, suffix);
202                return returnValue;
203        }
204
205        /**
206         * If s is null return "" otherwise return s
207         */
208        public static final String toEmpty(String s) {
209                if (s == null) {
210                        return "";
211                } else {
212                        return s;
213                }
214        }
215
216        public static final String getAsciiString(byte[] bytes) {
217                return getString(bytes, ASCII);
218        }
219
220        public static final String getUTF8String(byte[] bytes) {
221                return getString(bytes, UTF8);
222        }
223
224        public static final String getString(byte[] bytes, String encoding) {
225                if (bytes == null) {
226                        return null;
227                }
228                if (encoding == null) {
229                        return new String(bytes);
230                }
231                try {
232                        return new String(bytes, encoding);
233                } catch (UnsupportedEncodingException e) {
234                        throw new IllegalArgumentException(e);
235                }
236        }
237
238        public static final byte[] getAsciiBytes(String s) {
239                if (s == null) {
240                        return null;
241                } else {
242                        return getBytes(s, ASCII);
243                }
244        }
245
246        public static final byte[] getUTF8Bytes(String s) {
247                if (s == null) {
248                        return null;
249                } else {
250                        return getBytes(s, UTF8);
251                }
252        }
253
254        public static final byte[] getBytes(String s, String encoding) {
255                if (s == null) {
256                        return null;
257                }
258                if (encoding == null) {
259                        return s.getBytes();
260                }
261                try {
262                        return s.getBytes(encoding);
263                } catch (UnsupportedEncodingException e) {
264                        throw new IllegalArgumentException(e);
265                }
266        }
267
268        public static final boolean contains(List<String> tokens, String value, boolean caseSensitive) {
269                for (String token : tokens) {
270                        if (equals(token, value, caseSensitive)) {
271                                return true;
272                        }
273                }
274                return false;
275        }
276
277        public static final boolean equals(String s1, String s2, boolean caseSensitive) {
278                if (caseSensitive) {
279                        return StringUtils.equals(s1, s2);
280                } else {
281                        return StringUtils.equalsIgnoreCase(s1, s2);
282                }
283        }
284
285        /**
286         * Combine <code>tokens</code> into a <code>String</code>
287         */
288        public static final String toString(String[] tokens) {
289                if (tokens == null) {
290                        return null;
291                }
292                StringBuilder sb = new StringBuilder();
293                for (String token : tokens) {
294                        sb.append(token);
295                }
296                return sb.toString();
297        }
298
299        /**
300         * Convert dots to forward slashes and trim.
301         */
302        public static final String getPath(String s) {
303                return trim(replace(s, DOT, FORWARD_SLASH));
304        }
305
306        /**
307         * Surround the string with double quotes.
308         */
309        public static final String quote(String s) {
310                return s == null ? null : DOUBLE_QUOTE + s + DOUBLE_QUOTE;
311        }
312
313        /**
314         * Split comma separated values into tokens, optionally trimming the tokens.
315         */
316        public static final String[] splitCSV(String csv, boolean trim) {
317                return split(csv, COMMA, trim);
318        }
319
320        /**
321         * Split comma separated values into tokens, trimming as we go.
322         */
323        public static final String[] splitAndTrimCSV(String csv) {
324                return splitCSV(csv, true);
325        }
326
327        /**
328         * Split the string into tokens using the indicated separator, trimming as we go.
329         */
330        public static final String[] splitAndTrim(String s, String separatorChars) {
331                return split(s, separatorChars, true);
332        }
333
334        /**
335         * Split the string into tokens using the indicated separator chars, optionally trimming the tokens.
336         */
337        public static final String[] split(String s, String separatorChars, boolean trim) {
338                String[] tokens = StringUtils.split(s, separatorChars);
339                if (tokens == null) {
340                        return null;
341                }
342                for (int i = 0; i < tokens.length; i++) {
343                        tokens[i] = trim ? StringUtils.trim(tokens[i]) : tokens[i];
344                }
345                return tokens;
346        }
347
348        /**
349         * Replace carriage returns and linefeeds with a space
350         */
351        public static final String flatten(String s) {
352                return flatten(s, SPACE, SPACE);
353        }
354
355        /**
356         * Replace carriage returns with <code>cr</code> and linefeeds with <code>lf</code>.
357         */
358        public static final String flatten(String s, String cr, String lf) {
359                return StringUtils.replace(StringUtils.replace(s, CR, cr), LF, lf);
360        }
361
362        /**
363         * Replace <code>cr</code> with carriage return and <code>lf</code> with linefeed.
364         */
365        public static final String inflate(String s, String cr, String lf) {
366                return StringUtils.replace(StringUtils.replace(s, cr, CR), lf, LF);
367        }
368}