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.examples;
018
019import ninja.leaping.configurate.ConfigurationNode;
020import org.checkerframework.checker.nullness.qual.Nullable;
021import ninja.leaping.configurate.hocon.HoconConfigurationLoader;
022import ninja.leaping.configurate.transformation.ConfigurationTransformation;
023
024import java.io.IOException;
025import java.nio.file.Paths;
026
027
028/**
029 * An example of how to use transformations to migrate a configuration to a newer schema version.
030 * <p>
031 * It's like DFU but not hot garbage! (and probably less PhD-worthy)
032 */
033public class Transformations {
034    private static final int VERSION_LATEST = 1; // easy way to track the latest version, update as more revisions are added
035    private static final String VERSION_KEY = "version";
036
037    private static Object[] path(Object... path) {
038        return path;
039    }
040
041    /**
042     * Create a new builder for versioned configurations. This builder uses a field in the node (by default {@code
043     * schema-version}) to determine the current schema version (using -1 for no version present).
044     *
045     * @return versioned transformation
046     */
047    public static ConfigurationTransformation create() {
048        return ConfigurationTransformation.versionedBuilder()
049                .setVersionKey(VERSION_KEY) // this is the default, but to show it can be customized
050                .addVersion(VERSION_LATEST, zeroToOne()) // syntax: target version, latest version
051                .addVersion(0, initialTransform())
052                .build();
053    }
054
055    /**
056     * A transformation. This one has multiple actions, and demonstrates how wildcards work
057     *
058     * @return created transformation
059     */
060    public static ConfigurationTransformation initialTransform() {
061        return ConfigurationTransformation.builder()
062                // Move the node at `serverVersion` to the location <code>{"server", "version"}</code>
063                .addAction(path("serverVersion"), (path, value) -> {
064                    return new Object[]{"server", "version"};
065                })
066                // For every direct child of the `section` node, set the value of its child `new-value` to something
067                .addAction(path("section", ConfigurationTransformation.WILDCARD_OBJECT), (path, value) -> {
068                    value.getNode("new-value").setValue("i'm a default");
069
070                    return null; // don't move the value
071                })
072                .build();
073    }
074
075    public static ConfigurationTransformation zeroToOne() {
076        return ConfigurationTransformation.builder()
077                // oh, turns out we want to use a different format for this, so we'll change it again
078                .addAction(path("server", "version"), (path, value) -> {
079                    @Nullable String val = value.getString();
080                    if (val != null) {
081                        value.setValue(val.replaceAll("-", "_"));
082                    }
083                    return null;
084                })
085                .build();
086    }
087
088    /**
089     * Apply the transformations to a node
090     * <p>
091     * This method also prints information about the version update that occurred
092     *
093     * @param node The node to transform
094     * @param <N>  node type
095     * @return provided node, after transformation
096     */
097    public static <N extends ConfigurationNode> N updateNode(N node) {
098        if (!node.isVirtual()) { // we only want to migrate existing data
099            ConfigurationTransformation trans = create();
100            int startVersion = node.getNode(VERSION_KEY).getInt(-1);
101            trans.apply(node);
102            int endVersion = node.getNode(VERSION_KEY).getInt();
103            if (startVersion != endVersion) { // we might not have made any changes
104                System.out.println("Updated config schema from " + startVersion + " to " + endVersion);
105            }
106        }
107        return node;
108    }
109
110    public static void main(String[] args) throws IOException {
111        if (args.length != 1) {
112            System.err.println("Not enough arguments, usage: transformations <file>");
113            System.err.println("Apply the test transformations to a single file");
114        }
115        HoconConfigurationLoader loader = HoconConfigurationLoader.builder()
116                .setPath(Paths.get(args[0]))
117                .build();
118
119        loader.save(updateNode(loader.load())); // tada
120    }
121
122}