001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 003// 004// Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0 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// -------------------------------------------------------------------------------- 016 017package com.echothree.util.common.string; 018 019import com.echothree.model.control.core.common.MimeTypes; 020import com.google.common.html.HtmlEscapers; 021import com.ibm.icu.text.Transliterator; 022import java.util.Arrays; 023import java.util.Iterator; 024import java.util.Locale; 025 026public class StringUtils { 027 028 private StringUtils() { 029 super(); 030 } 031 032 private static class StringUtilsHolder { 033 static StringUtils instance = new StringUtils(); 034 } 035 036 public static StringUtils getInstance() { 037 return StringUtilsHolder.instance; 038 } 039 040 private Transliterator transliterator = Transliterator.getInstance("Any-Latin; Latin-ASCII; NFKD; [:Nonspacing Mark:] Remove; NFKC", Transliterator.FORWARD); 041 042 public String toAscii7(String text) { 043 return transliterator.transliterate(text); 044 } 045 046 public String getIndent(int indentAmount, int indentCount) { 047 return String.format("%" + indentAmount * indentCount + "s", ""); 048 } 049 050 /** Take a string and trim all leading and trailing spaces from it. 051 * If the resulting length is zero, return null. 052 */ 053 public String trimToNull(String string) { 054 String result = null; 055 056 if(string != null) { 057 result = string.trim(); 058 059 if(result.length() == 0) { 060 result = null; 061 } 062 } 063 064 return result; 065 } 066 067 /** 068 * null-safe String comparison. 069 */ 070 public int nullSafeCompareTo(final String s1, final String s2) { 071 if (s1 == null ^ s2 == null) { 072 return (s1 == null) ? -1 : 1; 073 } 074 075 if (s1 == null && s2 == null) { 076 return 0; 077 } 078 079 return s1.compareTo(s2); 080 } 081 082 public String left(String str, int len) { 083 return str == null ? null : str.length() <= len ? str : str.substring(0, len); 084 } 085 086 public String right(String str, int len) { 087 return str == null ? null : str.length() <= len ? str : str.substring(str.length() - len); 088 } 089 090 public String cleanStringToName(final String s) { 091 char[] a = s.toCharArray(); 092 int l = a.length; 093 StringBuilder r = new StringBuilder(l); 094 095 for(int i = 0; i < l; i++) { 096 char t = a[i]; 097 098 if(!Character.isLetterOrDigit(t) && t != '-' && t != '_') { 099 t = '_'; 100 } 101 102 r.append(t); 103 104 } 105 106 return r.toString(); 107 } 108 109 public int countSpaces(final String str) { 110 int count = 0; 111 112 for(int codePoint : codePoints(str)) { 113 if(Character.isSpaceChar(codePoint)) { 114 count++; 115 } 116 } 117 118 return count; 119 } 120 121 // Look for the first Space character in a String, and construct a new String that contains it. 122 public String getSpace(final String str) { 123 String result = null; 124 125 for(int codePoint : codePoints(str)) { 126 if(Character.isSpaceChar(codePoint)) { 127 result = new String(new int[] { codePoint }, 0, 1); 128 break; 129 } 130 } 131 132 return result; 133 } 134 135 public String cleanString(final String str, final boolean includeDigits, final boolean includeLetters, final boolean includeSpaces) { 136 StringBuilder sb = new StringBuilder(); 137 138 for(int codePoint : codePoints(str)) { 139 boolean appended = false; 140 141 if(includeDigits && includeLetters) { 142 if(Character.isLetterOrDigit(codePoint)) { 143 sb.appendCodePoint(codePoint); 144 appended = true; 145 } 146 } else { 147 if(includeDigits && Character.isDigit(codePoint)) { 148 sb.appendCodePoint(codePoint); 149 appended = true; 150 } 151 152 if(!appended && includeLetters && Character.isLetter(codePoint)) { 153 sb.appendCodePoint(codePoint); 154 appended = true; 155 } 156 } 157 158 if(!appended && includeSpaces && Character.isSpaceChar(codePoint)) { 159 sb.appendCodePoint(codePoint); 160 } 161 } 162 163 return sb.toString(); 164 } 165 166 public String cleanStringToLettersOrDigits(final String str) { 167 return cleanString(str, true, true, false); 168 } 169 170 public String upperCaseFirstCharacter(final String str) { 171 return str == null ? null : str.length() == 0 ? str : new StringBuilder(str.substring(0,1).toUpperCase(Locale.getDefault())).append(str.substring(1)).toString(); 172 } 173 174 public String lowerCaseFirstCharacter(final String str) { 175 return str == null ? null : str.length() == 0 ? str : new StringBuilder(str.substring(0,1).toLowerCase(Locale.getDefault())).append(str.substring(1)).toString(); 176 } 177 178 public String normalizeCase(final String str) { 179 return str == null ? null : str.length() == 0 ? str : new StringBuilder(str.substring(0,1).toUpperCase(Locale.getDefault())).append(str.substring(1).toLowerCase(Locale.getDefault())).toString(); 180 } 181 182 // Similar to http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5003547 183 public Iterable<Integer> codePoints(final String str) { 184 return () -> new Iterator<Integer>() { 185 int nextIndex = 0; 186 int len = str.length(); 187 188 @Override 189 public boolean hasNext() { 190 return nextIndex < len; 191 } 192 193 @Override 194 public Integer next() { 195 int result = str.codePointAt(nextIndex); 196 nextIndex += Character.charCount(result); 197 return result; 198 } 199 200 @Override 201 public void remove() { 202 throw new UnsupportedOperationException(); 203 } 204 }; 205 } 206 207 public String convertToHtml(final String value, final String mimeTypeName) { 208 if(mimeTypeName.equals(MimeTypes.TEXT_HTML.mimeTypeName())) { 209 return value; 210 } else if(mimeTypeName.equals(MimeTypes.TEXT_PLAIN.mimeTypeName())) { 211 return new StringBuilder("<pre>").append(HtmlEscapers.htmlEscaper().escape(value)).append("</pre>").toString(); 212 } else if(mimeTypeName.equals(MimeTypes.TEXT_X_MARKUP.mimeTypeName())) { 213 return MarkupUtils.getInstance().filter(value); 214 } else if(mimeTypeName.equals(MimeTypes.TEXT_X_TEXTILE.mimeTypeName())) { 215 return TextileUtils.getInstance().filter(value); 216 } else { 217 return "Unsupported mimeTypeName."; 218 } 219 } 220 221 public String mask(final String value, final char mask, final int unmasked) { 222 int len = value.length(); 223 String result; 224 225 // Make sure there's enough String to mask anything at all 226 if(len - unmasked > 0) { 227 char maskChars[] = new char[len - unmasked]; 228 229 Arrays.fill(maskChars, mask); 230 231 result = new StringBuilder().append(maskChars).append(value, len - unmasked, len).toString(); 232 } else { 233 result = value; 234 } 235 236 return result; 237 } 238 239 public String mask(final String value, final char mask) { 240 char maskChars[] = new char[value.length()]; 241 242 Arrays.fill(maskChars, mask); 243 244 return new String(maskChars); 245 } 246 247 public String repeatingStringFromChar(final char val, final int length) { 248 char[] data = new char[length]; 249 250 Arrays.fill(data, val); 251 252 return String.valueOf(data); 253 } 254 255 public boolean isAllUpperCase(String str) { 256 return str == null ? false : str.equals(str.toUpperCase(Locale.getDefault())); 257 } 258 259 public boolean isAllLowerCase(String str) { 260 return str == null ? false : str.equals(str.toLowerCase(Locale.getDefault())); 261 } 262 263 public boolean isAllSameCase(String str) { 264 return str == null ? false : isAllUpperCase(str) || isAllLowerCase(str); 265 } 266 267}