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 ninja.leaping.configurate.reference;
018
019import com.google.common.reflect.TypeToken;
020import ninja.leaping.configurate.ConfigurationNode;
021import ninja.leaping.configurate.loader.ConfigurationLoader;
022import ninja.leaping.configurate.objectmapping.ObjectMappingException;
023import ninja.leaping.configurate.reactive.Publisher;
024import ninja.leaping.configurate.reactive.TransactionalSubscriber;
025import org.checkerframework.checker.nullness.qual.Nullable;
026
027import java.io.IOException;
028import java.nio.file.Path;
029import java.util.Map;
030import java.util.concurrent.ForkJoinPool;
031import java.util.function.Function;
032
033/**
034 * An updating reference to a base configuration node
035 *
036 * @param <N> The type of node to work with
037 */
038public interface ConfigurationReference<N extends ConfigurationNode> extends AutoCloseable {
039    /**
040     * Create a new configuration reference that will only update when loaded
041     *
042     * @param loader The loader to load and save from
043     * @param <N>    The type of node
044     * @return The newly created reference, with an initial load performed
045     * @throws IOException If the configuration contained fails to load
046     */
047    static <N extends ConfigurationNode> ConfigurationReference<N> createFixed(ConfigurationLoader<? extends N> loader) throws IOException {
048        ConfigurationReference<N> ret = new ManualConfigurationReference<>(loader, ForkJoinPool.commonPool());
049        ret.load();
050        return ret;
051    }
052
053    /**
054     * Create a new configuration reference that will automatically update when triggered by the provided {@link
055     * WatchServiceListener}
056     *
057     * @param loaderCreator A function that can create a {@link ConfigurationLoader}
058     * @param file          The file to load this configuration from
059     * @param listener      The watch service listener that will receive events
060     * @param <T>           The node type
061     * @return The created reference
062     * @throws IOException If the underlying loader fails to load a configuration
063     * @see WatchServiceListener#listenToConfiguration(Function, Path)
064     */
065    static <T extends ConfigurationNode> ConfigurationReference<T> createWatching(Function<Path, ConfigurationLoader<? extends T>> loaderCreator, Path file, WatchServiceListener listener) throws IOException {
066        final WatchingConfigurationReference<T> ret = new WatchingConfigurationReference<>(loaderCreator.apply(file), listener.taskExecutor);
067        ret.load();
068        ret.setDisposable(listener.listenToFile(file, ret));
069
070        return ret;
071    }
072
073    /**
074     * Reload a configuration using the provided loader.
075     * <p>
076     * If the load fails, this reference will continue pointing to old configuration values
077     *
078     * @throws IOException When an error occurs
079     */
080    void load() throws IOException;
081
082    /**
083     * Save this configuration using the provided loader.
084     *
085     * @throws IOException When an error occurs in the underlying ID
086     */
087    void save() throws IOException;
088
089    /**
090     * Update the configuration node pointed to by this reference, and save it using the reference's loader
091     * <p>
092     * Even if the loader fails to save this new node, the node pointed to by this reference will be updated.
093     *
094     * @param newNode The new node to save
095     * @throws IOException When an error occurs within the loader
096     */
097    void save(N newNode) throws IOException;
098
099    /**
100     * Save this configuration using the provided loader. Any errors will be submitted to subscribers of the returned
101     * publisher.
102     *
103     * @return publisher providing an event when the save is complete
104     */
105    Publisher<N> saveAsync();
106
107    /**
108     * Update this configuration using the provided function, returning a {@link Publisher} which will complete with the
109     * result of the operation. The update function will be called asynchronously, and will be saved to this
110     * reference's loader when complete.
111     *
112     * @param updater update function
113     * @return publisher providing an event when the update is complete
114     */
115    Publisher<N> updateAsync(Function<N, ? extends N> updater);
116
117    /**
118     * Get the base node this reference refers to.
119     *
120     * @return The node
121     */
122    N getNode();
123
124    /**
125     * Get the loader this reference uses to load and save its node
126     *
127     * @return The loader
128     */
129    ConfigurationLoader<? extends N> getLoader();
130
131    /**
132     * Get the node at the given path, using the root node
133     *
134     * @param path The path, a series of path elements
135     * @return A child node
136     * @see ConfigurationNode#getNode(Object...)
137     */
138    N get(Object... path);
139
140    /**
141     * Update the value of the node at the given path, using the root node as a base.
142     *
143     * @param path  The path to get the child at
144     * @param value The value to set the child node to
145     */
146    default void set(Object[] path, @Nullable Object value) {
147        getNode().getNode(path).setValue(value);
148    }
149
150    /**
151     * Set the value of the node at {@code path} to the given value, using the appropriate {@link
152     * ninja.leaping.configurate.objectmapping.serialize.TypeSerializer} to serialize the data if it's not directly
153     * supported by the provided configuration.
154     *
155     * @param path  The path to set the value at
156     * @param type  The type of data to serialize
157     * @param value The value to set
158     * @param <T>   The type parameter for the value
159     * @throws ObjectMappingException If thrown by the serialization mechanism
160     */
161    default <T> void set(Object[] path, TypeToken<T> type, @Nullable T value) throws ObjectMappingException {
162        getNode().getNode(path).setValue(type, value);
163    }
164
165    /**
166     * Create a reference to the node at the provided path. The value will be deserialized according to the provided
167     * TypeToken.
168     * <p>
169     * The returned reference will update with reloads of and changes to the value of the provided configuration. Any
170     * serialization errors encountered will be submitted to the {@link #errors()} stream
171     *
172     * @param type The value's type
173     * @param path The path from the root node to the node a value will be gotten from
174     * @param <T>  The value type
175     * @return A deserializing reference to the node at the given path
176     * @throws ObjectMappingException if a type serializer could not be found for the provided type
177     */
178    default <T> ValueReference<T> referenceTo(TypeToken<T> type, Object... path) throws ObjectMappingException {
179        return referenceTo(type, path, null);
180    }
181
182    /**
183     * Create a reference to the node at the provided path. The value will be deserialized according to type of the
184     * provided Class.
185     * <p>
186     * The returned reference will update with reloads of and changes to the value of the provided configuration. Any
187     * serialization errors encountered will be submitted to the {@link #errors()} stream
188     *
189     * @param type The value's type
190     * @param path The path from the root node to the node a value will be gotten from
191     * @param <T>  The value type
192     * @return A deserializing reference to the node at the given path
193     * @throws ObjectMappingException if a type serializer could not be found for the provided type
194     */
195    default <T> ValueReference<T> referenceTo(Class<T> type, Object... path) throws ObjectMappingException {
196        return referenceTo(type, path, null);
197    }
198
199    /**
200     * Create a reference to the node at the provided path. The value will be deserialized according to the provided
201     * TypeToken.
202     * <p>
203     * The returned reference will update with reloads of and changes to the value of the provided configuration. Any
204     * serialization errors encountered will be submitted to the {@link #errors()} stream
205     *
206     * @param type         The value's type.
207     * @param path         The path from the root node to the node a value will be gotten from.
208     * @param defaultValue The value to use when there is no data present in the targeted node.
209     * @param <T>          The value type
210     * @return A deserializing reference to the node at the given path
211     * @throws ObjectMappingException if a type serializer could not be found for the provided type
212     */
213    <T> ValueReference<T> referenceTo(TypeToken<T> type, Object[] path, @Nullable T defaultValue) throws ObjectMappingException;
214
215    /**
216     * Create a reference to the node at the provided path. The value will be deserialized according to type of the
217     * provided Class.
218     * <p>
219     * The returned reference will update with reloads of and changes to the value of the provided configuration. Any
220     * serialization errors encountered will be submitted to the {@link #errors()} stream
221     *
222     * @param type         The value's type
223     * @param path         The path from the root node to the node a value will be gotten from
224     * @param defaultValue The value to use when there is no data present in the targeted node.
225     * @param <T>          The value type
226     * @return A deserializing reference to the node at the given path
227     * @throws ObjectMappingException if a type serializer could not be found for the provided type
228     */
229    <T> ValueReference<T> referenceTo(Class<T> type, Object[] path, @Nullable T defaultValue) throws ObjectMappingException;
230
231    /**
232     * Access the {@link Publisher} that will broadcast update events, providing the newly created node. The returned
233     * publisher will be transaction-aware, i.e. any {@link TransactionalSubscriber} attached will progress through
234     * their phases appropriately
235     *
236     * @return The publisher
237     */
238    Publisher<N> updates();
239
240    /**
241     * A stream that will receive errors that occur while loading or saving to this reference
242     *
243     * @return The publisher
244     */
245    Publisher<Map.Entry<ErrorPhase, Throwable>> errors();
246
247    /**
248     * {@inheritDoc}
249     */
250    @Override
251    void close();
252
253    /**
254     * Representing the phase where an error occurred
255     */
256    enum ErrorPhase {
257        LOADING, SAVING, UNKNOWN, VALUE;
258    }
259}