Newer
Older
sklauncher / launcher / src / main / java / com / skcraft / launcher / util / EnvironmentParser.java
package com.skcraft.launcher.util;

import com.google.common.io.CharSource;
import com.google.common.io.Files;
import lombok.Data;
import lombok.RequiredArgsConstructor;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * Parses dotenv-style files.
 */
@RequiredArgsConstructor
public class EnvironmentParser {
	private final BufferedReader reader;

	private char read() throws IOException {
		int c = reader.read();

		if (c == -1) {
			throw new EOFException("End of stream reached unexpectedly!");
		}

		return (char) c;
	}

	public Map<String, String> parse() throws IOException {
		HashMap<String, String> result = new HashMap<>();

		while (reader.ready()) {
			KeyValue entry = parseLine();

			result.put(entry.getKey(), entry.getValue());
		}

		return result;
	}

	public KeyValue parseLine() throws IOException {
		String key = parseKey();
		String value = parseValue();

		try {
			reader.mark(1);
			char newline = read();
			if (newline == '\r') {
				reader.mark(1);
				if (read() != '\n') {
					throw new IOException("Expected CRLF but only got CR");
				}
				reader.reset();
			} else if (newline != '\n') {
				reader.reset();
			}
		} catch (EOFException ignored) {
		}

		return new KeyValue(key, value);
	}

	private String parseKey() throws IOException {
		StringBuilder buffer = new StringBuilder();

		// Very lenient key parsing.
		while (true) {
			char c = read();

			switch (c) {
				case '=':
				case '\r':
				case '\n':
					return buffer.toString();
				default:
					buffer.append(c);
			}
		}
	}

	private String parseValue() throws IOException {
		StringBuilder buffer = new StringBuilder();

		try {
			while (true) {
				char c = read();

				switch (c) {
					case '\r':
					case '\n':
						return buffer.toString();
					case '"':
						buffer.append(parseQuotedPhrase());
						break;
					case '\\':
						char next = read();
						buffer.append(next);
						break;
					default:
						buffer.append(c);
				}
			}
		} catch (EOFException e) {
			// No terminating newline. bad!
			return buffer.toString();
		}
	}

	private String parseQuotedPhrase() throws IOException {
		StringBuilder buffer = new StringBuilder();

		while (true) {
			char c = read();

			switch (c) {
				case '"':
					return buffer.toString();
				case '\\':
					char next = read();
					buffer.append(next);
					break;
				default:
					buffer.append(c);
			}
		}
	}

	public static Map<String, String> parse(File target) throws IOException {
		CharSource charSource = Files.asCharSource(target, StandardCharsets.UTF_8);

		EnvironmentParser parser = new EnvironmentParser(charSource.openBufferedStream());
		return parser.parse();
	}

	@Data
	private static class KeyValue {
		private final String key;
		private final String value;
	}
}