Changed the format of how the parsed configuration file is stored in memory - lines are now part of the categories, avoiding a costly scan over the whole array if lines are added to a category in the middle

git-svn-id: file:///srv/devel/repo-conversion/nusu@304 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2014-07-20 22:53:03 +00:00
parent 0753aeb220
commit 73a17b7a3e
3 changed files with 49 additions and 32 deletions

View File

@ -70,7 +70,6 @@ namespace Nuclex.Support.Settings {
/// <param name="state">Current parser state</param> /// <param name="state">Current parser state</param>
/// <param name="line">Line that has been read</param> /// <param name="line">Line that has been read</param>
private static void parseLine(ParserState state, string line) { private static void parseLine(ParserState state, string line) {
state.Store.lines.Add(line);
// If the line is empty, ignore it // If the line is empty, ignore it
int length = line.Length; int length = line.Length;
@ -98,6 +97,8 @@ namespace Nuclex.Support.Settings {
} else { } else {
parseOption(state, line, firstCharacterIndex); parseOption(state, line, firstCharacterIndex);
} }
state.Category.Lines.Add(line);
} }
/// <summary>Parses a category definition encountered on a line</summary> /// <summary>Parses a category definition encountered on a line</summary>
@ -130,13 +131,12 @@ namespace Nuclex.Support.Settings {
// Now we know that the line holds a category definition and where exactly in // Now we know that the line holds a category definition and where exactly in
// the line the category name is located. Create the category. // the line the category name is located. Create the category.
state.Category = new Category() { state.Category = new Category() {
LineIndex = state.Store.lines.Count - 1,
CategoryName = new StringSegment( CategoryName = new StringSegment(
line, nameStartIndex, nameEndIndex - nameStartIndex + 1 line, nameStartIndex, nameEndIndex - nameStartIndex + 1
), ),
OptionLookup = new Dictionary<string, Option>() OptionLookup = new Dictionary<string, Option>(),
Lines = new List<string>()
}; };
state.Store.categories.Add(state.Category);
state.Store.categoryLookup.Add(state.Category.CategoryName.ToString(), state.Category); state.Store.categoryLookup.Add(state.Category.CategoryName.ToString(), state.Category);
} }
@ -160,7 +160,7 @@ namespace Nuclex.Support.Settings {
// We have enough information to know that this is an assignment of some kind // We have enough information to know that this is an assignment of some kind
Option option = new Option() { Option option = new Option() {
LineIndex = state.Store.lines.Count - 1, LineIndex = state.Category.Lines.Count - 1,
OptionName = new StringSegment( OptionName = new StringSegment(
line, firstCharacterIndex, nameEndIndex - firstCharacterIndex + 1 line, firstCharacterIndex, nameEndIndex - firstCharacterIndex + 1
) )

View File

@ -209,6 +209,17 @@ namespace Nuclex.Support.Settings {
); );
} }
/// <summary>
/// Verifies that options can be added to the configuration file
/// </summary>
[Test]
public void OptionsCanBeAdded() {
var configurationFile = new ConfigurationFileStore();
configurationFile.Set<string>(null, "test", "123");
Assert.That(configurationFile.Get<string>(null, "test"), Is.EqualTo("123"));
}
} }
} // namespace Nuclex.Support.Settings } // namespace Nuclex.Support.Settings

View File

@ -37,15 +37,15 @@ namespace Nuclex.Support.Settings {
/// <summary>Stores informations about a category found in the configuration file</summary> /// <summary>Stores informations about a category found in the configuration file</summary>
private class Category { private class Category {
/// <summary>Index of the line the category is defined in</summary>
public int LineIndex;
/// <summary>Name of the category as a string</summary> /// <summary>Name of the category as a string</summary>
public StringSegment CategoryName; public StringSegment CategoryName;
/// <summary>Lookup table for the options in this category</summary> /// <summary>Lookup table for the options in this category</summary>
public IDictionary<string, Option> OptionLookup; public IDictionary<string, Option> OptionLookup;
/// <summary>Lines this category and its options consist of</summary>
public IList<string> Lines;
} }
#endregion // class Category #endregion // class Category
@ -70,30 +70,31 @@ namespace Nuclex.Support.Settings {
/// <summary>Initializes a new, empty configuration file</summary> /// <summary>Initializes a new, empty configuration file</summary>
public ConfigurationFileStore() { public ConfigurationFileStore() {
this.lines = new List<string>();
this.categories = new List<Category>();
this.options = new List<Option>(); this.options = new List<Option>();
this.categoryLookup = new Dictionary<string, Category>(); this.categoryLookup = new Dictionary<string, Category>();
this.RootCategory = new Category() { this.RootCategory = new Category() {
LineIndex = -1, OptionLookup = new Dictionary<string, Option>(),
OptionLookup = new Dictionary<string, Option>() Lines = new List<string>()
}; };
} }
/// <summary>Saves the configuration file into the specified writer</summary> /// <summary>Saves the configuration file into the specified writer</summary>
/// <param name="writer">Writer the configuration file will be saved into</param> /// <param name="writer">Writer the configuration file will be saved into</param>
public void Save(TextWriter writer) { public void Save(TextWriter writer) {
for(int index = 0; index < this.lines.Count; ++index) { for(int index = 0; index < this.RootCategory.Lines.Count; ++index) {
writer.WriteLine(this.lines[index]); writer.WriteLine(this.RootCategory.Lines[index]);
}
foreach(Category category in this.categoryLookup.Values) {
for(int index = 0; index < category.Lines.Count; ++index) {
writer.WriteLine(category.Lines[index]);
}
} }
} }
/// <summary>Enumerates the categories defined in the configuration</summary> /// <summary>Enumerates the categories defined in the configuration</summary>
/// <returns>An enumerable list of all used categories</returns> /// <returns>An enumerable list of all used categories</returns>
public IEnumerable<string> EnumerateCategories() { public IEnumerable<string> EnumerateCategories() {
for(int index = 0; index < this.categories.Count; ++index) { return this.categoryLookup.Keys;
yield return this.categories[index].CategoryName.ToString();
}
} }
/// <summary>Enumerates the options stored under the specified category</summary> /// <summary>Enumerates the options stored under the specified category</summary>
@ -242,18 +243,29 @@ namespace Nuclex.Support.Settings {
} }
Option newOption = new Option() { Option newOption = new Option() {
LineIndex = this.lines.Count,
OptionName = new StringSegment(line, 0, name.Length), OptionName = new StringSegment(line, 0, name.Length),
OptionValue = new StringSegment(line, name.Length + 3, valueLength) OptionValue = new StringSegment(line, name.Length + 3, valueLength)
}; };
// TODO: Find end line of category and add line // Figure out which line the new option should be put in
int lastLineIndex = category.Lines.Count - 1;
if((lastLineIndex > 0) && (category.Lines[lastLineIndex].Length == 0)) {
newOption.LineIndex = lastLineIndex;
category.Lines.Insert(lastLineIndex, line);
} else {
newOption.LineIndex = category.Lines.Count;
category.Lines.Add(line);
category.Lines.Add(string.Empty);
}
category.OptionLookup.Add(name, newOption);
} }
/// <summary>Changes the value of an option</summary> /// <summary>Changes the value of an option</summary>
/// <param name="category">Category that holds the option</param>
/// <param name="option">Option whose value will be changed</param> /// <param name="option">Option whose value will be changed</param>
/// <param name="newValue">New value that will be assigned to the option</param> /// <param name="newValue">New value that will be assigned to the option</param>
private void changeOption(Option option, string newValue) { private void changeOption(Category category, Option option, string newValue) {
int newValueLength; int newValueLength;
if(newValue == null) { if(newValue == null) {
newValueLength = 0; newValueLength = 0;
@ -287,8 +299,8 @@ namespace Nuclex.Support.Settings {
line = builder.ToString(); line = builder.ToString();
} }
this.lines[option.LineIndex] = line;
option.OptionValue = new StringSegment(line, option.OptionValue.Offset, newValueLength); option.OptionValue = new StringSegment(line, option.OptionValue.Offset, newValueLength);
category.Lines[option.LineIndex] = line;
} }
/// <summary>Creates a new category in the configuration file</summary> /// <summary>Creates a new category in the configuration file</summary>
@ -304,26 +316,20 @@ namespace Nuclex.Support.Settings {
categoryDefinition = builder.ToString(); categoryDefinition = builder.ToString();
} }
// An empty line before the category definition for better readability
this.lines.Add(string.Empty);
Category newCategory = new Category() { Category newCategory = new Category() {
LineIndex = this.lines.Count,
CategoryName = new StringSegment(categoryDefinition, 1, category.Length), CategoryName = new StringSegment(categoryDefinition, 1, category.Length),
OptionLookup = new Dictionary<string, Option>() OptionLookup = new Dictionary<string, Option>(),
Lines = new List<string>()
}; };
this.lines.Add(categoryDefinition);
newCategory.Lines.Add(categoryDefinition);
newCategory.Lines.Add(string.Empty);
this.categoryLookup.Add(category, newCategory); this.categoryLookup.Add(category, newCategory);
return newCategory; return newCategory;
} }
/// <summary>Lines contained in the configuration file</summary>
private IList<string> lines;
/// <summary>Records where categories are stored in the configuration file</summary>
private IList<Category> categories;
/// <summary>Records where options are stored in the configuration file</summary> /// <summary>Records where options are stored in the configuration file</summary>
private IList<Option> options; private IList<Option> options;