com.opengamma.strata.collect.io.IniFile.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.strata.collect.io.IniFile.java

Source

/**
 * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
 * 
 * Please see distribution for license.
 */
package com.opengamma.strata.collect.io;

import java.io.UncheckedIOException;
import java.util.LinkedHashMap;
import java.util.Map;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.io.CharSource;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Unchecked;

/**
 * An INI file.
 * <p>
 * Represents an INI file together with the ability to parse it from a {@link CharSource}.
 * <p>
 * The INI file format used here is deliberately simple.
 * There are two elements - key-value pairs and sections.
 * <p>
 * The basic element is a key-value pair.
 * The key is separated from the value using the '=' symbol.
 * Duplicate keys are allowed.
 * For example 'key = value'.
 * The equals sign and value may be omitted, in which case the value is an empty string.
 * <p>
 * All properties are grouped into named sections.
 * The section name occurs on a line by itself surrounded by square brackets.
 * Duplicate section names are not allowed.
 * For example '[section]'.
 * <p>
 * Keys, values and section names are trimmed.
 * Blank lines are ignored.
 * Whole line comments begin with hash '#' or semicolon ';'.
 * No escape format is available.
 * Lookup is case sensitive.
 * <p>
 * This example explains the format:
 * <pre>
 *  # line comment
 *  [foo]
 *  key = value
 * 
 *  [bar]
 *  key = value
 *  month = January
 * </pre>
 * <p>
 * The aim of this class is to parse the basic format.
 * Interpolation of variables is not supported.
 */
public final class IniFile {

    /**
     * The INI sections.
     */
    private final ImmutableMap<String, PropertySet> sectionMap;

    //-------------------------------------------------------------------------
    /**
     * Parses the specified source as an INI file.
     * <p>
     * This parses the specified character source expecting an INI file format.
     * The resulting instance can be queried for each section in the file.
     * 
     * @param source  the INI file resource
     * @return the INI file
     * @throws UncheckedIOException if an IO exception occurs
     * @throws IllegalArgumentException if the file cannot be parsed
     */
    public static IniFile of(CharSource source) {
        ArgChecker.notNull(source, "source");
        ImmutableList<String> lines = Unchecked.wrap(() -> source.readLines());
        Map<String, Multimap<String, String>> parsedIni = parse(lines);
        ImmutableMap.Builder<String, PropertySet> builder = ImmutableMap.builder();
        parsedIni.forEach((sectionName, sectionData) -> builder.put(sectionName, PropertySet.of(sectionData)));
        return new IniFile(builder.build());
    }

    //-------------------------------------------------------------------------
    // parses the INI file format
    private static Map<String, Multimap<String, String>> parse(ImmutableList<String> lines) {
        Map<String, Multimap<String, String>> ini = new LinkedHashMap<>();
        Multimap<String, String> currentSection = null;
        int lineNum = 0;
        for (String line : lines) {
            lineNum++;
            line = line.trim();
            if (line.length() == 0 || line.startsWith("#") || line.startsWith(";")) {
                continue;
            }
            if (line.startsWith("[") && line.endsWith("]")) {
                String sectionName = line.substring(1, line.length() - 1).trim();
                if (ini.containsKey(sectionName)) {
                    throw new IllegalArgumentException(
                            "Invalid INI file, duplicate section not allowed, line " + lineNum);
                }
                currentSection = ArrayListMultimap.create();
                ini.put(sectionName, currentSection);

            } else if (currentSection == null) {
                throw new IllegalArgumentException(
                        "Invalid INI file, properties must be within a [section], line " + lineNum);

            } else {
                int equalsPosition = line.indexOf('=');
                String key = (equalsPosition < 0 ? line.trim() : line.substring(0, equalsPosition).trim());
                String value = (equalsPosition < 0 ? "" : line.substring(equalsPosition + 1).trim());
                if (key.length() == 0) {
                    throw new IllegalArgumentException("Invalid INI file, empty key, line " + lineNum);
                }
                currentSection.put(key, value);
            }
        }
        return ini;
    }

    //-------------------------------------------------------------------------
    /**
     * Obtains an instance, specifying the map of section to properties.
     * 
     * @param sectionMap  the map of sections
     * @return the INI file
     */
    public static IniFile of(Map<String, PropertySet> sectionMap) {
        return new IniFile(ImmutableMap.copyOf(sectionMap));
    }

    //-------------------------------------------------------------------------
    /**
     * Restricted constructor.
     * 
     * @param sectionMap  the sections
     */
    private IniFile(ImmutableMap<String, PropertySet> sectionMap) {
        this.sectionMap = sectionMap;
    }

    //-------------------------------------------------------------------------
    /**
     * Returns the set of sections of this INI file.
     * 
     * @return the set of sections
     */
    public ImmutableSet<String> sections() {
        return sectionMap.keySet();
    }

    /**
     * Returns the INI file as a map.
     * <p>
     * The iteration order of the map matches that of the original file.
     * 
     * @return the INI file sections
     */
    public ImmutableMap<String, PropertySet> asMap() {
        return sectionMap;
    }

    //-------------------------------------------------------------------------
    /**
     * Checks if this INI file contains the specified section.
     * 
     * @param name  the section name
     * @return true if the section exists
     */
    public boolean contains(String name) {
        ArgChecker.notNull(name, "name");
        return sectionMap.containsKey(name);
    }

    /**
     * Gets a single section of this INI file.
     * <p>
     * This returns the section associated with the specified name.
     * If the section does not exist an exception is thrown.
     * 
     * @param name  the section name
     * @return the INI file section
     * @throws IllegalArgumentException if the section does not exist
     */
    public PropertySet section(String name) {
        ArgChecker.notNull(name, "name");
        if (contains(name) == false) {
            throw new IllegalArgumentException("Unknown INI file section: " + name);
        }
        return sectionMap.get(name);
    }

    //-------------------------------------------------------------------------
    /**
     * Checks if this INI file equals another.
     * <p>
     * The comparison checks the content.
     * 
     * @param obj  the other file, null returns false
     * @return true if equal
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (obj instanceof IniFile) {
            return sectionMap.equals(((IniFile) obj).sectionMap);
        }
        return false;
    }

    /**
     * Returns a suitable hash code for the INI file.
     * 
     * @return the hash code
     */
    @Override
    public int hashCode() {
        return sectionMap.hashCode();
    }

    /**
     * Returns a string describing the INI file.
     * 
     * @return the descriptive string
     */
    @Override
    public String toString() {
        return sectionMap.toString();
    }

}