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