org.wisdom.maven.utils.Properties2HoconConverter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wisdom-maven-plugin Show documentation
Show all versions of wisdom-maven-plugin Show documentation
The Maven Wisdom Plugin allows building applications for Wisdom.
The newest version!
/*
* #%L
* Wisdom-Framework
* %%
* Copyright (C) 2013 - 2014 Wisdom Framework
* %%
* 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.
* #L%
*/
package org.wisdom.maven.utils;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
import java.util.Set;
/**
* A converter taking a "properties" file as input and generating a "hocon" file. Basically, it convert every entry
* into the hocon format. The conversion is a 'best effort' conversion and may introduce incompatibilities. So a
* human review is required.
*
* First it changes entry by entry, and does not compute the object structure supported by hocon. This was made on
* purpose to reduce the learning curve.
*
* Translations rules are the following:
*
* - Keys are mapped to hocon key
* - Single line values are tranformed into quoted String value
* - If a value from the properties file is already quoted, the hocon value does not add quote
* - Multi-line values from the properties file are transformed to single line value (concatenation)
* - Unquoted, single-line values with "," are mapped to list
* - Comments are kept as comments
*
*/
public class Properties2HoconConverter {
private static final Set BOOLEAN_VALUES = ImmutableSet.of("true", "false", "on", "off", "yes", "no");
private Properties2HoconConverter() {
// Avoid direct instantiation.
}
/**
* Converts the given properties file (props) to hocon.
*
* @param props the properties file, must exist and be a valid properties file.
* @param backup whether or not a backup should be generated. It backup is set to true, a ".backup" file is
* generated before the conversion.
* @return the output file. It's the input file, except if the props' name does not end with ".conf", in that
* case it generates a new file with this extension.
* @throws IOException if something bad happens.
*/
public static File convert(File props, boolean backup) throws IOException {
if (!props.isFile()) {
throw new IllegalArgumentException("The given properties file does not exist : " + props.getAbsolutePath());
}
Properties properties;
try {
properties = readFileAsProperties(props);
} catch (IOException e) {
throw new IllegalArgumentException("Cannot convert " + props.getName() + " to hocon - the file is not a " +
"valid properties file", e);
}
// Properties cannot be null there, but might be empty
if (properties == null || properties.isEmpty()) {
throw new IllegalArgumentException("Cannot convert an empty file : " + props.getAbsolutePath());
}
// The conversion can start
if (backup) {
// Generate backup
try {
generateBackup(props);
} catch (IOException e) {
throw new IllegalStateException("Cannot generate the backup for " + props.getName(), e);
}
}
String hocon = convertToHocon(props);
File output = props;
if (!props.getName().endsWith(".conf")) {
// Replace extension
output = new File(props.getParentFile(),
props.getName().substring(0, props.getName().lastIndexOf(".")) + ".conf");
}
FileUtils.write(output, hocon);
return output;
}
/**
* Generates the hocon string resulting from the conversion of the given properties file.
*
* @param props the properties file, must exist and be a valid properties file.
* @return the converted configuration (hocon format).
* @throws IOException
*/
public static String convertToHocon(File props) throws IOException {
StringBuilder output = new StringBuilder();
List lines = FileUtils.readLines(props);
boolean readingValue = false;
for (String line : lines) {
if (isComment(line)) {
// Comment
output.append("#").append(line.trim().substring(1)).append("\n");
continue;
}
if (line.trim().isEmpty()) {
if (!readingValue) {
output.append("\n");
}
continue;
}
if (!readingValue) {
int position = getKeyValueSeparatorPosition(line);
if (position != 0) {
String key = line.substring(0, position).trim();
int vPosition = getValueStartPosition(line, position);
String value;
if (vPosition == -1) {
// No value
value = "";
} else {
value = fixValue(line.substring(vPosition).trim());
}
output.append(fixKey(key)).append("=");
if (value.endsWith("\\")) {
// Multiline
readingValue = true;
output.append("\"");
output.append(value.substring(0, value.length() - 1));
} else {
if (vPosition == -1) {
output.append("\"\"");
} else {
output.append(fixSingleLineValue(line.substring(vPosition)).trim()).append("\n");
}
readingValue = false;
}
} else {
throw new IllegalStateException("No key-value separator found in " + line);
}
} else {
String value = line.trim();
if (value.endsWith("\\")) {
// Multiline
readingValue = true;
output.append(fixValue(value.substring(0, value.length() - 1)));
} else {
output.append(fixValue(value)).append("\"\n");
readingValue = false;
}
}
}
return output.toString();
}
private static boolean isComment(String line) {
final String trim = line.trim();
return trim.startsWith("#")
|| trim.startsWith("!");
}
private static String fixValue(String value) {
if (value.isEmpty()) {
return value;
}
return value
.replace("\\#", "#")
.replace("\\!", "!")
.replace("\\=", "=")
.replace("\\:", ":")
.replace("\b", "b");
}
private static String fixSingleLineValue(String value) {
if (value.isEmpty()) {
return "";
}
if (value.startsWith("\"") && value.endsWith("\"")) {
return value;
}
String escaped = fixValue(value);
if (escaped.contains(",")) {
// The value is considered as a list
return "[" + escaped + "]";
} else if (isBoolean(escaped) || isNumeric(escaped)) {
// The value is a number or a boolean
return escaped;
} else if (escaped.contains("${") && escaped.contains("}")) {
// The value contains substitution
return escaped;
} else {
return "\"" + escaped + "\"";
}
}
private static boolean isNumeric(String escaped) {
try {
//noinspection ResultOfMethodCallIgnored
Double.parseDouble(escaped); //NOSONAR ignoring result.
return true;
} catch (NumberFormatException e) {
return false;
}
}
private static boolean isBoolean(String escaped) {
String lc = escaped.toLowerCase();
return BOOLEAN_VALUES.contains(lc);
}
private static String fixKey(String key) {
if (key.indexOf(' ') != -1 || key.indexOf(':') != -1 || key.indexOf('=') != -1) {
String unescape = key.trim()
.replace("\\=", "=")
.replace("\\:", ":")
.replace("\\ ", " ")
.replace("\b", "b");
return "\"" + unescape + "\"";
} else {
return key.replace("\b", "b").trim();
}
}
private static int getKeyValueSeparatorPosition(String line) {
int position = 0;
int positionOfEqual = getIndexOf(line, '=');
if (positionOfEqual != -1) {
position = positionOfEqual;
}
int positionOfColumn = getIndexOf(line, ':');
if (isBefore(positionOfColumn, positionOfEqual)) {
position = positionOfColumn;
}
int positionOfSpace = getIndexOf(line, ' ');
if (isBefore(positionOfSpace, positionOfEqual) && isBefore(positionOfSpace, positionOfColumn)) {
position = positionOfSpace;
}
if (position == 0) {
position = line.length();
}
return position;
}
private static int getValueStartPosition(String line, int keyIndex) {
int i = keyIndex;
for (; i < line.length(); i++) {
char c = line.charAt(i);
if (!Character.isSpaceChar(c) && c != '=' && c != ':') {
return i;
}
}
return -1;
}
private static boolean isBefore(int index1, int index2) {
return index1 != -1 && (index2 == -1 || index1 < index2);
}
private static int getIndexOf(String line, char c) {
int begin = 0;
int i = 0;
// Skip any space character.
for (; begin == 0 && i < line.length(); i++) {
if (!Character.isSpaceChar(line.charAt(i))) {
begin = i + 1;
}
}
if (i == line.length()) {
return -1;
}
int pos = line.indexOf(c, begin);
while (pos != -1) {
if (pos == 0) {
return 0;
} else if (line.charAt(pos - 1) == '\\') {
// Escaped
pos = line.indexOf(c, pos + 1);
} else {
return pos;
}
}
return -1;
}
private static void generateBackup(File props) throws IOException {
File backup = new File(props.getParentFile(), props.getName() + ".backup");
FileUtils.copyFile(props, backup);
}
private static Properties readFileAsProperties(File props) throws IOException {
Properties properties = new Properties();
InputStream stream = null;
try {
stream = FileUtils.openInputStream(props);
properties.load(stream);
return properties;
} finally {
IOUtils.closeQuietly(stream);
}
}
}