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}