001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.util;
020
021import com.plotsquared.core.PlotSquared;
022import com.plotsquared.core.configuration.caption.Caption;
023import org.checkerframework.checker.nullness.qual.NonNull;
024
025import java.lang.reflect.Array;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.Collection;
029import java.util.Comparator;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Map.Entry;
034import java.util.Set;
035import java.util.regex.Pattern;
036
037public class StringMan {
038
039    // Stolen from https://stackoverflow.com/a/366532/12620913 | Debug: https://regex101.com/r/DudJLb/1
040    private static final Pattern STRING_SPLIT_PATTERN = Pattern.compile("[^\\s\"]+|\"([^\"]*)\"");
041
042    public static String replaceFromMap(String string, Map<String, String> replacements) {
043        StringBuilder sb = new StringBuilder(string);
044        int size = string.length();
045        for (Entry<String, String> entry : replacements.entrySet()) {
046            if (size == 0) {
047                break;
048            }
049            String key = entry.getKey();
050            String value = entry.getValue();
051            int start = sb.indexOf(key, 0);
052            while (start > -1) {
053                int end = start + key.length();
054                int nextSearchStart = start + value.length();
055                sb.replace(start, end, value);
056                size -= end - start;
057                start = sb.indexOf(key, nextSearchStart);
058            }
059        }
060        return sb.toString();
061    }
062
063    public static int intersection(Set<String> options, String[] toCheck) {
064        int count = 0;
065        for (String check : toCheck) {
066            if (options.contains(check)) {
067                count++;
068            }
069        }
070        return count;
071    }
072
073    public static String getString(Object obj) {
074        if (obj == null) {
075            return "null";
076        }
077        if (obj instanceof String) {
078            return (String) obj;
079        }
080        if (obj instanceof Caption) {
081            return ((Caption) obj).getComponent(PlotSquared.platform());
082        }
083        if (obj.getClass().isArray()) {
084            StringBuilder result = new StringBuilder();
085            String prefix = "";
086
087            for (int i = 0; i < Array.getLength(obj); i++) {
088                result.append(prefix).append(getString(Array.get(obj, i)));
089                prefix = ",";
090            }
091            return "( " + result + " )";
092        } else if (obj instanceof Collection<?>) {
093            StringBuilder result = new StringBuilder();
094            String prefix = "";
095            for (Object element : (Collection<?>) obj) {
096                result.append(prefix).append(getString(element));
097                prefix = ",";
098            }
099            return "[ " + result + " ]";
100        } else {
101            return obj.toString();
102        }
103    }
104
105    public static String replaceFirst(char c, String s) {
106        if (s == null) {
107            return "";
108        }
109        if (s.isEmpty()) {
110            return s;
111        }
112        char[] chars = s.toCharArray();
113        char[] newChars = new char[chars.length];
114        int used = 0;
115        boolean found = false;
116        for (char cc : chars) {
117            if (!found && (c == cc)) {
118                found = true;
119            } else {
120                newChars[used++] = cc;
121            }
122        }
123        if (found) {
124            chars = new char[newChars.length - 1];
125            System.arraycopy(newChars, 0, chars, 0, chars.length);
126            return String.valueOf(chars);
127        }
128        return s;
129    }
130
131    public static String replaceAll(String string, Object... pairs) {
132        StringBuilder sb = new StringBuilder(string);
133        for (int i = 0; i < pairs.length; i += 2) {
134            String key = pairs[i] + "";
135            String value = pairs[i + 1] + "";
136            int start = sb.indexOf(key, 0);
137            while (start > -1) {
138                int end = start + key.length();
139                int nextSearchStart = start + value.length();
140                sb.replace(start, end, value);
141                start = sb.indexOf(key, nextSearchStart);
142            }
143        }
144        return sb.toString();
145    }
146
147    public static boolean isAlphanumeric(String str) {
148        for (int i = 0; i < str.length(); i++) {
149            char c = str.charAt(i);
150            if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c
151                    > 0x7a)) {
152                return false;
153            }
154        }
155        return true;
156    }
157
158    public static boolean isAlphanumericUnd(String str) {
159        for (int i = 0; i < str.length(); i++) {
160            char c = str.charAt(i);
161            if (c < 0x30 || (c >= 0x3a) && (c <= 0x40) || (c > 0x5a) && (c <= 0x60) || (c > 0x7a)) {
162                return false;
163            }
164        }
165        return true;
166    }
167
168    public static boolean isAlpha(String str) {
169        for (int i = 0; i < str.length(); i++) {
170            char c = str.charAt(i);
171            if ((c <= 0x40) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a)) {
172                return false;
173            }
174        }
175        return true;
176    }
177
178    public static String join(Collection<?> collection, String delimiter) {
179        return join(collection.toArray(), delimiter);
180    }
181
182    public static String joinOrdered(Collection<?> collection, String delimiter) {
183        Object[] array = collection.toArray();
184        Arrays.sort(array, Comparator.comparingInt(Object::hashCode));
185        return join(array, delimiter);
186    }
187
188    public static String join(Collection<?> collection, char delimiter) {
189        return join(collection.toArray(), delimiter + "");
190    }
191
192    public static boolean isAsciiPrintable(char c) {
193        return (c >= ' ') && (c < '');
194    }
195
196    public static boolean isAsciiPrintable(String s) {
197        for (char c : s.toCharArray()) {
198            if (!isAsciiPrintable(c)) {
199                return false;
200            }
201        }
202        return true;
203    }
204
205    public static int getLevenshteinDistance(String s, String t) {
206        int n = s.length();
207        int m = t.length();
208        if (n == 0) {
209            return m;
210        } else if (m == 0) {
211            return n;
212        }
213        if (n > m) {
214            String tmp = s;
215            s = t;
216            t = tmp;
217            n = m;
218            m = t.length();
219        }
220        int[] p = new int[n + 1];
221        int[] d = new int[n + 1];
222        int i;
223        for (i = 0; i <= n; i++) {
224            p[i] = i;
225        }
226        for (int j = 1; j <= m; j++) {
227            char t_j = t.charAt(j - 1);
228            d[0] = j;
229
230            for (i = 1; i <= n; i++) {
231                int cost = s.charAt(i - 1) == t_j ? 0 : 1;
232                d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
233            }
234            int[] _d = p;
235            p = d;
236            d = _d;
237        }
238        return p[n];
239    }
240
241    public static String join(Object[] array, String delimiter) {
242        StringBuilder result = new StringBuilder();
243        for (int i = 0, j = array.length; i < j; i++) {
244            if (i > 0) {
245                result.append(delimiter);
246            }
247            result.append(array[i]);
248        }
249        return result.toString();
250    }
251
252    public static String join(int[] array, String delimiter) {
253        Integer[] wrapped = new Integer[array.length];
254        for (int i = 0; i < array.length; i++) {
255            wrapped[i] = array[i];
256        }
257        return join(wrapped, delimiter);
258    }
259
260    public static boolean isEqualToAny(String a, String... args) {
261        for (String arg : args) {
262            if (StringMan.isEqual(a, arg)) {
263                return true;
264            }
265        }
266        return false;
267    }
268
269    public static boolean isEqualIgnoreCaseToAny(@NonNull String a, String... args) {
270        for (String arg : args) {
271            if (a.equalsIgnoreCase(arg)) {
272                return true;
273            }
274        }
275        return false;
276    }
277
278    public static boolean isEqual(String a, String b) {
279        if ((a == null && b != null) || (a != null && b == null)) {
280            return false;
281        } else if (a == null /* implies that b is null */) {
282            return false;
283        }
284        return a.equals(b);
285    }
286
287    public static boolean isEqualIgnoreCase(String a, String b) {
288        return a.equals(b) || ((a != null) && (b != null) && (a.length() == b.length()) && a
289                .equalsIgnoreCase(b));
290    }
291
292    public static String repeat(String s, int n) {
293        StringBuilder sb = new StringBuilder();
294        sb.append(String.valueOf(s).repeat(Math.max(0, n)));
295        return sb.toString();
296    }
297
298    public static boolean contains(String name, char c) {
299        for (char current : name.toCharArray()) {
300            if (c == current) {
301                return true;
302            }
303        }
304        return false;
305    }
306
307    public <T> Collection<T> match(Collection<T> col, String startsWith) {
308        if (col == null) {
309            return null;
310        }
311        startsWith = startsWith.toLowerCase();
312        Iterator<?> iterator = col.iterator();
313        while (iterator.hasNext()) {
314            Object item = iterator.next();
315            if (item == null || !item.toString().toLowerCase().startsWith(startsWith)) {
316                iterator.remove();
317            }
318        }
319        return col;
320    }
321
322    /**
323     * @param message an input string
324     * @return a list of strings
325     * @since 6.4.0
326     *
327     *         <table border="1">
328     *         <caption>Converts multiple quoted and single strings into a list of strings</caption>
329     *         <thead>
330     *           <tr>
331     *             <th>Input</th>
332     *             <th>Output</th>
333     *           </tr>
334     *         </thead>
335     *         <tbody>
336     *           <tr>
337     *             <td>title "sub title"</td>
338     *             <td>["title", "sub title"]</td>
339     *           </tr>
340     *           <tr>
341     *             <td>"a title" subtitle</td>
342     *             <td>["a title", "subtitle"]</td>
343     *           </tr>
344     *           <tr>
345     *             <td>"title" "subtitle"</td>
346     *             <td>["title", "subtitle"]</td>
347     *           </tr>
348     *           <tr>
349     *             <td>"PlotSquared is going well" the authors "and many contributors"</td>
350     *             <td>["PlotSquared is going well", "the", "authors", "and many contributors"]</td>
351     *           </tr>
352     *         </tbody>
353     *         </table>
354     */
355    public static @NonNull List<String> splitMessage(@NonNull String message) {
356        var matcher = StringMan.STRING_SPLIT_PATTERN.matcher(message);
357        List<String> splitMessages = new ArrayList<>();
358        while (matcher.find()) {
359            splitMessages.add(matcher.group(matcher.groupCount() - 1).replaceAll("\"", ""));
360        }
361        return splitMessages;
362    }
363
364}