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    /**
043     * @deprecated Unused internally. Scheduled for removal in next major release.
044     */
045    @Deprecated(forRemoval = true, since = "6.11.1")
046    public static String replaceFromMap(String string, Map<String, String> replacements) {
047        StringBuilder sb = new StringBuilder(string);
048        int size = string.length();
049        for (Entry<String, String> entry : replacements.entrySet()) {
050            if (size == 0) {
051                break;
052            }
053            String key = entry.getKey();
054            String value = entry.getValue();
055            int start = sb.indexOf(key, 0);
056            while (start > -1) {
057                int end = start + key.length();
058                int nextSearchStart = start + value.length();
059                sb.replace(start, end, value);
060                size -= end - start;
061                start = sb.indexOf(key, nextSearchStart);
062            }
063        }
064        return sb.toString();
065    }
066
067    public static int intersection(Set<String> options, String[] toCheck) {
068        int count = 0;
069        for (String check : toCheck) {
070            if (options.contains(check)) {
071                count++;
072            }
073        }
074        return count;
075    }
076
077    /**
078     * @deprecated Unused internally. Scheduled for removal in next major release.
079     */
080    @Deprecated(forRemoval = true, since = "6.11.1")
081    public static String getString(Object obj) {
082        if (obj == null) {
083            return "null";
084        }
085        if (obj instanceof String) {
086            return (String) obj;
087        }
088        if (obj instanceof Caption) {
089            return ((Caption) obj).getComponent(PlotSquared.platform());
090        }
091        if (obj.getClass().isArray()) {
092            StringBuilder result = new StringBuilder();
093            String prefix = "";
094
095            for (int i = 0; i < Array.getLength(obj); i++) {
096                result.append(prefix).append(getString(Array.get(obj, i)));
097                prefix = ",";
098            }
099            return "( " + result + " )";
100        } else if (obj instanceof Collection<?>) {
101            StringBuilder result = new StringBuilder();
102            String prefix = "";
103            for (Object element : (Collection<?>) obj) {
104                result.append(prefix).append(getString(element));
105                prefix = ",";
106            }
107            return "[ " + result + " ]";
108        } else {
109            return obj.toString();
110        }
111    }
112
113    /**
114     * @deprecated Unused internally. Scheduled for removal in next major release.
115     */
116    @Deprecated(forRemoval = true, since = "6.11.1")
117    public static String replaceFirst(char c, String s) {
118        if (s == null) {
119            return "";
120        }
121        if (s.isEmpty()) {
122            return s;
123        }
124        char[] chars = s.toCharArray();
125        char[] newChars = new char[chars.length];
126        int used = 0;
127        boolean found = false;
128        for (char cc : chars) {
129            if (!found && (c == cc)) {
130                found = true;
131            } else {
132                newChars[used++] = cc;
133            }
134        }
135        if (found) {
136            chars = new char[newChars.length - 1];
137            System.arraycopy(newChars, 0, chars, 0, chars.length);
138            return String.valueOf(chars);
139        }
140        return s;
141    }
142
143    /**
144     * @deprecated Unused internally. Scheduled for removal in next major release.
145     */
146    @Deprecated(forRemoval = true, since = "6.11.1")
147    public static String replaceAll(String string, Object... pairs) {
148        StringBuilder sb = new StringBuilder(string);
149        for (int i = 0; i < pairs.length; i += 2) {
150            String key = pairs[i] + "";
151            String value = pairs[i + 1] + "";
152            int start = sb.indexOf(key, 0);
153            while (start > -1) {
154                int end = start + key.length();
155                int nextSearchStart = start + value.length();
156                sb.replace(start, end, value);
157                start = sb.indexOf(key, nextSearchStart);
158            }
159        }
160        return sb.toString();
161    }
162
163    /**
164     * @deprecated Unused internally. Scheduled for removal in next major release.
165     */
166    @Deprecated(forRemoval = true, since = "6.11.1")
167    public static boolean isAlphanumeric(String str) {
168        for (int i = 0; i < str.length(); i++) {
169            char c = str.charAt(i);
170            if ((c < 0x30) || ((c >= 0x3a) && (c <= 0x40)) || ((c > 0x5a) && (c <= 0x60)) || (c
171                    > 0x7a)) {
172                return false;
173            }
174        }
175        return true;
176    }
177
178    public static boolean isAlphanumericUnd(String str) {
179        for (int i = 0; i < str.length(); i++) {
180            char c = str.charAt(i);
181            if (c < 0x30 || (c >= 0x3a) && (c <= 0x40) || (c > 0x5a) && (c <= 0x60) || (c > 0x7a)) {
182                return false;
183            }
184        }
185        return true;
186    }
187
188    /**
189     * @deprecated Unused internally. Scheduled for removal in next major release.
190     */
191    @Deprecated(forRemoval = true, since = "6.11.1")
192    public static boolean isAlpha(String str) {
193        for (int i = 0; i < str.length(); i++) {
194            char c = str.charAt(i);
195            if ((c <= 0x40) || ((c > 0x5a) && (c <= 0x60)) || (c > 0x7a)) {
196                return false;
197            }
198        }
199        return true;
200    }
201
202    public static String join(Collection<?> collection, String delimiter) {
203        return join(collection.toArray(), delimiter);
204    }
205
206    public static String joinOrdered(Collection<?> collection, String delimiter) {
207        Object[] array = collection.toArray();
208        Arrays.sort(array, Comparator.comparingInt(Object::hashCode));
209        return join(array, delimiter);
210    }
211
212    /**
213     * @deprecated Unused internally. Scheduled for removal in next major release.
214     */
215    @Deprecated(forRemoval = true, since = "6.11.1")
216    public static String join(Collection<?> collection, char delimiter) {
217        return join(collection.toArray(), delimiter + "");
218    }
219
220    /**
221     * @deprecated Unused internally. Scheduled for removal in next major release.
222     */
223    @Deprecated(forRemoval = true, since = "6.11.1")
224    public static boolean isAsciiPrintable(char c) {
225        return (c >= ' ') && (c < '');
226    }
227
228    /**
229     * @deprecated Unused internally. Scheduled for removal in next major release.
230     */
231    @Deprecated(forRemoval = true, since = "6.11.1")
232    public static boolean isAsciiPrintable(String s) {
233        for (char c : s.toCharArray()) {
234            if (!isAsciiPrintable(c)) {
235                return false;
236            }
237        }
238        return true;
239    }
240
241    public static int getLevenshteinDistance(String s, String t) {
242        int n = s.length();
243        int m = t.length();
244        if (n == 0) {
245            return m;
246        } else if (m == 0) {
247            return n;
248        }
249        if (n > m) {
250            String tmp = s;
251            s = t;
252            t = tmp;
253            n = m;
254            m = t.length();
255        }
256        int[] p = new int[n + 1];
257        int[] d = new int[n + 1];
258        int i;
259        for (i = 0; i <= n; i++) {
260            p[i] = i;
261        }
262        for (int j = 1; j <= m; j++) {
263            char t_j = t.charAt(j - 1);
264            d[0] = j;
265
266            for (i = 1; i <= n; i++) {
267                int cost = s.charAt(i - 1) == t_j ? 0 : 1;
268                d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1] + cost);
269            }
270            int[] _d = p;
271            p = d;
272            d = _d;
273        }
274        return p[n];
275    }
276
277    public static String join(Object[] array, String delimiter) {
278        StringBuilder result = new StringBuilder();
279        for (int i = 0, j = array.length; i < j; i++) {
280            if (i > 0) {
281                result.append(delimiter);
282            }
283            result.append(array[i]);
284        }
285        return result.toString();
286    }
287
288    /**
289     * @deprecated Unused internally. Scheduled for removal in next major release.
290     */
291    @Deprecated(forRemoval = true, since = "6.11.1")
292    public static String join(int[] array, String delimiter) {
293        Integer[] wrapped = new Integer[array.length];
294        for (int i = 0; i < array.length; i++) {
295            wrapped[i] = array[i];
296        }
297        return join(wrapped, delimiter);
298    }
299
300    /**
301     * @deprecated Unused internally. Scheduled for removal in next major release.
302     */
303    @Deprecated(forRemoval = true, since = "6.11.1")
304    public static boolean isEqualToAny(String a, String... args) {
305        for (String arg : args) {
306            if (StringMan.isEqual(a, arg)) {
307                return true;
308            }
309        }
310        return false;
311    }
312
313    public static boolean isEqualIgnoreCaseToAny(@NonNull String a, String... args) {
314        for (String arg : args) {
315            if (a.equalsIgnoreCase(arg)) {
316                return true;
317            }
318        }
319        return false;
320    }
321
322    public static boolean isEqual(String a, String b) {
323        if ((a == null && b != null) || (a != null && b == null)) {
324            return false;
325        } else if (a == null /* implies that b is null */) {
326            return false;
327        }
328        return a.equals(b);
329    }
330
331    /**
332     * @deprecated Unused internally. Scheduled for removal in next major release.
333     */
334    @Deprecated(forRemoval = true, since = "6.11.1")
335    public static boolean isEqualIgnoreCase(String a, String b) {
336        return a.equals(b) || ((a != null) && (b != null) && (a.length() == b.length()) && a
337                .equalsIgnoreCase(b));
338    }
339
340    public static String repeat(String s, int n) {
341        StringBuilder sb = new StringBuilder();
342        sb.append(String.valueOf(s).repeat(Math.max(0, n)));
343        return sb.toString();
344    }
345
346    /**
347     * @deprecated Unused internally. Scheduled for removal in next major release.
348     */
349    @Deprecated(forRemoval = true, since = "6.11.1")
350    public static boolean contains(String name, char c) {
351        for (char current : name.toCharArray()) {
352            if (c == current) {
353                return true;
354            }
355        }
356        return false;
357    }
358
359    /**
360     * @deprecated Unused internally. Scheduled for removal in next major release.
361     */
362    @Deprecated(forRemoval = true, since = "6.11.1")
363    public <T> Collection<T> match(Collection<T> col, String startsWith) {
364        if (col == null) {
365            return null;
366        }
367        startsWith = startsWith.toLowerCase();
368        Iterator<?> iterator = col.iterator();
369        while (iterator.hasNext()) {
370            Object item = iterator.next();
371            if (item == null || !item.toString().toLowerCase().startsWith(startsWith)) {
372                iterator.remove();
373            }
374        }
375        return col;
376    }
377
378    /**
379     * @param message an input string
380     * @return a list of strings
381     * @since 6.4.0
382     *
383     *         <table border="1">
384     *         <caption>Converts multiple quoted and single strings into a list of strings</caption>
385     *         <thead>
386     *           <tr>
387     *             <th>Input</th>
388     *             <th>Output</th>
389     *           </tr>
390     *         </thead>
391     *         <tbody>
392     *           <tr>
393     *             <td>title "sub title"</td>
394     *             <td>["title", "sub title"]</td>
395     *           </tr>
396     *           <tr>
397     *             <td>"a title" subtitle</td>
398     *             <td>["a title", "subtitle"]</td>
399     *           </tr>
400     *           <tr>
401     *             <td>"title" "subtitle"</td>
402     *             <td>["title", "subtitle"]</td>
403     *           </tr>
404     *           <tr>
405     *             <td>"PlotSquared is going well" the authors "and many contributors"</td>
406     *             <td>["PlotSquared is going well", "the", "authors", "and many contributors"]</td>
407     *           </tr>
408     *         </tbody>
409     *         </table>
410     */
411    public static @NonNull List<String> splitMessage(@NonNull String message) {
412        var matcher = StringMan.STRING_SPLIT_PATTERN.matcher(message);
413        List<String> splitMessages = new ArrayList<>();
414        while (matcher.find()) {
415            splitMessages.add(matcher.group(matcher.groupCount() - 1).replaceAll("\"", ""));
416        }
417        return splitMessages;
418    }
419
420}