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

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