/* Copyright (C) 2009 Mobile Sorcery AB
This program is free software; you can redistribute it and/or modify it
under the terms of the Eclipse Public License v1.0.
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 Eclipse Public License v1.0 for
more details.
You should have received a copy of the Eclipse Public License v1.0 along
with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html
*/
package com.mobilesorcery.sdk.core;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.mobilesorcery.sdk.core.SectionedPropertiesFile.Section.Entry;
/**
* A utility class for parsing and producing section-based properties files,
* ie files that look like this:
* <blockquote><code>
* [section1]
* a = b
* b = c
* [section2]
* f = g
* g = h
* </code></blockquote>
* <p>There may exist several sections with the same name.
* @author Mattias Bybro, mattias.bybro@purplescout.se
*
*/
public class SectionedPropertiesFile {
public static class Section {
public static class Entry {
private final String value;
private final String key;
public static Entry parse(String entry) {
boolean inEscape = false;
boolean commentStarted = false;
StringBuffer currentBuffer = new StringBuffer();
String key = null;
String value = null;
char[] chars = entry.toCharArray();
for (int i = 0; i < chars.length && !commentStarted; i++) {
char ch = chars[i];
if (ch == '\\' && !inEscape) {
// Escape
inEscape = true;
} else if (ch == '=' && !inEscape) {
key = currentBuffer.toString().trim();
currentBuffer = new StringBuffer();
} else if (ch == '#' && !inEscape) {
commentStarted = true;
} else {
inEscape = false;
currentBuffer.append(ch);
}
}
value = currentBuffer.toString().trim();
if (key == null) {
// If no = sign, this key = value
key = value;
}
// Bug #751: used to check whether value
// empty too, but we shouldn't.
if (!Util.isEmpty(key)) {
return new Entry(key, value);
} else {
return null;
}
}
public Entry(String key, String value) {
this.key = key;
this.value = value;
}
public String getValue() {
return value;
}
public String getKey() {
return key;
}
@Override
public String toString() {
StringBuffer toString = new StringBuffer();
if (key != null) {
toString.append(escape(key));
toString.append(" = "); //$NON-NLS-1$
}
toString.append(escape(value));
return toString.toString();
}
}
private final String name;
private final ArrayList<Entry> entries = new ArrayList<Entry>();
Section(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<Entry> getEntries() {
return entries;
}
public void addEntry(Entry entry) {
entries.add(entry);
}
public void addEntry(String key, String value) {
addEntry(new Entry(key, value));
}
public void addEntries(Map<String, String> properties) {
for (String key : properties.keySet()) {
addEntry(new Entry(key, properties.get(key)));
}
}
public String[] getValues() {
String[] values = new String[entries.size()];
for (int i = 0; i < values.length; i++) {
values[i] = entries.get(i).getValue();
}
return values;
}
public Map<String, String> getEntriesAsMap() {
HashMap<String, String> map = new HashMap<String, String>();
for (Entry entry : entries) {
String key = entry.getKey();
if (key != null) {
map.put(key, entry.getValue());
}
}
return map;
}
@Override
public String toString() {
StringBuffer toString = new StringBuffer();
if (name != null) {
toString.append("["); //$NON-NLS-1$
toString.append(name);
toString.append("]\n"); //$NON-NLS-1$
}
for (Entry entry : entries) {
toString.append(entry.toString());
toString.append('\n');
}
return toString.toString();
}
private static String escape(String str) {
return str.replace("\\", "\\\\").replace("=", "\\="); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
public static SectionedPropertiesFile parse(File file) throws IOException {
FileReader reader = new FileReader(file);
try {
return parse(reader);
} finally {
if (reader != null) {
reader.close();
}
}
}
public static SectionedPropertiesFile parse(Reader input) throws IOException {
SectionedPropertiesFile result = new SectionedPropertiesFile();
LineNumberReader lines = new LineNumberReader(input);
Section currentSection = new Section(null);
// TODO: Refactor into separate class.
for (String line = lines.readLine(); line != null; line = lines.readLine()) {
String trimmed = line.trim();
//currentSection
if (trimmed.startsWith("[")) { //$NON-NLS-1$
int endIndex = trimmed.indexOf(']');
if (endIndex != -1) {
String currentSectionName = trimmed.substring(1, endIndex);
result.sections.add(currentSection);
currentSection = new Section(currentSectionName);
}
} else {
Entry entry = Entry.parse(line);
if (entry != null) {
currentSection.addEntry(entry);
}
}
}
result.sections.add(currentSection);
return result;
}
public static SectionedPropertiesFile create() {
SectionedPropertiesFile result = new SectionedPropertiesFile();
result.sections.add(new Section(null));
return result;
}
private final ArrayList<Section> sections = new ArrayList<Section>();
private SectionedPropertiesFile() {
}
public Section getFirstSection(String name) {
for (Section section : sections) {
if (section.getName() == null && name == null) {
return section;
}
if (section.getName() != null && section.getName().equals(name)) {
return section;
}
}
return null;
}
public Section addSection(String name) {
Section section = new Section(name);
sections.add(section);
return section;
}
/**
* Returns a string representation of this sectioned
* properties that can be parsed using the <code>parse</code>
* methods.
*/
@Override
public String toString() {
StringBuffer toString = new StringBuffer();
for (Section section : sections) {
toString.append(section);
toString.append('\n');
}
return toString.toString();
}
/**
* Returns the 'default' section.
* Equivalant to call getFirstSection(null)
* There is exactly one default section
* @return
*/
public Section getDefaultSection() {
return getFirstSection(null);
}
public List<Section> getSections() {
return sections;
}
/**
* Writes these properties to a file that can
* be parsed using {@link #parse(File)}.
* @param output
* @throws IOException
*/
public void write(File output) throws IOException {
output.getParentFile().mkdirs();
FileWriter writeTo = new FileWriter(output);
try {
writeTo.write(toString());
} finally {
Util.safeClose(writeTo);
}
}
}