fr.cenotelie.commons.utils.config.Configuration Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2016 Association Cénotélie (cenotelie.fr)
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this program.
* If not, see .
******************************************************************************/
package fr.cenotelie.commons.utils.config;
import fr.cenotelie.commons.utils.IOUtils;
import fr.cenotelie.commons.utils.TextUtils;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
/**
* Represents a configuration with values associated to properties
* This structure is NOT thread safe
*
* @author Laurent Wouters
*/
public class Configuration {
/**
* The global section of this configuration
*/
private final Section global;
/**
* The named sections
*/
private final Map sections;
/**
* Initializes an empty configuration
*/
public Configuration() {
global = new Section(null);
sections = new HashMap<>();
}
/**
* Gets all the named sections in this configuration
*
* @return The named sections
*/
public Collection getSections() {
return new ArrayList<>(sections.values());
}
/**
* Gets the global section in this configuration
*
* @return The global section in this configuration
*/
public Section getGlobalSection() {
return global;
}
/**
* Gets the section with the specified name
*
* @param name A section's name
* @return The section with the specified name
*/
public Section getSection(String name) {
if (name == null)
return global;
if (!sections.containsKey(name))
sections.put(name, new Section(name));
return sections.get(name);
}
/**
* Gets a list of the values for the specified property in the global section
*
* @param property A property in the global section
* @return A list of the values associated to the specified property
*/
public List getAll(String property) {
return global.getAll(property);
}
/**
* Gets the first value for the specified property in the global section
*
* @param property A property in the global section
* @return The first value associated to the specified property
*/
public String get(String property) {
return global.get(property);
}
/**
* Gets the list of values for the specified section and property
*
* @param section A section in this configuration
* @param property A property in the section
* @return A list of the values associated to the specified property
*/
public List getAll(String section, String property) {
if (section == null)
return global.getAll(property);
Section current = sections.get(section);
if (current == null)
return Collections.emptyList();
return current.getAll(property);
}
/**
* Gets the list of values for the specified section and property
*
* @param section A section in this configuration
* @param property A property in the section
* @return A list of the values associated to the specified property
*/
public String get(String section, String property) {
if (section == null)
return global.get(property);
Section current = sections.get(section);
if (current == null)
return null;
return current.get(property);
}
/**
* Gets whether the specified property and associated value are present in a section
*
* @param section A section
* @param property A property
* @param value An associated value
* @return Whether the property has the associated value
*/
public boolean hasValue(String section, String property, String value) {
if (section == null)
return global.hasValue(property, value);
Section current = sections.get(section);
return current != null && current.hasValue(property, value);
}
/**
* Adds the specified property-value pair in the specified section
*
* @param property A property in the section
* @param value The value to associate
*/
public void add(String property, String value) {
global.add(property, value);
}
/**
* Adds the specified property-value pair in the specified section
*
* @param section A section in this configuration
* @param property A property in the section
* @param value The value to associate
*/
public void add(String section, String property, String value) {
getSection(section).add(property, value);
}
/**
* Removes the specified property - value pair from global section
*
* @param property A property
* @param value The associated value to remove
*/
public void remove(String property, String value) {
global.remove(property, value);
}
/**
* Removes the specified property - value pair from the specified section
*
* @param section Name of the section in the config file
* @param property A property
* @param value The associated value to remove
*/
public void remove(String section, String property, String value) {
getSection(section).remove(property, value);
}
/**
* Sets the property, removing all previous values, if any
*
* @param property A property
* @param value The new value
*/
public void set(String property, String value) {
global.set(property, value);
}
/**
* Sets the property, removing all previous values, if any
*
* @param section A section in this configuration
* @param property A property
* @param value The new value
*/
public void set(String section, String property, String value) {
getSection(section).set(property, value);
}
/**
* Clears any value for the property
*
* @param property The property to clear
*/
public void clear(String property) {
global.clear(property);
}
/**
* Clears any value for the property
*
* @param section A section in this configuration
* @param property The property to clear
*/
public void clear(String section, String property) {
getSection(section).clear(property);
}
/**
* Exports this configuration to the specified file
*
* @param file The file to export to
* @throws IOException When writing fails
*/
public void save(String file) throws IOException {
try (Writer writer = IOUtils.getWriter(file)) {
save(writer);
}
}
/**
* Exports this configuration to the specified file
*
* @param file The file to export to
* @throws IOException When writing fails
*/
public void save(File file) throws IOException {
try (Writer writer = IOUtils.getWriter(file)) {
save(writer);
}
}
/**
* Exports this configuration to the specified writer
*
* @param writer The writer to use
* @throws IOException When writing fails
*/
private void save(Writer writer) throws IOException {
global.save(writer);
boolean before = !global.isEmpty();
for (Section section : sections.values()) {
if (before)
writer.write(IOUtils.LINE_SEPARATOR);
section.save(writer);
before = (before || !section.isEmpty());
}
writer.flush();
}
/**
* Imports the configuration from the specified file
*
* @param file The file to import from
* @throws IOException When reading fails
*/
public void load(String file) throws IOException {
try (Reader reader = IOUtils.getReader(file)) {
load(reader);
}
}
/**
* Imports the configuration from the specified file
*
* @param file The file to import from
* @throws IOException When reading fails
*/
public void load(File file) throws IOException {
try (Reader reader = IOUtils.getReader(file)) {
load(reader);
}
}
/**
* Imports the configuration from the specified stream
*
* @param stream The stream to read from
* @param charset The charset to use
* @throws IOException when reading fails
*/
public void load(InputStream stream, Charset charset) throws IOException {
load(new InputStreamReader(stream, charset));
}
/**
* Imports the configuration from the specified reader
*
* @param reader The reader
* @throws IOException When reading fails
*/
public void load(Reader reader) throws IOException {
char[] buffer = new char[BUFFER_LENGTH];
int bufferNext = 0;
int state = STATE_INIT;
String currentSection = null;
String currentProperty = null;
int c = reader.read();
while (c > 0) {
switch (state) {
case STATE_INIT: {
if (isLineEnding(c)) {
state = STATE_INIT;
} else if (isWhitespace(c)) {
state = STATE_INIT;
} else if (isCommentStart(c)) {
state = STATE_COMMENT;
} else if (c == '[') {
bufferNext = 0;
state = STATE_SECTION_TITLE_WITHIN;
} else {
buffer[0] = (char) c;
bufferNext = 1;
state = STATE_PROPERTY_NAME;
}
break;
}
case STATE_COMMENT: {
if (isLineEnding(c))
state = STATE_INIT;
break;
}
case STATE_SECTION_TITLE_WITHIN: {
if (c == ']') {
currentSection = new String(buffer, 0, bufferNext);
currentSection = TextUtils.unescape(currentSection.trim());
state = STATE_SECTION_TITLE_AFTER;
} else {
if (bufferNext == buffer.length)
buffer = Arrays.copyOf(buffer, buffer.length + BUFFER_LENGTH);
buffer[bufferNext] = (char) c;
bufferNext++;
state = STATE_SECTION_TITLE_WITHIN;
}
break;
}
case STATE_SECTION_TITLE_AFTER: {
if (isLineEnding(c)) {
state = STATE_INIT;
} else if (isWhitespace(c)) {
state = STATE_SECTION_TITLE_AFTER;
} else if (isCommentStart(c)) {
state = STATE_COMMENT;
}
// any content after the section title on the same line is dropped
break;
}
case STATE_PROPERTY_NAME: {
if (isLineEnding(c)) {
// drop this content
state = STATE_INIT;
} else if (c == '=') {
currentProperty = new String(buffer, 0, bufferNext);
currentProperty = TextUtils.unescape(currentProperty.trim());
bufferNext = 0;
state = STATE_PROPERTY_VALUE;
} else {
if (bufferNext == buffer.length)
buffer = Arrays.copyOf(buffer, buffer.length + BUFFER_LENGTH);
buffer[bufferNext] = (char) c;
bufferNext++;
state = STATE_PROPERTY_NAME;
}
break;
}
case STATE_PROPERTY_VALUE: {
if (isLineEnding(c)) {
String value = new String(buffer, 0, bufferNext);
value = TextUtils.unescape(value.trim());
add(currentSection, currentProperty, value);
state = STATE_INIT;
} else {
if (bufferNext == buffer.length)
buffer = Arrays.copyOf(buffer, buffer.length + BUFFER_LENGTH);
buffer[bufferNext] = (char) c;
bufferNext++;
state = STATE_PROPERTY_VALUE;
}
break;
}
}
c = reader.read();
}
if (state == STATE_PROPERTY_VALUE) {
// was reading a value when the stream ended
String value = new String(buffer, 0, bufferNext);
value = TextUtils.unescape(value.trim());
add(currentSection, currentProperty, value);
}
}
/**
* Gets whether the specified character starts a comment in a configuration file
*
* @param c The character
* @return Whether the specified character starts a comment in a configuration file
*/
private static boolean isCommentStart(int c) {
return c == '#' || c == ';';
}
/**
* Gets whether the specified character is a line-ending character
*
* @param c The character
* @return Whether the specified character is a line-ending character
*/
private static boolean isLineEnding(int c) {
return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029;
}
/**
* Gets whether the character is a spacing character
*
* @param c The character
* @return Whether the character is a spacing character
*/
private static boolean isWhitespace(int c) {
return c == 0x0020 || c == 0x0009 || c == 0x000B || c == 0x000C;
}
/**
* The initial length of the buffer for loading a configuration
*/
private static final int BUFFER_LENGTH = 1024;
/**
* The initial state of the loader state machine
*/
private static final int STATE_INIT = 0x00;
/**
* The state when within a comment
*/
private static final int STATE_COMMENT = 0x01;
/**
* The state when within the title of a section
*/
private static final int STATE_SECTION_TITLE_WITHIN = 0x10;
/**
* The state when after the title of a section (still on the same line)
*/
private static final int STATE_SECTION_TITLE_AFTER = 0x11;
/**
* The state when reading a property name
*/
private static final int STATE_PROPERTY_NAME = 0x20;
/**
* The state when reading a property value
*/
private static final int STATE_PROPERTY_VALUE = 0x21;
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy