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}