001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package net.tridentsdk.config;
019
020import com.google.common.collect.Lists;
021import com.google.common.collect.Sets;
022import com.google.gson.JsonArray;
023import com.google.gson.JsonElement;
024import com.google.gson.JsonObject;
025import net.tridentsdk.util.TridentLogger;
026
027import javax.annotation.concurrent.GuardedBy;
028import javax.annotation.concurrent.ThreadSafe;
029import java.math.BigDecimal;
030import java.math.BigInteger;
031import java.util.Collection;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.stream.Collectors;
036
037/**
038 * Represents a section of the Config file
039 *
040 * @author The TridentSDK Team
041 * @since 0.3-alpha-DP
042 */
043@ThreadSafe
044public class ConfigSection {
045    final Object handleLock = new Object();
046    private final Object parentLock = new Object();
047    @GuardedBy("parentLock")
048    ConfigSection parent;
049    @GuardedBy("handleLock")
050    JsonObject jsonHandle;
051
052    /**
053     * Instantiated by subclasses only
054     */
055    protected ConfigSection() {
056    }
057
058    /**
059     * Creates a config section for the parent section
060     *
061     * @param parent the section to be sub-sectioned under
062     * @param obj    the section handler, used to store values
063     */
064    protected ConfigSection(ConfigSection parent, JsonObject obj) {
065        this.parent = parent;
066        this.jsonHandle = obj;
067    }
068
069    /**
070     * Gets a config section with the elements defined in the specified collection
071     *
072     * @param list the collection of objects to serialize to a config section
073     * @param <V>  the type in the collection
074     * @return the config section with all the values in the list
075     */
076    public static <V> ConfigSection addToList(Collection<V> list) {
077        if (!(list instanceof ConfigSectionList)) {
078            TridentLogger.get().error(
079                    new UnsupportedOperationException("Can only add new ConfigSection-s to ConfigSectionList"));
080            return null;
081        }
082        ConfigSection section = new ConfigSection(((ConfigSectionList) list).parent(), new JsonObject());
083        list.add((V) section);
084
085        return section;
086    }
087
088    /**
089     * Gets an integer from the config with the given tag, or defaults to the fallback if the tag is not found
090     *
091     * @param tag the tag to find the value from
092     * @param def the default value if no tag is found
093     * @return the integer at the tag
094     */
095    public int getInt(String tag, int def) {
096        synchronized (handleLock) {
097            return this.contains(tag) ? this.jsonHandle.get(tag).getAsInt() : def;
098        }
099    }
100
101    /**
102     * Gets an integer from the config with the given tag
103     *
104     * <p>Gives {@code 0} if the value could not be found</p>
105     *
106     * @param tag the tag to find the value from
107     * @return the integer at the tag
108     */
109    public int getInt(String tag) {
110        synchronized (handleLock) {
111            return this.getInt(tag, 0);
112        }
113    }
114
115    /**
116     * Sets the value at the tag to a specified integer
117     *
118     * @param tag the tag to set the value
119     * @param in  the integer value to set the tag
120     */
121    public void setInt(String tag, int in) {
122        synchronized (handleLock) {
123            this.jsonHandle.addProperty(tag, in);
124        }
125    }
126
127    /**
128     * Gets a double from the config with the given tag, or defaults to the fallback if the tag is not found
129     *
130     * @param tag the tag to find the value from
131     * @param def the default value if no tag is found
132     * @return the double at the tag
133     */
134    public double getDouble(String tag, double def) {
135        synchronized (handleLock) {
136            return this.contains(tag) ? this.jsonHandle.get(tag).getAsDouble() : def;
137        }
138    }
139
140    /**
141     * Gets a double from the config with the given tag
142     *
143     * <p>Gives {@code 0.0D} if the value could not be found</p>
144     *
145     * @param tag the tag to find the value from
146     * @return the double at the tag
147     */
148    public double getDouble(String tag) {
149        synchronized (handleLock) {
150            return this.getDouble(tag, 0.0D);
151        }
152    }
153
154    /**
155     * Sets a double at the tag to a specified double
156     *
157     * @param tag the tag to set the value
158     * @param d   the double to set to the tag
159     */
160    public void setDouble(String tag, double d) {
161        synchronized (handleLock) {
162            this.jsonHandle.addProperty(tag, d);
163        }
164    }
165
166    /**
167     * Gets a float from the config with the given tag, or defaults to the fallback if the tag is not found
168     *
169     * @param tag the tag to find the value from
170     * @param def the default value if no tag is found
171     * @return the float at the tag
172     */
173    public float getFloat(String tag, float def) {
174        synchronized (handleLock) {
175            return this.contains(tag) ? this.jsonHandle.get(tag).getAsFloat() : def;
176        }
177    }
178
179    /**
180     * Gets an float from the config with the given tag
181     *
182     * <p>Gives {@code 0.0F if the value could not be found}</p>
183     *
184     * @param tag the tag to find the value from
185     * @return the float at the tag
186     */
187    public float getFloat(String tag) {
188        synchronized (handleLock) {
189            return this.getFloat(tag, 0.0F);
190        }
191    }
192
193    /**
194     * Sets the float at the tag to a specified float
195     *
196     * @param tag the tag to set the value
197     * @param f   the float to set the tag to
198     */
199    public void setFloat(String tag, float f) {
200        synchronized (handleLock) {
201            this.jsonHandle.addProperty(tag, f);
202        }
203    }
204
205    /**
206     * Gets a character from the config with the given tag, or defaults to the fallback if the tag is not found
207     *
208     * @param tag the tag to find the value from
209     * @param def the default value if no tag is found
210     * @return the character at the tag
211     */
212    public char getChar(String tag, char def) {
213        synchronized (handleLock) {
214            return this.contains(tag) ? this.jsonHandle.get(tag).getAsCharacter() : def;
215        }
216    }
217
218    /**
219     * Gets a character from the config with the given tag
220     *
221     * <p>Gives {@code \u0000} if the value could not be found</p>
222     *
223     * @param tag the tag to find the value from
224     * @return the character at the tag
225     */
226    public char getChar(String tag) {
227        synchronized (handleLock) {
228            return this.getChar(tag, '\u0000');
229        }
230    }
231
232    /**
233     * Sets a character at the tag to the specified character
234     *
235     * @param tag the tag to set the value
236     * @param c   the character to set the tag to
237     */
238    public void setChar(String tag, char c) {
239        synchronized (handleLock) {
240            this.jsonHandle.addProperty(tag, c);
241        }
242    }
243
244    /**
245     * Gets a boolean from the config with the given tag, defaulting to the fallback if the tag is not found
246     *
247     * @param tag the tag to find the value from
248     * @param def the default value if the tag is not found
249     * @return the boolean at the tag
250     */
251    public boolean getBoolean(String tag, boolean def) {
252        synchronized (handleLock) {
253            return this.contains(tag) ? this.jsonHandle.get(tag).getAsBoolean() : def;
254        }
255    }
256
257    /**
258     * Gets a boolean from the config with the given tag
259     *
260     * <p>Gives {@code false} if the value could not be found</p>
261     *
262     * @param tag the tag to find the value from
263     * @return the boolean at the tag
264     */
265    public boolean getBoolean(String tag) {
266        synchronized (handleLock) {
267            return this.getBoolean(tag, false);
268        }
269    }
270
271    /**
272     * Sets the boolean the specified tag
273     *
274     * @param tag the tag to set the value
275     * @param b   the boolean to set to at the tag
276     */
277    public void setBoolean(String tag, boolean b) {
278        synchronized (handleLock) {
279            this.jsonHandle.addProperty(tag, b);
280        }
281    }
282
283    /**
284     * Gets a byte from the config with the given tag, defaulting to the fallback if the tag is not found
285     *
286     * @param tag the tag to find the value from
287     * @param def the default if the tag is not found
288     * @return the byte at the tag
289     */
290    public byte getByte(String tag, byte def) {
291        synchronized (handleLock) {
292            return this.contains(tag) ? this.jsonHandle.get(tag).getAsByte() : def;
293        }
294    }
295
296    /**
297     * Gets a character from the config with the given tag
298     *
299     * <p>Gives {@code (byte) 0} if the value could not be found</p>
300     *
301     * @param tag the tag to find the value from
302     * @return the character at the tag
303     */
304    public byte getByte(String tag) {
305        synchronized (handleLock) {
306            return this.getByte(tag, (byte) 0);
307        }
308    }
309
310    /**
311     * Sets the byte the specified tag
312     *
313     * @param tag the tag to set the value
314     * @param b   the byte to set to at the tag
315     */
316    public void setByte(String tag, byte b) {
317        synchronized (handleLock) {
318            this.jsonHandle.addProperty(tag, b);
319        }
320    }
321
322    /**
323     * Gets the string at the specified tag, defaulting to the specified default if not found
324     *
325     * @param tag the tag to set the value
326     * @param def the default value to
327     */
328    public String getString(String tag, String def) {
329        synchronized (handleLock) {
330            return this.contains(tag) ? this.jsonHandle.get(tag).getAsString() : def;
331        }
332    }
333
334    public String getString(String tag) {
335        synchronized (handleLock) {
336            return this.getString(tag, null);
337        }
338    }
339
340    /**
341     * Sets the string the specified tag
342     *
343     * @param tag the tag to set the value
344     * @param s   the string to set at the tag
345     */
346    public void setString(String tag, String s) {
347        synchronized (handleLock) {
348            this.jsonHandle.addProperty(tag, s);
349        }
350    }
351
352    /**
353     * Gets the list at the tag
354     *
355     * @param tag  the tag to find the value from
356     * @param type the types contained in the list
357     * @param <V>  the list type
358     * @return the list from the section
359     */
360    public <V> List<V> getList(String tag, Class<V> type) {
361        JsonArray array;
362        synchronized (handleLock) {
363            array = this.jsonHandle.get(tag).getAsJsonArray();
364        }
365
366        //Handle ConfigSection seperately as it is special
367        if (type.equals(ConfigSection.class)) {
368            List<V> result = new ConfigSectionList<>(this, array);
369            for (JsonElement element : array) {
370                if (element == null)
371                    continue;
372                result.add((V) new ConfigSection(this, element.getAsJsonObject()));
373            }
374            return result;
375        } else {
376            List<V> result = new ConfigList<>(array);
377            for (JsonElement element : array) {
378                if (element == null)
379                    continue;
380                result.add(GsonFactory.gson().fromJson(element, type));
381            }
382            return result;
383        }
384    }
385
386    /**
387     * Adds an empty list into the tag
388     *
389     * @param tag  the tag to set the value
390     * @param type the types in the list
391     * @param <V>  the list type
392     * @return the list added to the section
393     */
394    public <V> List<V> addList(String tag, Class<V> type) {
395        synchronized (handleLock) {
396            this.jsonHandle.add(tag, new JsonArray());
397        }
398
399        return this.getList(tag, type);
400    }
401
402    /**
403     * Gets a BigInteger from the section, defaulting the the fallback if not found
404     *
405     * @param tag the tag to find the value from
406     * @param def the default value
407     * @return the BigInteger value at the tag
408     */
409    public BigInteger getBigInteger(String tag, BigInteger def) {
410        synchronized (handleLock) {
411            return this.contains(tag) ? this.jsonHandle.get(tag).getAsBigInteger() : def;
412        }
413    }
414
415    /**
416     * Gets the BigInteger at the tag
417     *
418     * <p>Defaults to {@code null} if not found</p>
419     *
420     * @param tag the tag to find the value from
421     * @return the BigInteger at the tag
422     */
423    public BigInteger getBigInteger(String tag) {
424        synchronized (handleLock) {
425            return this.getBigInteger(tag, null);
426        }
427    }
428
429    /**
430     * Sets the {@link java.math.BigInteger} the specified tag
431     *
432     * @param s  the tag to set the integer to
433     * @param bi the BigInteger ot set the tag to
434     */
435    public void setBigInteger(String s, BigInteger bi) {
436        synchronized (handleLock) {
437            this.setString(s, bi.toString());
438        }
439    }
440
441    /**
442     * Gets a BigDecimal at the specified tag, defaulting to the fallback if not found
443     *
444     * @param tag the tag to find the value from
445     * @param def the default value
446     * @return the value at the tag
447     */
448    public BigDecimal getBigDecimal(String tag, BigDecimal def) {
449        synchronized (handleLock) {
450            return this.contains(tag) ? this.jsonHandle.get(tag).getAsBigDecimal() : def;
451        }
452    }
453
454    /**
455     * Gets a BigDecimal at the specified tag
456     *
457     * <p>Defaults to {@code null} if the tag is not found</p>
458     *
459     * @param tag the tag to find the value from
460     * @return the value of the tag
461     */
462    public BigDecimal getBigDecimal(String tag) {
463        synchronized (handleLock) {
464            return this.getBigDecimal(tag, null);
465        }
466    }
467
468    /**
469     * Sets the {@link java.math.BigDecimal} the specified tag
470     *
471     * @param tag the tag to set the value
472     * @param bd  the BigDecimal to set the tag to
473     */
474    public void setBigDecimal(String tag, BigDecimal bd) {
475        synchronized (handleLock) {
476            this.setString(tag, bd.toPlainString());
477        }
478    }
479
480    /**
481     * Gets the Object at the specified tag
482     *
483     * @param tag   the tag to find the value from
484     * @param clazz the type of the object
485     * @param <V>   the object type
486     * @return the value
487     */
488    public <V> V getObject(String tag, Class<V> clazz) {
489        synchronized (handleLock) {
490            return this.contains(tag) ? GsonFactory.gson().fromJson(this.jsonHandle.get(tag), clazz) : null;
491        }
492    }
493
494    /**
495     * Sets the object at the specified tag
496     *
497     * @param tag    the tag to set the value
498     * @param object the value to set the tag to
499     */
500    public void setObject(String tag, Object object) {
501        synchronized (handleLock) {
502            this.jsonHandle.add(tag, GsonFactory.gson().toJsonTree(object));
503        }
504    }
505
506    /**
507     * Removes the tag from the configuration
508     *
509     * @param tag the tag to remove
510     */
511    public void remove(String tag) {
512        synchronized (handleLock) {
513            this.jsonHandle.remove(tag);
514        }
515    }
516
517    /**
518     * Checks to see if the section contains the tag
519     *
520     * @param tag the tag to see if contained
521     * @return {@code true} if the tag is in the section, {@code false} if not
522     */
523    public boolean contains(String tag) {
524        synchronized (handleLock) {
525            return this.jsonHandle.has(tag);
526        }
527    }
528
529    /**
530     * As JSON form
531     *
532     * @return the JSON version of the section
533     */
534    public JsonObject asJsonObject() {
535        synchronized (handleLock) {
536            return this.jsonHandle;
537        }
538    }
539
540    /**
541     * The JSON root from the parent
542     *
543     * @return the parent root section
544     */
545    public Config rootSection() {
546        synchronized (parentLock) {
547            return this.parent.rootSection();
548        }
549    }
550
551    /**
552     * The section parent
553     *
554     * @return the parent of the section
555     */
556    public ConfigSection parentSection() {
557        synchronized (parentLock) {
558            return this.parent;
559        }
560    }
561
562    /**
563     * Gets a sub section which has the current section as a parent
564     *
565     * <p>A new section is created if it does not exist</p>
566     *
567     * @param tag the tag to get the section from
568     * @return the section with the given tag under this section
569     */
570    public ConfigSection getConfigSection(String tag) {
571        if (this.contains(tag)) {
572            return new ConfigSection(this, this.jsonHandle.get(tag).getAsJsonObject());
573        } else {
574            this.jsonHandle.add(tag, new JsonObject());
575            return new ConfigSection(this, this.jsonHandle.get(tag).getAsJsonObject());
576        }
577    }
578
579    /**
580     * Returns all of the topmost keys. Will not have inner section keys.
581     *
582     * @return the config keys
583     */
584    public Set<String> keys() {
585        Set<Map.Entry<String, JsonElement>> entries = entries();
586
587        Set<String> keys = Sets.newHashSet();
588        keys.addAll(entries.stream().map(Map.Entry::getKey).collect(Collectors.toList()));
589
590        return keys;
591    }
592
593    /**
594     * Returns the topmost values
595     *
596     * @return the values
597     */
598    public Collection<JsonElement> values() {
599        Set<Map.Entry<String, JsonElement>> entries = entries();
600
601        List<JsonElement> values = Lists.newArrayList();
602        values.addAll(entries.stream().map(Map.Entry::getValue).collect(Collectors.toList()));
603
604        return values;
605    }
606
607    /**
608     * Obtains the set of the topmost key-value entries
609     *
610     * @return the key-value entry set
611     */
612    public Set<Map.Entry<String, JsonElement>> entries() {
613        synchronized (handleLock) {
614            return jsonHandle.entrySet();
615        }
616    }
617
618    /**
619     * Saves the parent data
620     */
621    public void save() {
622        synchronized (parentLock) {
623            this.parent.save();
624        }
625    }
626}