001/*
002 * Configurate
003 * Copyright (C) zml and Configurate contributors
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 */
017package org.spongepowered.configurate.examples;
018
019import io.leangen.geantyref.TypeToken;
020import org.checkerframework.checker.nullness.qual.Nullable;
021import org.spongepowered.configurate.CommentedConfigurationNode;
022import org.spongepowered.configurate.ConfigurateException;
023import org.spongepowered.configurate.NodePath;
024import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
025import org.spongepowered.configurate.objectmapping.ConfigSerializable;
026import org.spongepowered.configurate.reference.ConfigurationReference;
027import org.spongepowered.configurate.reference.ValueReference;
028import org.spongepowered.configurate.reference.WatchServiceListener;
029
030import java.io.IOException;
031import java.nio.file.Path;
032import java.nio.file.Paths;
033import java.util.List;
034import java.util.Objects;
035import java.util.UUID;
036
037public class ValueReferences implements AutoCloseable {
038
039    private final WatchServiceListener listener;
040    private final ConfigurationReference<CommentedConfigurationNode> base;
041    private final ValueReference<String, CommentedConfigurationNode> name;
042    private final ValueReference<Integer, CommentedConfigurationNode> cookieCount;
043    private final ValueReference<List<TestObject>, CommentedConfigurationNode> complex;
044
045    @ConfigSerializable
046    static class TestObject {
047        String name;
048        UUID id = UUID.randomUUID();
049
050        @Override
051        public boolean equals(final Object o) {
052            if (this == o) {
053                return true;
054            }
055
056            if (!(o instanceof TestObject)) {
057                return false;
058            }
059
060            final TestObject that = (TestObject) o;
061            return this.name.equals(that.name)
062                    && this.id.equals(that.id);
063        }
064
065        @Override
066        public int hashCode() {
067            return Objects.hash(this.name, this.id);
068        }
069
070        @Override
071        public String toString() {
072            return "TestObject{"
073                    + "name='" + this.name + '\''
074                    + ", id=" + this.id
075                    + '}';
076        }
077    }
078
079    public ValueReferences(final Path configFile) throws IOException, ConfigurateException {
080        // Initialize the watch service and node
081        this.listener = WatchServiceListener.create();
082        this.base = this.listener.listenToConfiguration(file -> HoconConfigurationLoader.builder()
083                .defaultOptions(opts -> opts.shouldCopyDefaults(true))
084                .path(file)
085                .build(), configFile);
086
087        // Perform actions on successful and unsuccessful reloads
088        this.base.updates().subscribe($ -> System.out.println("Configuration auto-reloaded"));
089        this.base.errors().subscribe(err -> {
090            final Throwable thr = err.getValue();
091            System.out.println("Unable to " + err.getKey() + " the configuration: " + thr.getMessage());
092            if (thr.getCause() != null) {
093                System.out.println(thr.getCause().getMessage());
094            }
095        });
096
097        // Get node references
098        this.name = this.base.referenceTo(String.class, "name");
099        this.name.subscribe(newName -> System.out.println("Reloaded, name is: " + newName));
100        this.cookieCount = this.base.referenceTo(Integer.class, NodePath.path("cookie-count"), 5);
101        this.complex = this.base.referenceTo(new TypeToken<List<TestObject>>() {}, "complex");
102
103        // And then save now that values have been initialized
104        this.base.save();
105    }
106
107    public void repl() {
108        boolean running = true;
109        if (System.console() == null) {
110            System.err.println("Not at an interactive prompt");
111            this.printData();
112            return;
113        }
114
115        while (running) {
116            final @Nullable String next = System.console().readLine(">");
117            if (next == null) {
118                break;
119            }
120            final String[] cmd = next.split(" ", -1);
121            if (cmd.length == 0) {
122                continue;
123            }
124
125            switch (cmd[0]) {
126                case "stop":
127                    running = false;
128                    break;
129                case "name":
130                    if (cmd.length < 2) {
131                        System.err.println("Not enough arguments, usage: name <new-name>");
132                        break;
133                    }
134                    this.name.setAndSave(cmd[1]);
135                    System.out.println("Name: " + this.name.get());
136                    break;
137                case "dump":
138                    printData();
139                    break;
140                case "help":
141                    System.out.println("Value reference tester\n"
142                            + "Commands:\n\n"
143                            + "stop: Exit the loop\n"
144                            + "name <name>: Update the name in the config file\n"
145                            + "dump: Dump all accessed data in the config file\n"
146                            + "help: Show this message"
147                    );
148                    break;
149                default:
150                    System.err.println("Unknown command '" + next + "'");
151                    System.err.println("help for help");
152            }
153        }
154    }
155
156    public void printData() {
157        System.out.println("Name: " + this.name.get());
158        System.out.println("Cookie count: " + this.cookieCount.get());
159        System.out.println("Complex: ");
160        if (this.complex.get().isEmpty()) {
161            System.out.println("(empty)");
162        } else {
163            for (TestObject obj : this.complex.get()) {
164                System.out.println("- " + obj);
165            }
166        }
167    }
168
169    @Override
170    public void close() throws IOException {
171        this.base.close();
172        this.listener.close();
173    }
174
175    public static void main(final String[] args) {
176        if (args.length != 1) {
177            System.err.println("Usage: ./reference-example <file>");
178            return;
179        }
180        final Path path = Paths.get(args[0]);
181        try (ValueReferences engine = new ValueReferences(path)) {
182            engine.repl();
183        } catch (final IOException e) { // may be a ConfigurateException, or something else
184            System.out.println("Error loading configuration: " + e.getMessage());
185            e.printStackTrace();
186        }
187    }
188
189}