001/** 002 * Copyright 2010-2013 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 java.io.UnsupportedEncodingException; 019import java.util.List; 020 021import org.apache.commons.lang3.StringUtils; 022 023/** 024 * Operations on <code>String</code> that are <code>null</code> safe 025 */ 026public class Str { 027 028 public static final String EMPTY_STRING = ""; 029 public static final String UTF8 = Encodings.UTF8; 030 public static final String COMMA = ","; 031 public static final String SPACE = " "; 032 public static final String CR = "\r"; 033 public static final String LF = "\n"; 034 public static final String DOT = "."; 035 public static final String COLON = ":"; 036 public static final String FORWARD_SLASH = "/"; 037 public static final char DOUBLE_QUOTE = '"'; 038 public static final String CDATA_PREFIX = "<![CDATA["; 039 public static final String CDATA_SUFFIX = "]]>"; 040 public static final String[] EMPTY_ARRAY = new String[0]; 041 042 private static final String CONCEALED_PREFIX = "cnc--"; 043 044 /** 045 * <p> 046 * 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 047 * 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 048 * secret knowledge. 049 * </p> 050 * 051 * <p> 052 * 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 053 * to defeat any serious attempt at discovering the original text. 054 * </p> 055 * 056 * <p> 057 * 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 058 * <code>mon</code> in order to beat a case of the munchies. :) 059 * </p> 060 * 061 * <p> 062 * 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 063 * change it <b>OR</b> just give up and head out to lunch instead. 064 * </p> 065 * 066 * @see reveal 067 */ 068 public static final String conceal(String text) { 069 if (text == null) { 070 return null; 071 } 072 Assert.noBlanks(text); 073 if (isConcealed(text)) { 074 return text; 075 } 076 Assert.notConcealed(text); 077 char[] chars = text.toCharArray(); 078 StringBuilder sb = new StringBuilder(); 079 sb.append(CONCEALED_PREFIX); 080 for (char c : chars) { 081 sb.append(Ascii.flip(c)); 082 } 083 return sb.toString(); 084 } 085 086 /** 087 * Reveal the original contents of a string concealed by the <code>conceal</code> method. 088 * 089 * @see conceal 090 */ 091 public static final String reveal(String text) { 092 if (text == null) { 093 return null; 094 } 095 Assert.noBlanks(text); 096 if (!isConcealed(text)) { 097 return text; 098 } 099 Assert.concealed(text); 100 String substring = removePrefix(text, CONCEALED_PREFIX); 101 char[] chars = substring.toCharArray(); 102 StringBuilder sb = new StringBuilder(); 103 for (char c : chars) { 104 sb.append(Ascii.flip(c)); 105 } 106 return sb.toString(); 107 } 108 109 /** 110 * Return true if <code>text</code> is concealed 111 */ 112 public static final boolean isConcealed(String text) { 113 return StringUtils.startsWith(text, CONCEALED_PREFIX); 114 } 115 116 /** 117 * If <code>strings</code> are <code>null</code> return <code>EMPTY_ARRAY</code>, otherwise return <code>strings</code>. 118 */ 119 public static final String[] toEmptyArray(String[] strings) { 120 if (strings == null) { 121 return EMPTY_ARRAY; 122 } else { 123 return strings; 124 } 125 } 126 127 /** 128 * Convert the tokens into a string delimited by the colon "<code>:</code>" character 129 * 130 * <pre> 131 * "foo","bar" ,"baz" -> foo:bar:baz 132 * "foo", null ,"baz" -> foo::baz 133 * "foo", "" ,"baz" -> foo::baz 134 * "foo", null , null -> foo:: 135 * null,"bar" , null -> :bar: 136 * </pre> 137 */ 138 public static final String getId(String... tokens) { 139 if (tokens == null) { 140 return null; 141 } 142 StringBuilder sb = new StringBuilder(); 143 for (int i = 0; i < tokens.length; i++) { 144 if (i != 0) { 145 sb.append(COLON); 146 } 147 sb.append(StringUtils.trimToEmpty(tokens[i])); 148 } 149 return sb.toString(); 150 } 151 152 /** 153 * Turn the string into CDATA - http://en.wikipedia.org/wiki/CDATA 154 */ 155 public static final String cdata(String s) { 156 if (s == null) { 157 return null; 158 } else { 159 return CDATA_PREFIX + s + CDATA_SUFFIX; 160 } 161 } 162 163 /** 164 * If <code>s</code> ends with <code>suffix</code>, remove it 165 */ 166 public static final String removeSuffix(String s, String suffix) { 167 if (StringUtils.endsWith(s, suffix)) { 168 int end = StringUtils.length(s) - StringUtils.length(suffix); 169 return StringUtils.substring(s, 0, end); 170 } else { 171 return s; 172 } 173 } 174 175 /** 176 * If <code>s</code> starts with <code>prefix</code>, remove it 177 */ 178 public static final String removePrefix(String s, String prefix) { 179 if (StringUtils.startsWith(s, prefix)) { 180 int beginIndex = StringUtils.length(prefix); 181 return StringUtils.substring(s, beginIndex); 182 } else { 183 return s; 184 } 185 } 186 187 /** 188 * Return true if <code>s</code> starts with <code>prefix</code> and ends with <code>suffix</code> 189 */ 190 public static final boolean matches(String s, String prefix, String suffix) { 191 return StringUtils.startsWith(s, prefix) && StringUtils.endsWith(s, suffix); 192 } 193 194 public static final String remove(String s, String prefix, String suffix) { 195 String returnValue = s; 196 returnValue = removePrefix(returnValue, prefix); 197 returnValue = removeSuffix(returnValue, suffix); 198 return returnValue; 199 } 200 201 /** 202 * If s is null return "" otherwise return s 203 */ 204 public static final String toEmpty(String s) { 205 if (s == null) { 206 return ""; 207 } else { 208 return s; 209 } 210 } 211 212 public static final String getString(byte[] bytes, String encoding) { 213 if (bytes == null) { 214 return null; 215 } 216 if (encoding == null) { 217 return new String(bytes); 218 } 219 try { 220 return new String(bytes, encoding); 221 } catch (UnsupportedEncodingException e) { 222 throw new IllegalArgumentException(e); 223 } 224 } 225 226 public static final byte[] getUTF8Bytes(String s) { 227 if (s == null) { 228 return null; 229 } else { 230 return getBytes(s, UTF8); 231 } 232 } 233 234 public static final byte[] getBytes(String s, String encoding) { 235 if (s == null) { 236 return null; 237 } 238 if (encoding == null) { 239 return s.getBytes(); 240 } 241 try { 242 return s.getBytes(encoding); 243 } catch (UnsupportedEncodingException e) { 244 throw new IllegalArgumentException(e); 245 } 246 } 247 248 public static final boolean contains(List<String> tokens, String value, boolean caseSensitive) { 249 for (String token : tokens) { 250 if (equals(token, value, caseSensitive)) { 251 return true; 252 } 253 } 254 return false; 255 } 256 257 public static final boolean equals(String s1, String s2, boolean caseSensitive) { 258 if (caseSensitive) { 259 return StringUtils.equals(s1, s2); 260 } else { 261 return StringUtils.equalsIgnoreCase(s1, s2); 262 } 263 } 264 265 /** 266 * Combine <code>tokens</code> into a <code>String</code> 267 */ 268 public static final String toString(String[] tokens) { 269 if (tokens == null) { 270 return null; 271 } 272 StringBuilder sb = new StringBuilder(); 273 for (String token : tokens) { 274 sb.append(token); 275 } 276 return sb.toString(); 277 } 278 279 /** 280 * Convert dots to forward slashes and trim. 281 */ 282 public static final String getPath(String s) { 283 return StringUtils.trim(StringUtils.replace(s, DOT, FORWARD_SLASH)); 284 } 285 286 /** 287 * Surround the string with double quotes. 288 */ 289 public static final String quote(String s) { 290 return s == null ? null : DOUBLE_QUOTE + s + DOUBLE_QUOTE; 291 } 292 293 /** 294 * Split comma separated values into tokens, optionally trimming the tokens. 295 */ 296 public static final String[] splitCSV(String csv, boolean trim) { 297 return split(csv, COMMA, trim); 298 } 299 300 /** 301 * Split comma separated values into tokens, trimming as we go. 302 */ 303 public static final String[] splitAndTrimCSV(String csv) { 304 return splitCSV(csv, true); 305 } 306 307 /** 308 * Split the string into tokens using the indicated separator, trimming as we go. 309 */ 310 public static final String[] splitAndTrim(String s, String separatorChars) { 311 return split(s, separatorChars, true); 312 } 313 314 /** 315 * Split the string into tokens using the indicated separator chars, optionally trimming the tokens. 316 */ 317 public static final String[] split(String s, String separatorChars, boolean trim) { 318 String[] tokens = StringUtils.split(s, separatorChars); 319 if (tokens == null) { 320 return null; 321 } 322 for (int i = 0; i < tokens.length; i++) { 323 tokens[i] = trim ? StringUtils.trim(tokens[i]) : tokens[i]; 324 } 325 return tokens; 326 } 327 328 /** 329 * Replace carriage returns and linefeeds with a space 330 */ 331 public static final String flatten(String s) { 332 return flatten(s, SPACE, SPACE); 333 } 334 335 /** 336 * Replace carriage returns with <code>cr</code> and linefeeds with <code>lf</code>. 337 */ 338 public static final String flatten(String s, String cr, String lf) { 339 return StringUtils.replace(StringUtils.replace(s, CR, cr), LF, lf); 340 } 341 342 /** 343 * Replace <code>cr</code> with carriage return and <code>lf</code> with linefeed. 344 */ 345 public static final String inflate(String s, String cr, String lf) { 346 return StringUtils.replace(StringUtils.replace(s, cr, CR), lf, LF); 347 } 348}