org.devzendo.commoncode.file.INIFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of common-code Show documentation
Show all versions of common-code Show documentation
Common utility code
(Apache License v2) 2008-2024 Matt Gumbley, DevZendo.org
The newest version!
/*
* Copyright (C) 2008-2017 Matt Gumbley, DevZendo.org http://devzendo.org
*
* 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.
*/
package org.devzendo.commoncode.file;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* API for access to Windows-.ini style files.
*
* @author matt
*
*/
public class INIFile {
private static final Logger LOGGER = LoggerFactory.getLogger(INIFile.class);
private final Map > mySectionMap;
private final File myFile;
private boolean bDirty = false;
private int myWriteSuspensions = 0;
/**
* Create a new .ini file, or load an existing one with a given path.
* Throws UncheckedIOException on load/save error.
* @param fileName the path of the file to create.
*/
public INIFile(final String fileName) {
super();
myWriteSuspensions = 0;
mySectionMap = new HashMap < String, Map > ();
myFile = new File(fileName);
if (myFile.exists()) {
LOGGER.debug("Loading existing INI file: " + fileName);
loadFile();
} else {
LOGGER.debug("Creating new INI file: " + fileName);
saveFile();
}
bDirty = false;
}
/**
* Allow the writing of the file to be suspended. Calls to this can
* be nested, although all such calls must be matched with calls
* to resumeWrite, else the file will never be updated.
*/
public final synchronized void suspendWrite() {
myWriteSuspensions++;
}
/**
* Resume the writing of the file on calls to setXXXX. If the file had
* changed during write suspension, it is now written.
*/
public final synchronized void resumeWrite() {
myWriteSuspensions--;
if (myWriteSuspensions == 0 && bDirty) {
saveFile();
}
}
private void saveFile() {
if (myWriteSuspensions > 0 || !bDirty) {
return;
}
final String lineSep = System.getProperty("line.separator");
try {
final FileOutputStream fos = new FileOutputStream(myFile);
final FileDescriptor fd = fos.getFD();
final FileWriter fw = new FileWriter(fd);
try {
for (String sectionName : mySectionMap.keySet()) {
fw.write("[" + sectionName + "]" + lineSep);
final Map nvps = mySectionMap.get(sectionName);
for (String name : nvps.keySet()) {
final String valueObject = nvps.get(name);
//final String value = valueObject == null ? "" : valueObject.toString();
final String value = valueObject.toString();
fw.write(name + "=" + value + lineSep);
}
}
} finally {
fw.flush();
fos.flush();
fd.sync();
fw.close();
fos.close();
}
} catch (final IOException e) {
final String msg = "Could not write INI file " + myFile.getAbsolutePath() + ": " + e.getMessage();
LOGGER.error(msg);
throw new UncheckedIOException(msg, e);
}
}
private void loadFile() {
final Matcher sectionMatcher = Pattern.compile("^\\[(.*)\\]$").matcher("");
final Matcher nvpMatcher = Pattern.compile("^(.*?)=(.*)$$").matcher("");
Map currentSectionMap = null;
String currentSectionName = null;
int lineNo = 0;
try {
final BufferedReader br = new BufferedReader(new FileReader(myFile));
try {
while (true) {
final String line = br.readLine();
LOGGER.debug("Read line '" + line + "'");
if (line == null) {
break;
}
lineNo++;
sectionMatcher.reset(line);
if (sectionMatcher.lookingAt()) {
currentSectionName = sectionMatcher.group(1);
LOGGER.debug("Found section [" + currentSectionName + "]");
final Map newSectionMap = new HashMap();
currentSectionMap = newSectionMap;
mySectionMap.put(currentSectionName, newSectionMap);
} else {
nvpMatcher.reset(line);
if (nvpMatcher.lookingAt()) {
if (currentSectionMap == null) {
final String msg = "Line " + lineNo + " name=value line not under any [section]: '" + line + "'";
LOGGER.error(msg);
throw new IllegalStateException(msg);
} else {
final String name = nvpMatcher.group(1);
final String value = nvpMatcher.group(2);
LOGGER.debug("[" + currentSectionName + "] " + name + "=" + value);
currentSectionMap.put(name, value);
}
} else {
final String msg = "Line " + lineNo + " not matched against [section] or name=value: '" + line + "'";
LOGGER.error(msg);
throw new IllegalStateException(msg);
}
}
}
} catch (final IOException ioe) {
LOGGER.error("Could not load INI file: " + ioe.getMessage());
// hard to test for this
} finally {
if (br != null) {
try {
br.close();
} catch (final IOException e1) {
LOGGER.error("Could not close BufferedReader: " + e1.getMessage());
// hard to test for this
}
}
}
} catch (final FileNotFoundException fnfe) {
// This is generated if there is genuinely nothing at this File object's path,
// or, if there is, but it is not a *file* (e.g. if it's a directory).
final String msg = "INI file " + myFile.getAbsolutePath() + " not found";
LOGGER.error(msg);
throw new UncheckedIOException(msg, fnfe);
}
}
/**
* Obtain a value from the file, with no default if it does not exist.
* @param sectionName the [section name] under which this value could be found
* @param name the name= key to the given value
* @return the value, if one exists, null if it does not exist.
*/
public final String getValue(final String sectionName, final String name) {
if (!mySectionMap.containsKey(sectionName)) {
LOGGER.debug("getValue(" + sectionName + "," + name + "): not found [section]");
return null;
} else {
final Map sectionMap = mySectionMap.get(sectionName);
final String value = sectionMap.get(name); // returns null on 'not found'
LOGGER.debug("getValue(" + sectionName + "," + name + "): returning '" + value + "'");
return value;
}
}
/**
* Obtain a value from the file, with a default if it does not exist.
* @param sectionName the [section name] under which this value could be found
* @param name the name= key to the given value
* @param defaultValue the value, if the name does not exist.
* @return the value, if one exists, defaultValue if it does not exist.
*/
public final String getValue(final String sectionName, final String name, final String defaultValue) {
final String value = getValue(sectionName, name);
return value == null ? defaultValue : value;
}
/**
* Remove a name=value pair from the file. If the removed entry is the final
* entry in this [section name], remove the section name also.
* @param sectionName the [section name] under which the name will be deleted.
* @param name the name= that will be deleted, if it exists.
*/
public final synchronized void removeValue(final String sectionName, final String name) {
if (!mySectionMap.containsKey(sectionName)) {
LOGGER.debug("removeValue(" + sectionName + ", " + name + "): not found [section]");
return;
}
bDirty = true;
final Map sectionMap = mySectionMap.get(sectionName);
sectionMap.remove(name);
if (sectionMap.size() == 0) {
LOGGER.debug("removeValue(" + sectionName + ", " + name
+ "): final name returned from [section]; removing [section]");
mySectionMap.remove(sectionName);
}
saveFile();
}
/**
* Store a String value in the file.
* @param sectionName the [section name] under which this value will be stored.
* @param name the name= key to the given value
* @param value the value.
*/
public final synchronized void setValue(final String sectionName, final String name, final String value) {
if (sectionName == null || name == null || value == null) {
throw new IllegalArgumentException("Null values cannot be stored: sectionName: "
+ sectionName + " name: "
+ name + " value: "
+ value);
}
Map sectionMap = null;
bDirty = true;
if (!mySectionMap.containsKey(sectionName)) {
sectionMap = new HashMap();
mySectionMap.put(sectionName, sectionMap);
LOGGER.debug("setValue(" + sectionName + "," + name + "," + value + "): created new [class]");
} else {
sectionMap = mySectionMap.get(sectionName);
}
LOGGER.debug("setValue(" + sectionName + "," + name + "," + value + "): saving");
sectionMap.put(name, value);
saveFile();
}
/**
* Store a long value in the file.
* @param sectionName the [section name] under which this value will be stored.
* @param name the name= key to the given value
* @param value the value.
*/
public final void setLongValue(final String sectionName, final String name, final long value) {
setValue(sectionName, name, "" + value);
}
/**
* Store an integer value in the file.
* @param sectionName the [section name] under which this value will be stored.
* @param name the name= key to the given value
* @param value the value.
*/
public final void setIntegerValue(final String sectionName, final String name, final int value) {
setValue(sectionName, name, "" + value);
}
/**
* Store a boolean value in the file.
* @param sectionName the [section name] under which this value will be stored.
* @param name the name= key to the given value
* @param value the value.
*/
public final void setBooleanValue(final String sectionName, final String name, final boolean value) {
setValue(sectionName, name, value ? "TRUE" : "FALSE");
}
/**
* Obtain a long value from the file.
* @param sectionName the [section name] from which this value will be obtained.
* @param name the name= key to the given value
* @return the value.
*/
public final long getLongValue(final String sectionName, final String name) {
final String value = getValue(sectionName, name, "0");
try {
return Long.parseLong(value);
} catch (final NumberFormatException e) {
LOGGER.warn("Value of section [" + sectionName + "], name '" + name + "' is '" + value + "' which is not a long integer");
return 0L;
}
}
/**
* Obtain an integer value in the file.
* @param sectionName the [section name] from which this value will be obtained.
* @param name the name= key to the given value
* @return the value.
*/
public final int getIntegerValue(final String sectionName, final String name) {
final String value = getValue(sectionName, name, "0");
try {
return Integer.parseInt(value);
} catch (final NumberFormatException e) {
LOGGER.warn("Value of section [" + sectionName + "], name '" + name + "' is '" + value + "' which is not an integer");
return 0;
}
}
/**
* Obtain a boolean value in the file.
* @param sectionName the [section name] from which this value will be obtained.
* @param name the name= key to the given value
* @return the value, or false if this name does not exist.
*/
public final boolean getBooleanValue(final String sectionName, final String name) {
final String value = getValue(sectionName, name, "FALSE");
try {
return Boolean.parseBoolean(value);
} catch (final NumberFormatException e) {
LOGGER.warn("Value of section [" + sectionName + "], name '" + name + "' is '" + value + "' which is not a boolean");
return false;
}
}
/**
* Obtain an array of values from a section name in the file.
* @param sectionName the [section name] from which these values will be obtained.
* @return the values.
*/
public final String[] getArray(final String sectionName) {
if (!mySectionMap.containsKey(sectionName)) {
LOGGER.debug("getArray(" + sectionName + "): not found [section]");
return new String[0];
} else {
final Map sectionMap = mySectionMap.get(sectionName);
final ArrayList array = new ArrayList();
for (int i = 0; i < sectionMap.size(); i++) {
array.add(sectionMap.get("" + (i + 1)));
}
return array.toArray(new String[0]);
}
}
/**
* Set an array of values under a section name.
* @param sectionName the [section name]
* @param array the array of values
*/
public final synchronized void setArray(final String sectionName, final String[] array) {
bDirty = true;
final Map arrayMap = new HashMap();
for (int i = 0; i < array.length; i++) {
arrayMap.put("" + (i + 1), array[i]);
}
mySectionMap.put(sectionName, arrayMap);
LOGGER.debug("setArray(" + sectionName + ", ...): saving");
saveFile();
}
/**
* Completely remove a [section name], even if populated.
* @param sectionName the [section name]
*/
public final synchronized void removeSection(final String sectionName) {
if (!mySectionMap.containsKey(sectionName)) {
LOGGER.debug("removeSection(" + sectionName + "): not found [section]");
} else {
bDirty = true;
mySectionMap.remove(sectionName);
LOGGER.debug("removeSection(" + sectionName + "): saving");
saveFile();
}
}
}