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

com.electronwill.nightconfig.hocon.HoconWriter Maven / Gradle / Ivy

package com.electronwill.nightconfig.hocon;

import com.electronwill.nightconfig.core.UnmodifiableCommentedConfig;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import com.electronwill.nightconfig.core.io.*;
import com.electronwill.nightconfig.core.utils.FakeUnmodifiableCommentedConfig;
import com.electronwill.nightconfig.core.utils.StringUtils;

import java.io.Writer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;

import static com.electronwill.nightconfig.core.NullObject.NULL_OBJECT;

/**
 * A configurable HOCON writer.
 *
 * @author TheElectronWill
 */
public final class HoconWriter implements ConfigWriter {
	// --- Constant char arrays ---
	private static final char[] NULL_CHARS = {'n', 'u', 'l', 'l'};
	private static final char[] TRUE_CHARS = {'t', 'r', 'u', 'e'};
	private static final char[] FALSE_CHARS = {'f', 'a', 'l', 's', 'e'};
	private static final char[] EMPTY_OBJECT = {'{', '}'}, EMPTY_ARRAY = {'[', ']'};

	private static final char[] TO_ESCAPE = {'"', '\n', '\r', '\t', '\\'};
	private static final char[] ESCAPED = {'"', 'n', 'r', 't', '\\'};

	private static final char[] VALUE_SEPARATOR = {',', ' '};
	private static final char[] FORBIDDEN_IN_UNQUOTED = {'$', '"', '{', '}', '[', ']', ':', '=',
														 ',', '+', '#', '`', '^', '?', '!', '@',
														 '*', '&', '\\'};

	// --- Writer's settings ---
	private Predicate indentObjectElementsPredicate = c -> true;
	private Predicate> indentArrayElementsPredicate = c -> true;
	private boolean newlineAfterObjectStart;
	private char[] newline = NewlineStyle.system().chars;
	private char[] indent = IndentStyle.TABS.chars;
	private char[] kvSeparator = KeyValueSeparatorStyle.COLON.chars;
	private char[] commentPrefix = CommentStyle.HASH.chars;
	private int currentIndentLevel;

	// --- Writer's methods ---
	@Override
	public void write(UnmodifiableConfig config, Writer writer) {
		currentIndentLevel = -1;
		UnmodifiableCommentedConfig commentedConfig;
		if (config instanceof UnmodifiableCommentedConfig) {
			commentedConfig = (UnmodifiableCommentedConfig)config;
		} else {
			commentedConfig = new FakeUnmodifiableCommentedConfig(config);
		}
		writeObject(commentedConfig, new WriterOutput(writer), true);
	}

	private void writeObject(UnmodifiableCommentedConfig config, CharacterOutput output, boolean root) {
		if (config.isEmpty()) {
			output.write(EMPTY_OBJECT);
			return;
		}
		if (!root) {
			output.write('{');// HOCON allows to omit the root braces
		}
		if (newlineAfterObjectStart) {
			output.write(newline);
		}
		final Iterator it = config.entrySet().iterator();
		final boolean indentElements = indentObjectElementsPredicate.test(config);
		if (indentElements) {
			output.write(newline);
			increaseIndentLevel();
		}
		while (true) {
			final UnmodifiableCommentedConfig.Entry entry = it.next();
			final String key = entry.getKey();
			final Object value = entry.getValue();
			final List comments = StringUtils.splitLines(entry.getComment());
			for(String comment : comments) {
				output.write(commentPrefix);
				output.write(comment);
				output.write(newline);
			}
			if (indentElements) {
				writeIndent(output);// Indents the line
			}
			writeString(key, output);// key
			if (value instanceof UnmodifiableConfig) {
				output.write(' ');
			} else {
				output.write(kvSeparator);
				// HOCON allows to omit the separator if the value is a config
			}
			writeValue(value, output);// value
			if (indentElements) {
				output.write(newline);
			} else {
				output.write(',');
			}
			if (!it.hasNext()) break;
		}
		if (indentElements) {
			decreaseIndentLevel();
			writeIndent(output);
		}
		if (!root) {
			output.write('}');// HOCON allows to omit the root braces
		}
	}

	private void writeValue(Object v, CharacterOutput output) {
		if (v == null || v == NULL_OBJECT) {
			output.write(NULL_CHARS);
		} else if (v instanceof String) {
			writeString((String)v, output);
		} else if (v instanceof Number) {
			output.write(v.toString());
		} else if (v instanceof UnmodifiableCommentedConfig) {
			writeObject((UnmodifiableCommentedConfig)v, output, false);
		} else if (v instanceof UnmodifiableConfig) {
			writeObject(new FakeUnmodifiableCommentedConfig((UnmodifiableConfig)v), output, false);
		} else if (v instanceof Collection) {
			writeArray((Collection)v, output);
		} else if (v instanceof Boolean) { writeBoolean((boolean)v, output); } else {
			throw new WritingException("Unsupported value type: " + v.getClass());
		}
	}

	private void writeArray(Collection collection, CharacterOutput output) {
		if (collection.isEmpty()) {
			output.write(EMPTY_ARRAY);
			return;
		}
		output.write('[');// Opens the array
		if (newlineAfterObjectStart) {
			output.write(newline);
		}
		final Iterator it = collection.iterator();
		final boolean indentElements = indentArrayElementsPredicate.test(collection);
		if (indentElements) {
			output.write(newline);
			increaseIndentLevel();
		}
		while (true) {
			Object value = it.next();
			if (indentElements) {
				writeIndent(output);
			}
			writeValue(value, output);
			if (it.hasNext()) {
				output.write(VALUE_SEPARATOR);
				if (indentElements) {
					output.write(newline);
				}
			} else {
				if (indentElements) {
					output.write(newline);
				}
				break;// Nothing else to write
			}
		}
		if (indentElements) {
			decreaseIndentLevel();
			writeIndent(output);
		}
		output.write(']');// Closes the array
	}

	private void writeBoolean(boolean b, CharacterOutput output) {
		if (b) {
			output.write(TRUE_CHARS);
		} else {
			output.write(FALSE_CHARS);
		}
	}

	private void writeString(String s, CharacterOutput output) {
		if (canBeUnquoted(s)) {
			output.write(s);
			return;
		}
		output.write('"');
		final int length = s.length();
		for (int i = 0; i < length; i++) {
			char c = s.charAt(i);
			int escapeIndex = Utils.arrayIndexOf(TO_ESCAPE, c);
			if (escapeIndex == -1) {
				output.write(c);
			} else {// the character must be escaped
				char escaped = ESCAPED[escapeIndex];
				output.write('\\');
				output.write(escaped);
			}
		}
		output.write('"');
	}

	private boolean canBeUnquoted(CharSequence s) {
		final int length = s.length();
		for (int i = 0; i < length; i++) {
			if (Utils.arrayContains(FORBIDDEN_IN_UNQUOTED, s.charAt(i))) {
				return false;
			}
		}
		return true;
	}

	private void increaseIndentLevel() {
		currentIndentLevel++;
	}

	private void decreaseIndentLevel() {
		currentIndentLevel--;
	}

	private void writeIndent(CharacterOutput output) {
		for (int i = 0; i < currentIndentLevel; i++) {
			output.write(indent);
		}
	}

	// --- Settings ---
	public HoconWriter setIndentObjectElementsPredicate(
		Predicate indentObjectElementsPredicate) {
		this.indentObjectElementsPredicate = indentObjectElementsPredicate;
		return this;
	}

	public HoconWriter setIndentArrayElementsPredicate(
		Predicate> indentArrayElementsPredicate) {
		this.indentArrayElementsPredicate = indentArrayElementsPredicate;
		return this;
	}

	public HoconWriter setNewlineAfterObjectStart(boolean newlineAfterObjectStart) {
		this.newlineAfterObjectStart = newlineAfterObjectStart;
		return this;
	}

	public HoconWriter setIndent(IndentStyle indentStyle) {
		this.indent = indentStyle.chars;
		return this;
	}

	public HoconWriter setIndent(String indentString) {
		this.indent = indentString.toCharArray();
		return this;
	}

	public HoconWriter setNewline(NewlineStyle newlineStyle) {
		this.newline = newlineStyle.chars;
		return this;
	}

	public HoconWriter setNewline(String newlineString) {
		this.newline = newlineString.toCharArray();
		return this;
	}

	public HoconWriter setKeyValueSeparator(KeyValueSeparatorStyle separatorStyle) {
		this.kvSeparator = separatorStyle.chars;
		return this;
	}

	public HoconWriter setKeyValueSeparator(String separatorString) {
		this.kvSeparator = separatorString.toCharArray();
		return this;
	}

	public HoconWriter setCommentPrefix(CommentStyle commentPrefixStyle) {
		this.commentPrefix = commentPrefixStyle.chars;
		return this;
	}

	public HoconWriter setCommentPrefix(String commentPrefixString) {
		this.commentPrefix = commentPrefixString.toCharArray();
		return this;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy