/* * Copyright 2013 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.config; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import org.lwjgl.opengl.PixelFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.engine.SimpleUri; import org.terasology.engine.TerasologyConstants; import org.terasology.engine.paths.PathManager; import org.terasology.entitySystem.Component; import org.terasology.input.Input; import org.terasology.naming.Name; import org.terasology.naming.Version; import org.terasology.naming.gson.NameTypeAdapter; import org.terasology.naming.gson.VersionTypeAdapter; import org.terasology.utilities.gson.CaseInsensitiveEnumTypeAdapterFactory; import org.terasology.utilities.gson.InputHandler; import org.terasology.utilities.gson.SetMultimapTypeAdapter; import org.terasology.utilities.gson.UriTypeAdapterFactory; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.Set; /** * Terasology user config. Holds the various global configuration information that the user can modify. It can be saved * and loaded in a JSON format. */ public final class Config { public static final String PROPERTY_OVERRIDE_DEFAULT_CONFIG = "org.terasology.config.default.override"; private static final Logger logger = LoggerFactory.getLogger(Config.class); private RootConfig config; public PermissionConfig getPermission() { return config.getPermission(); } /** * @return Input configuration (mostly binds) */ public InputConfig getInput() { return config.getInput(); } public ModuleConfig getDefaultModSelection() { return config.getDefaultModSelection(); } public NetworkConfig getNetwork() { return config.getNetwork(); } public PlayerConfig getPlayer() { return config.getPlayer(); } public AudioConfig getAudio() { return config.getAudio(); } public SystemConfig getSystem() { return config.getSystem(); } public RenderingConfig getRendering() { return config.getRendering(); } public WorldGenerationConfig getWorldGeneration() { return config.getWorldGeneration(); } public SecurityConfig getSecurity() { return config.getSecurity(); } public NUIEditorConfig getNuiEditor() { return config.getNuiEditor(); } public String renderConfigAsJson(Object configObject) { return createGson().toJsonTree(configObject).toString(); } /** * Saves this config to the default configuration file */ public void save() { try (BufferedWriter writer = Files.newBufferedWriter(getConfigFile(), TerasologyConstants.CHARSET)) { createGson().toJson(config, writer); } catch (IOException e) { logger.error("Failed to save config", e); } } public void loadDefaults() { JsonObject jsonConfig = loadDefaultToJson(); config = createGson().fromJson(jsonConfig, RootConfig.class); } public void load() { JsonObject jsonConfig = loadDefaultToJson(); Optional<JsonObject> defaultsConfig = loadFileToJson(getOverrideDefaultConfigFile()); if (defaultsConfig.isPresent()) { merge(jsonConfig, defaultsConfig.get()); } Optional<JsonObject> userConfig = loadFileToJson(getConfigFile()); if (userConfig.isPresent()) { merge(jsonConfig, userConfig.get()); } config = createGson().fromJson(jsonConfig, RootConfig.class); } private Path getOverrideDefaultConfigFile() { return Paths.get(System.getProperty(PROPERTY_OVERRIDE_DEFAULT_CONFIG, "")); } public JsonObject loadDefaultToJson() { try (Reader baseReader = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/default.cfg")))) { return new JsonParser().parse(baseReader).getAsJsonObject(); } catch (IOException e) { throw new RuntimeException("Missing default configuration file"); } } public Optional<JsonObject> loadFileToJson(Path configPath) { if (Files.isRegularFile(configPath)) { try (Reader reader = Files.newBufferedReader(configPath, TerasologyConstants.CHARSET)) { JsonElement userConfig = new JsonParser().parse(reader); if (userConfig.isJsonObject()) { return Optional.of(userConfig.getAsJsonObject()); } } catch (IOException e) { logger.error("Failed to load config file {}, falling back on default config"); } } return Optional.empty(); } /** * @return The default configuration file location */ private Path getConfigFile() { return PathManager.getInstance().getHomePath().resolve("config.cfg"); } protected static Gson createGson() { return new GsonBuilder() .registerTypeAdapter(Name.class, new NameTypeAdapter()) .registerTypeAdapter(Version.class, new VersionTypeAdapter()) .registerTypeAdapter(BindsConfig.class, new BindsConfig.Handler()) .registerTypeAdapter(SetMultimap.class, new SetMultimapTypeAdapter<>(Input.class)) .registerTypeAdapter(SecurityConfig.class, new SecurityConfig.Handler()) .registerTypeAdapter(Input.class, new InputHandler()) .registerTypeAdapter(PixelFormat.class, new PixelFormatHandler()) .registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory()) .registerTypeAdapterFactory(new UriTypeAdapterFactory()) .setPrettyPrinting().create(); } private static Gson createGsonForModules() { return new GsonBuilder() .registerTypeAdapterFactory(new CaseInsensitiveEnumTypeAdapterFactory()) .registerTypeAdapterFactory(new UriTypeAdapterFactory()) .setPrettyPrinting().create(); } private static void merge(JsonObject target, JsonObject from) { for (Map.Entry<String, JsonElement> entry : from.entrySet()) { if (entry.getValue().isJsonObject()) { if (target.has(entry.getKey()) && target.get(entry.getKey()).isJsonObject()) { merge(target.get(entry.getKey()).getAsJsonObject(), entry.getValue().getAsJsonObject()); } else { target.remove(entry.getKey()); target.add(entry.getKey(), entry.getValue()); } } else { target.remove(entry.getKey()); target.add(entry.getKey(), entry.getValue()); } } } /** * @param uri the uri to look uo * @return a set that contains all keys for that uri, never <code>null</code> */ public Set<String> getModuleConfigKeys(SimpleUri uri) { Map<String, JsonElement> map = config.getModuleConfigs().get(uri); if (map == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(map.keySet()); } /** * @param uri the uri to look up * @param key the look-up key * @param clazz the class to convert the data to * @return a config component for the given uri and class or <code>null</code> */ public <T extends Component> T getModuleConfig(SimpleUri uri, String key, Class<T> clazz) { Map<String, JsonElement> map = config.getModuleConfigs().get(uri); if (map == null) { return null; } JsonElement element = map.get(key); Gson gson = createGsonForModules(); return gson.fromJson(element, clazz); } /** * @param generatorUri the generator Uri * @param configs the new config params for the world generator */ public void setModuleConfigs(SimpleUri generatorUri, Map<String, Component> configs) { Gson gson = createGsonForModules(); Map<String, JsonElement> map = Maps.newHashMap(); for (Map.Entry<String, Component> entry : configs.entrySet()) { JsonElement json = gson.toJsonTree(entry.getValue()); map.put(entry.getKey(), json); } config.getModuleConfigs().put(generatorUri, map); } private static class PixelFormatHandler implements JsonSerializer<PixelFormat>, JsonDeserializer<PixelFormat> { @Override public PixelFormat deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { if (json.isJsonPrimitive() && json.getAsJsonPrimitive().isNumber()) { return new PixelFormat().withDepthBits(json.getAsInt()); } return new PixelFormat().withDepthBits(24); } @Override public JsonElement serialize(PixelFormat src, Type typeOfSrc, JsonSerializationContext context) { return new JsonPrimitive(src.getDepthBits()); } } }