All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.config.EffectiveConfig Maven / Gradle / Ivy

Go to download

An intelligent way of booting complex applications and services according to their dependencies

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright (C) 2015 SoftIndex LLC.
 *
 * 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 io.datakernel.config;

import io.datakernel.annotation.Nullable;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;

import static io.datakernel.config.Config.concatPath;
import static io.datakernel.util.Preconditions.checkArgument;
import static java.lang.Math.min;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.*;

public final class EffectiveConfig implements Config {
	private static final class CallsRegistry {
		final Map calls = new HashMap<>();
		final Map defaultCalls = new HashMap<>();
		final Map all;

		CallsRegistry(Map all) {
			this.all = all;
		}

		void registerCall(String path, String value) {
			calls.put(path, value);
		}

		void registerDefaultCall(String path, String value, String defaultValue) {
			calls.put(path, value);
			defaultCalls.put(path, defaultValue);
		}
	}

	private final Config config;
	private final Map children;
	private final String path;
	private final CallsRegistry callsRegistry;

	// creators
	private EffectiveConfig(String path, Config config, CallsRegistry callsRegistry) {
		this.config = config;
		this.path = path;
		this.callsRegistry = callsRegistry;
		this.children = new LinkedHashMap<>();
		for (Map.Entry entry : config.getChildren().entrySet()) {
			this.children.put(entry.getKey(),
					new EffectiveConfig(concatPath(this.path, entry.getKey()), entry.getValue(), this.callsRegistry));
		}
	}

	public static EffectiveConfig wrap(Config config) {
		Map allProperties = new LinkedHashMap<>(); // same order as inserted in config
		fetchAllConfigs(config, "", allProperties);
		CallsRegistry callsRegistry = new CallsRegistry(allProperties);
		return new EffectiveConfig("", config, callsRegistry);
	}

	private static void fetchAllConfigs(Config config, String prefix, Map container) {
		if (config.isEmpty()) {
			return;
		}
		if (config.hasValue()) {
			container.put(prefix, config.getValue());
			return;
		}
		for (String childName : config.getChildren().keySet()) {
			fetchAllConfigs(config.getChild(childName), concatPath(prefix, childName), container);
		}
	}

	@Override
	public String getValue(@Nullable String defaultValue) {
		String value = config.getValue(defaultValue);
		synchronized (this) {
			callsRegistry.registerCall(path, value);
			if (defaultValue != null) {
				callsRegistry.registerDefaultCall(path, value, defaultValue);
			}
		}
		return value;
	}

	@Override
	public String getValue() {
		String value = config.getValue();
		synchronized (this) {
			callsRegistry.registerCall(path, value);
		}
		return value;
	}

	@Override
	public Map getChildren() {
		return children;
	}

	@Override
	public Config provideNoKeyChild(String key) {
		checkArgument(!getChildren().containsKey(key));
		return new EffectiveConfig(concatPath(path, key), config.provideNoKeyChild(key), callsRegistry);
	}

	@Override
	public int hashCode() {
		return config.hashCode();
	}

	@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
	@Override
	public boolean equals(Object obj) {
		return config.equals(obj);
	}

	@Override
	public String toString() {
		return config.toString();
	}

	// rendering
	public void saveEffectiveConfigTo(Path outputPath) {
		try {
			String renderedConfig = this.renderEffectiveConfig();
			Files.write(outputPath, renderedConfig.getBytes(UTF_8), new StandardOpenOption[]{CREATE, WRITE, TRUNCATE_EXISTING});
		} catch (IOException e) {
			throw new RuntimeException("Failed to serialize effective config as properties file", e);
		}
	}

	synchronized public String renderEffectiveConfig() {
		CallsRegistry register = this.callsRegistry;
		StringBuilder sb = new StringBuilder();

		TreeSet keys = new TreeSet<>();
		keys.addAll(register.calls.keySet());   // to get default
		keys.addAll(register.all.keySet());     // to get unused
		String lastKey = "";
		for (String key : keys) {
			if (!lastKey.isEmpty() && commonDots(lastKey, key) == 0) {
				sb.append("\n");
			}
			lastKey = key;

			String used = register.calls.get(key);
			String value = register.all.get(key);
			String defaultValue = register.defaultCalls.get(key);

			if (!register.calls.containsKey(key)) {
				sb.append("## ");
				writeProperty(sb, key, value);
			} else {
				if (Objects.equals(used, defaultValue)) {
					sb.append("# ");
				}
				writeProperty(sb, key, Objects.toString(used, ""));
			}
		}

		return sb.toString();
	}

	private static int commonDots(String key1, String key2) {
		int commonDots = 0;
		for (int i = 0; i < min(key1.length(), key2.length()); i++) {
			if (key2.charAt(i) != key1.charAt(i)) break;
			if (key2.charAt(i) == '.') commonDots++;
		}
		return commonDots;
	}

	private static void writeProperty(StringBuilder sb, String key, String value) {
		sb.append(encodeForPropertiesFile(key, true));
		sb.append(" = ");
		sb.append(encodeForPropertiesFile(value, false));
		sb.append("\n");
	}

	private static String encodeForPropertiesFile(String string, boolean escapeKey) {
		StringBuilder sb = new StringBuilder(string.length() * 2);

		for (int i = 0; i < string.length(); i++) {
			char c = string.charAt(i);
			if ((c > 61) && (c < 127)) {
				if (c == '\\') {
					sb.append('\\');
					sb.append('\\');
					continue;
				}
				sb.append(c);
				continue;
			}
			switch (c) {
				case ' ':
					if (i == 0 || escapeKey)
						sb.append('\\');
					sb.append(' ');
					break;
				case '\t':
					sb.append('\\');
					sb.append('t');
					break;
				case '\n':
					sb.append('\\');
					sb.append('n');
					break;
				case '\r':
					sb.append('\\');
					sb.append('r');
					break;
				case '\f':
					sb.append('\\');
					sb.append('f');
					break;
				case '=':
				case ':':
					if (escapeKey) sb.append('\\');
					sb.append(c);
					break;
				case '#':
				case '!':
					if (escapeKey && i == 0) sb.append('\\');
					sb.append(c);
					break;
				default:
					sb.append(c);
			}
		}
		return sb.toString();
	}

	public Map getEffectiveDefaults() {
		return callsRegistry.defaultCalls;
	}

	public Map getEffectiveCalls() {
		return callsRegistry.calls;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy