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}