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.configuration; 020 021import java.util.ArrayList; 022import java.util.LinkedHashMap; 023import java.util.LinkedHashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028/** 029 * A type of {@link ConfigurationSection} that is stored in memory. 030 */ 031public class MemorySection implements ConfigurationSection { 032 033 protected final Map<String, Object> map = new LinkedHashMap<>(); 034 private final Configuration root; 035 private final ConfigurationSection parent; 036 private final String path; 037 private final String fullPath; 038 039 /** 040 * Creates an empty MemorySection for use as a root {@link Configuration} 041 * section. 042 * 043 * <p>Note that calling this without being yourself a {@link Configuration} 044 * will throw an exception! 045 * 046 * @throws IllegalStateException Thrown if this is not a {@link 047 * Configuration} root. 048 */ 049 protected MemorySection() { 050 if (!(this instanceof Configuration)) { 051 throw new IllegalStateException( 052 "Cannot construct a root MemorySection when not a Configuration"); 053 } 054 055 this.path = ""; 056 this.fullPath = ""; 057 this.parent = null; 058 this.root = (Configuration) this; 059 } 060 061 /** 062 * Creates an empty MemorySection with the specified parent and path. 063 * 064 * @param parent Parent section that contains this own section. 065 * @param path Path that you may access this section from via the root 066 * {@link Configuration}. 067 * @throws IllegalArgumentException Thrown is parent or path is null, or 068 * if parent contains no root Configuration. 069 */ 070 protected MemorySection(ConfigurationSection parent, String path) { 071 this.path = path; 072 this.parent = parent; 073 this.root = parent.getRoot(); 074 075 if (this.root == null) { 076 throw new NullPointerException("Path may not be orphaned"); 077 } 078 079 this.fullPath = createPath(parent, path); 080 } 081 082 public static double toDouble(Object obj, double def) { 083 if (obj instanceof Number) { 084 return ((Number) obj).doubleValue(); 085 } 086 if (obj instanceof String) { 087 try { 088 return Double.parseDouble((String) obj); 089 } catch (NumberFormatException ignored) { 090 } 091 } else if (obj instanceof List<?> val) { 092 if (!val.isEmpty()) { 093 return toDouble(val.get(0), def); 094 } 095 } 096 return def; 097 } 098 099 public static int toInt(Object obj, int def) { 100 if (obj instanceof Number) { 101 return ((Number) obj).intValue(); 102 } 103 if (obj instanceof String) { 104 try { 105 return Integer.parseInt((String) obj); 106 } catch (NumberFormatException ignored) { 107 } 108 } else if (obj instanceof List<?> val) { 109 if (!val.isEmpty()) { 110 return toInt(val.get(0), def); 111 } 112 } 113 return def; 114 } 115 116 public static long toLong(Object obj, long def) { 117 if (obj instanceof Number) { 118 return ((Number) obj).longValue(); 119 } 120 if (obj instanceof String) { 121 try { 122 return Long.parseLong((String) obj); 123 } catch (NumberFormatException ignored) { 124 } 125 } else if (obj instanceof List<?> val) { 126 if (!val.isEmpty()) { 127 return toLong(val.get(0), def); 128 } 129 } 130 return def; 131 } 132 133 /** 134 * Creates a full path to the given {@link ConfigurationSection} from its 135 * root {@link Configuration}. 136 * 137 * <p>You may use this method for any given {@link ConfigurationSection}, not 138 * only {@link MemorySection}. 139 * 140 * @param section Section to create a path for. 141 * @param key Name of the specified section. 142 * @return Full path of the section from its root. 143 */ 144 public static String createPath(ConfigurationSection section, String key) { 145 return createPath(section, key, section.getRoot()); 146 } 147 148 /** 149 * Creates a relative path to the given {@link ConfigurationSection} from 150 * the given relative section. 151 * 152 * <p>You may use this method for any given {@link ConfigurationSection}, not 153 * only {@link MemorySection}. 154 * 155 * @param section Section to create a path for. 156 * @param key Name of the specified section. 157 * @param relativeTo Section to create the path relative to. 158 * @return Full path of the section from its root. 159 */ 160 public static String createPath( 161 ConfigurationSection section, String key, 162 ConfigurationSection relativeTo 163 ) { 164 Configuration root = section.getRoot(); 165 if (root == null) { 166 throw new IllegalStateException("Cannot create path without a root"); 167 } 168 char separator = root.options().pathSeparator(); 169 170 StringBuilder builder = new StringBuilder(); 171 for (ConfigurationSection parent = section; 172 (parent != null) && (parent != relativeTo); parent = parent.getParent()) { 173 if (builder.length() > 0) { 174 builder.insert(0, separator); 175 } 176 177 builder.insert(0, parent.getName()); 178 } 179 180 if ((key != null) && !key.isEmpty()) { 181 if (builder.length() > 0) { 182 builder.append(separator); 183 } 184 185 builder.append(key); 186 } 187 188 return builder.toString(); 189 } 190 191 @Override 192 public Set<String> getKeys(boolean deep) { 193 Set<String> result = new LinkedHashSet<>(); 194 195 Configuration root = getRoot(); 196 if ((root != null) && root.options().copyDefaults()) { 197 ConfigurationSection defaults = getDefaultSection(); 198 199 if (defaults != null) { 200 result.addAll(defaults.getKeys(deep)); 201 } 202 } 203 204 mapChildrenKeys(result, this, deep); 205 206 return result; 207 } 208 209 @Override 210 public Map<String, Object> getValues(boolean deep) { 211 Map<String, Object> result = new LinkedHashMap<>(); 212 213 Configuration root = getRoot(); 214 if ((root != null) && root.options().copyDefaults()) { 215 ConfigurationSection defaults = getDefaultSection(); 216 217 if (defaults != null) { 218 result.putAll(defaults.getValues(deep)); 219 } 220 } 221 222 mapChildrenValues(result, this, deep); 223 224 return result; 225 } 226 227 @Override 228 public boolean contains(String path) { 229 return get(path) != null; 230 } 231 232 @Override 233 public boolean isSet(String path) { 234 Configuration root = getRoot(); 235 if (root == null) { 236 return false; 237 } 238 if (root.options().copyDefaults()) { 239 return contains(path); 240 } 241 return get(path, null) != null; 242 } 243 244 @Override 245 public String getCurrentPath() { 246 return this.fullPath; 247 } 248 249 @Override 250 public String getName() { 251 return this.path; 252 } 253 254 @Override 255 public Configuration getRoot() { 256 return this.root; 257 } 258 259 @Override 260 public ConfigurationSection getParent() { 261 return this.parent; 262 } 263 264 @Override 265 public void addDefault(String path, Object value) { 266 Configuration root = getRoot(); 267 if (root == null) { 268 throw new IllegalStateException("Cannot add default without root"); 269 } 270 if (root == this) { 271 throw new UnsupportedOperationException( 272 "Unsupported addDefault(String, Object) implementation"); 273 } 274 root.addDefault(createPath(this, path), value); 275 } 276 277 @Override 278 public ConfigurationSection getDefaultSection() { 279 Configuration root = getRoot(); 280 Configuration defaults = root == null ? null : root.getDefaults(); 281 282 if (defaults != null) { 283 if (defaults.isConfigurationSection(getCurrentPath())) { 284 return defaults.getConfigurationSection(getCurrentPath()); 285 } 286 } 287 288 return null; 289 } 290 291 @Override 292 public void set(String path, Object value) { 293 Configuration root = getRoot(); 294 if (root == null) { 295 throw new IllegalStateException("Cannot use section without a root"); 296 } 297 298 char separator = root.options().pathSeparator(); 299 // i1 is the leading (higher) index 300 // i2 is the trailing (lower) index 301 int i1 = -1; 302 int i2; 303 ConfigurationSection section = this; 304 while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { 305 String node = path.substring(i2, i1); 306 ConfigurationSection subSection = section.getConfigurationSection(node); 307 if (subSection == null) { 308 section = section.createSection(node); 309 } else { 310 section = subSection; 311 } 312 } 313 314 String key = path.substring(i2); 315 if (section == this) { 316 if (value == null) { 317 this.map.remove(key); 318 } else { 319 this.map.put(key, value); 320 } 321 } else { 322 section.set(key, value); 323 } 324 } 325 326 @Override 327 public Object get(String path) { 328 return get(path, getDefault(path)); 329 } 330 331 @Override 332 public Object get(String path, Object defaultValue) { 333 if (path == null) { 334 throw new NullPointerException("Path cannot be null"); 335 } 336 337 if (path.isEmpty()) { 338 return this; 339 } 340 341 Configuration root = getRoot(); 342 if (root == null) { 343 throw new IllegalStateException("Cannot access section without a root"); 344 } 345 346 char separator = root.options().pathSeparator(); 347 // i1 is the leading (higher) index 348 // i2 is the trailing (lower) index 349 int i1 = -1; 350 int i2; 351 ConfigurationSection section = this; 352 while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { 353 section = section.getConfigurationSection(path.substring(i2, i1)); 354 if (section == null) { 355 return defaultValue; 356 } 357 } 358 359 String key = path.substring(i2); 360 if (section == this) { 361 Object result = this.map.get(key); 362 if (result == null) { 363 return defaultValue; 364 } else { 365 return result; 366 } 367 } 368 return section.get(key, defaultValue); 369 } 370 371 @Override 372 public ConfigurationSection createSection(String path) { 373 Configuration root = getRoot(); 374 if (root == null) { 375 throw new IllegalStateException("Cannot create section without a root"); 376 } 377 378 char separator = root.options().pathSeparator(); 379 // i1 is the leading (higher) index 380 // i2 is the trailing (lower) index 381 int i1 = -1; 382 int i2; 383 ConfigurationSection section = this; 384 while ((i1 = path.indexOf(separator, i2 = i1 + 1)) != -1) { 385 String node = path.substring(i2, i1); 386 ConfigurationSection subSection = section.getConfigurationSection(node); 387 if (subSection == null) { 388 section = section.createSection(node); 389 } else { 390 section = subSection; 391 } 392 } 393 394 String key = path.substring(i2); 395 if (section == this) { 396 ConfigurationSection result = new MemorySection(this, key); 397 this.map.put(key, result); 398 return result; 399 } 400 return section.createSection(key); 401 } 402 403 @Override 404 public ConfigurationSection createSection(String path, Map<?, ?> map) { 405 ConfigurationSection section = createSection(path); 406 407 for (Map.Entry<?, ?> entry : map.entrySet()) { 408 if (entry.getValue() instanceof Map) { 409 section.createSection(entry.getKey().toString(), (Map<?, ?>) entry.getValue()); 410 } else { 411 section.set(entry.getKey().toString(), entry.getValue()); 412 } 413 } 414 415 return section; 416 } 417 418 // Primitives 419 @Override 420 public String getString(String path) { 421 Object def = getDefault(path); 422 return getString(path, def != null ? def.toString() : null); 423 } 424 425 @Override 426 public String getString(String path, String def) { 427 Object val = get(path, def); 428 if (val != null) { 429 return val.toString(); 430 } else { 431 return def; 432 } 433 } 434 435 @Override 436 public boolean isString(String path) { 437 Object val = get(path); 438 return val instanceof String; 439 } 440 441 @Override 442 public int getInt(String path) { 443 Object def = getDefault(path); 444 return getInt(path, toInt(def, 0)); 445 } 446 447 @Override 448 public int getInt(String path, int def) { 449 Object val = get(path, def); 450 return toInt(val, def); 451 } 452 453 @Override 454 public boolean isInt(String path) { 455 Object val = get(path); 456 return val instanceof Integer; 457 } 458 459 @Override 460 public boolean getBoolean(String path) { 461 Object def = getDefault(path); 462 if (def instanceof Boolean) { 463 return getBoolean(path, (Boolean) def); 464 } else { 465 return getBoolean(path, false); 466 } 467 } 468 469 @Override 470 public boolean getBoolean(String path, boolean defaultValue) { 471 Object val = get(path, defaultValue); 472 if (val instanceof Boolean) { 473 return (Boolean) val; 474 } else { 475 return defaultValue; 476 } 477 } 478 479 @Override 480 public boolean isBoolean(String path) { 481 Object val = get(path); 482 return val instanceof Boolean; 483 } 484 485 @Override 486 public double getDouble(String path) { 487 Object def = getDefault(path); 488 return getDouble(path, toDouble(def, 0)); 489 } 490 491 @Override 492 public double getDouble(String path, double defaultValue) { 493 Object val = get(path, defaultValue); 494 return toDouble(val, defaultValue); 495 } 496 497 @Override 498 public boolean isDouble(String path) { 499 Object val = get(path); 500 return val instanceof Double; 501 } 502 503 @Override 504 public long getLong(String path) { 505 Object def = getDefault(path); 506 return getLong(path, toLong(def, 0)); 507 } 508 509 @Override 510 public long getLong(String path, long def) { 511 Object val = get(path, def); 512 return toLong(val, def); 513 } 514 515 @Override 516 public boolean isLong(String path) { 517 Object val = get(path); 518 return val instanceof Long; 519 } 520 521 // Java 522 @Override 523 public List<?> getList(String path) { 524 Object def = getDefault(path); 525 return getList(path, def instanceof List ? (List<?>) def : null); 526 } 527 528 @Override 529 public List<?> getList(String path, List<?> def) { 530 Object val = get(path, def); 531 return (List<?>) ((val instanceof List) ? val : def); 532 } 533 534 @Override 535 public boolean isList(String path) { 536 Object val = get(path); 537 return val instanceof List; 538 } 539 540 @Override 541 public List<String> getStringList(String path) { 542 List<?> list = getList(path); 543 544 if (list == null) { 545 return new ArrayList<>(0); 546 } 547 548 List<String> result = new ArrayList<>(); 549 550 for (Object object : list) { 551 if ((object instanceof String) || isPrimitiveWrapper(object)) { 552 result.add(String.valueOf(object)); 553 } 554 } 555 556 return result; 557 } 558 559 @Override 560 public List<Integer> getIntegerList(String path) { 561 List<?> list = getList(path); 562 563 List<Integer> result = new ArrayList<>(); 564 565 for (Object object : list) { 566 if (object instanceof Integer) { 567 result.add((Integer) object); 568 } else if (object instanceof String) { 569 try { 570 result.add(Integer.valueOf((String) object)); 571 } catch (NumberFormatException ignored) { 572 } 573 } else if (object instanceof Character) { 574 result.add((int) (Character) object); 575 } else if (object instanceof Number) { 576 result.add(((Number) object).intValue()); 577 } 578 } 579 580 return result; 581 } 582 583 @Override 584 public List<Boolean> getBooleanList(String path) { 585 List<?> list = getList(path); 586 587 List<Boolean> result = new ArrayList<>(); 588 589 for (Object object : list) { 590 if (object instanceof Boolean) { 591 result.add((Boolean) object); 592 } else if (object instanceof String) { 593 if (Boolean.TRUE.toString().equals(object)) { 594 result.add(true); 595 } else if (Boolean.FALSE.toString().equals(object)) { 596 result.add(false); 597 } 598 } 599 } 600 601 return result; 602 } 603 604 @Override 605 public List<Double> getDoubleList(String path) { 606 List<?> list = getList(path); 607 608 List<Double> result = new ArrayList<>(); 609 610 for (Object object : list) { 611 if (object instanceof Double) { 612 result.add((Double) object); 613 } else if (object instanceof String) { 614 try { 615 result.add(Double.valueOf((String) object)); 616 } catch (NumberFormatException ignored) { 617 } 618 } else if (object instanceof Character) { 619 result.add((double) (Character) object); 620 } else if (object instanceof Number) { 621 result.add(((Number) object).doubleValue()); 622 } 623 } 624 625 return result; 626 } 627 628 @Override 629 public List<Float> getFloatList(String path) { 630 List<?> list = getList(path); 631 632 List<Float> result = new ArrayList<>(); 633 634 for (Object object : list) { 635 if (object instanceof Float) { 636 result.add((Float) object); 637 } else if (object instanceof String) { 638 try { 639 result.add(Float.valueOf((String) object)); 640 } catch (NumberFormatException ignored) { 641 } 642 } else if (object instanceof Character) { 643 result.add((float) (Character) object); 644 } else if (object instanceof Number) { 645 result.add(((Number) object).floatValue()); 646 } 647 } 648 649 return result; 650 } 651 652 @Override 653 public List<Long> getLongList(String path) { 654 List<?> list = getList(path); 655 656 List<Long> result = new ArrayList<>(); 657 658 for (Object object : list) { 659 if (object instanceof Long) { 660 result.add((Long) object); 661 } else if (object instanceof String) { 662 try { 663 result.add(Long.valueOf((String) object)); 664 } catch (NumberFormatException ignored) { 665 } 666 } else if (object instanceof Character) { 667 result.add((long) (Character) object); 668 } else if (object instanceof Number) { 669 result.add(((Number) object).longValue()); 670 } 671 } 672 673 return result; 674 } 675 676 @Override 677 public List<Byte> getByteList(String path) { 678 List<?> list = getList(path); 679 680 List<Byte> result = new ArrayList<>(); 681 682 for (Object object : list) { 683 if (object instanceof Byte) { 684 result.add((Byte) object); 685 } else if (object instanceof String) { 686 try { 687 result.add(Byte.valueOf((String) object)); 688 } catch (NumberFormatException ignored) { 689 } 690 } else if (object instanceof Character) { 691 result.add((byte) ((Character) object).charValue()); 692 } else if (object instanceof Number) { 693 result.add(((Number) object).byteValue()); 694 } 695 } 696 697 return result; 698 } 699 700 @Override 701 public List<Character> getCharacterList(String path) { 702 List<?> list = getList(path); 703 704 List<Character> result = new ArrayList<>(); 705 706 for (Object object : list) { 707 if (object instanceof Character) { 708 result.add((Character) object); 709 } else if (object instanceof String str) { 710 711 if (str.length() == 1) { 712 result.add(str.charAt(0)); 713 } 714 } else if (object instanceof Number) { 715 result.add((char) ((Number) object).intValue()); 716 } 717 } 718 719 return result; 720 } 721 722 @Override 723 public List<Short> getShortList(String path) { 724 List<?> list = getList(path); 725 726 List<Short> result = new ArrayList<>(); 727 728 for (Object object : list) { 729 if (object instanceof Short) { 730 result.add((Short) object); 731 } else if (object instanceof String) { 732 try { 733 result.add(Short.valueOf((String) object)); 734 } catch (NumberFormatException ignored) { 735 } 736 } else if (object instanceof Character) { 737 result.add((short) ((Character) object).charValue()); 738 } else if (object instanceof Number) { 739 result.add(((Number) object).shortValue()); 740 } 741 } 742 743 return result; 744 } 745 746 @Override 747 public List<Map<?, ?>> getMapList(String path) { 748 List<?> list = getList(path); 749 List<Map<?, ?>> result = new ArrayList<>(); 750 751 for (Object object : list) { 752 if (object instanceof Map) { 753 result.add((Map<?, ?>) object); 754 } 755 } 756 757 return result; 758 } 759 760 @Override 761 public ConfigurationSection getConfigurationSection(String path) { 762 Object val = get(path, null); 763 if (val != null) { 764 return (val instanceof ConfigurationSection) ? (ConfigurationSection) val : null; 765 } 766 767 val = get(path, getDefault(path)); 768 return (val instanceof ConfigurationSection) ? createSection(path) : null; 769 } 770 771 @Override 772 public boolean isConfigurationSection(String path) { 773 Object val = get(path); 774 return val instanceof ConfigurationSection; 775 } 776 777 protected boolean isPrimitiveWrapper(Object input) { 778 return (input instanceof Integer) || (input instanceof Boolean) 779 || (input instanceof Character) || (input instanceof Byte) || (input instanceof Short) 780 || (input instanceof Double) || (input instanceof Long) || (input instanceof Float); 781 } 782 783 protected Object getDefault(String path) { 784 Configuration root = getRoot(); 785 Configuration defaults = root == null ? null : root.getDefaults(); 786 return (defaults == null) ? null : defaults.get(createPath(this, path)); 787 } 788 789 protected void mapChildrenKeys(Set<String> output, ConfigurationSection section, boolean deep) { 790 if (section instanceof MemorySection sec) { 791 792 for (Map.Entry<String, Object> entry : sec.map.entrySet()) { 793 output.add(createPath(section, entry.getKey(), this)); 794 795 if (deep && (entry.getValue() instanceof ConfigurationSection subsection)) { 796 mapChildrenKeys(output, subsection, deep); 797 } 798 } 799 } else { 800 Set<String> keys = section.getKeys(deep); 801 802 for (String key : keys) { 803 output.add(createPath(section, key, this)); 804 } 805 } 806 } 807 808 protected void mapChildrenValues( 809 Map<String, Object> output, ConfigurationSection section, 810 boolean deep 811 ) { 812 if (section instanceof MemorySection sec) { 813 814 for (Map.Entry<String, Object> entry : sec.map.entrySet()) { 815 output.put(createPath(section, entry.getKey(), this), entry.getValue()); 816 817 if (entry.getValue() instanceof ConfigurationSection) { 818 if (deep) { 819 mapChildrenValues(output, (ConfigurationSection) entry.getValue(), deep); 820 } 821 } 822 } 823 } else { 824 Map<String, Object> values = section.getValues(deep); 825 826 for (Map.Entry<String, Object> entry : values.entrySet()) { 827 output.put(createPath(section, entry.getKey(), this), entry.getValue()); 828 } 829 } 830 } 831 832 @Override 833 public String toString() { 834 Configuration root = getRoot(); 835 if (root == null) { 836 return getClass().getSimpleName() + "[path='" + getCurrentPath() + "', root='" + null 837 + "']"; 838 } else { 839 return getClass().getSimpleName() + "[path='" + getCurrentPath() + "', root='" + root 840 .getClass().getSimpleName() + "']"; 841 } 842 } 843 844}