Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Creating .INI Classes in Java


Aug03: Creating .INI Classes in Java

Mark is a software developer on Team RateView with TravelClick (http://www.TravelClick.net/). Mark can be reached at [email protected].


I recently worked on a project that required reading standard Microsoft-style .ini files in Java. Although the JDK 1.4 does not specifically provide an .ini class, I thought that either the JDK 1.4 java.util.Properties or the Preferences API would do the job. I quickly discovered, however, that neither cleanly translates to the standard .ini file format. By "standard" .ini file format, I mean a format similar to Microsoft's .ini file format, where text entries are organized by sections, each section with corresponding key/value pairs separated by an equals sign (=), and comments that use semicolons (;) and pound signs (#); see Example 1.

With this in mind, I created IniFile, a lightweight Java class that reads standard .ini text files into memory, lets you manipulate their contents, and store the various sections with their corresponding key/value pairs in memory.

IniFile (available electronically; see "Resource Center," page 5) has one public constructor, which takes two parameters. The first parameter is a String object pointing to a disk file; and the second parameter is a Boolean indicating whether the IniFile class should be case sensitive. If this parameter is False, then IniFile turns case sensitivity off (see Listing One). After calling the IniFile constructor, you call the loadFromFile() method (Listing Two) to load the contents of an .ini file in to memory. Calling the IniFile constructor and the loadFromFile() method on a file that does not exist causes no problems. In fact, this is what you need to do to create new files right from the start.

Working with IniSections

After loading IniFile (be it empty or not), you can create a new section or read from an existing section in the IniFile object. The IniFile class relies upon another class—IniSection (available electronically). Just as a typical .ini file has one or more sections designated by square brackets [MySection], the IniFile class creates one IniSection object for each section it finds in the file. If you want to add a section to your in-memory IniFile object, invoke the addSection() method, passing in the name of the section identifier you want to create:

inf.loadFromFile();

iniSection sect = inf.addSection("fruit");

As Figure 1 illustrates, IniFile also provides public methods to delete a section (public void deleteSection(String value)), get a pointer to an existing section (public IniSection getSection(String name)), get a collection of all sections (public Set getSections()), and query as to whether a given section exists (public boolean hasSection(String value)).

The IniSection class encapsulates a specific section in a given .ini file. A typical section has one or more key/value pairs that are separated by an equals sign:

[fruit]

banana = yellow

The IniSection class (Figure 2) provides accessor methods to delete a key (public void deleteKey(String key)), get a collection of the keys in a section (public Set getKeys()), get a value given a key (public String getValue(String key)), get a collection of values (public Set getValues()), determine whether a key exists (public boolean hasKey(String key)), determine whether a value exists (public boolean hasValue(String value)), set the value of key/value pairs (public void setValue (String key, String value)), and return the section represented as a property of java.util.Properties (public Properties getProperties()).

Writing to Disk with IniFile

IniFile works with the contents of an .ini file in memory after calling the loadFromFile() method. When you're ready to write changes to disk, simply invoke the flushToFile() method (Listing Three), and IniFile does the rest. This scratch-pad ability also lets you load the contents of one file, make changes in memory, then write the contents out to another file.

The IniFile class relies on a LinkedHashMap class to maintain an ordered list of its sections. I chose LinkedHashMap because, according to JDK 1.4, iteration, ordering is defined and is normally the order in which keys are inserted into the LinkedHashMap object. Just as with IniFile, the IniSection object utilizes a LinkedHashMap class to maintain its list of key/value pairs.

When IniFile parses a file into memory, it creates an IniSection object for each section it finds in the file. It then populates the IniSection object by calling the setValue() method, passing in the key and value. The setValue() method (Listing Four) is simply a wrapper method that first checks to see whether case sensitivity is on, then checks whether the key already exists in the internal LinkedHashMap _map. If the key does not exist, it calls the put() method of the LinkedHashMap class (available electronically).

When an IniSection has been filled in, the IniFile class calls the put method of its LinkedHashMap (_sectionMap), passing in the name of the section and the now-filled-in IniSection object:

_sectionMap.put(name, currentSection);

Maintaining Anonymity

While first storing all the elements of an IniSection object in a LinkedHashMap, then storing the IniSection objects in another LinkedHashMap, makes things easy for the IniFile class, I still needed a mechanism to flag lines that contained white space and comments. I also needed a means of storing these special lines in the order they appeared in the file and writing them back out to a file—while not having to worry about them during retrieval processes. The trick is to treat comment and whitespace lines as if they're keys and sections, but still make them anonymous so they cannot be retrieved.

For instance, if IniFile found:

1 # comment 1

2 # comment 2

3 # comment 3

4 [MyFirstSection]

at the top of a file, it would interpret and store lines 1-3 as anonymous sections. It doesn't matter to IniFile that the anonymous sections do not have associated key/value pairs. Line 4 would be stored as a legitimate section line.

In Listing Five, IniFile treats lines 1-4 as anonymous sections, even though two of the lines are whitespace lines (just like the comment lines). Again, line 5 is accepted as a legitimate section line, and lines 6-7 are stored as anonymous keys under a legitimate section tag [MyFirstSection]. Finally, lines 8-9 are stored as legitimate keys, and line 10 as another legitimate section.

The Not So Obvious

During testing, I searched my hard drive for files with a mask of "*.ini," in hopes of finding some large and complicated examples that would produce good test cases. Out of the gate, I found OPCTRNM.INI located in the C:\ORANT\DBS directory. When I opened it with TextPad, I found C++-style comment lines similar to Figure 3—even though you only use semicolons or pound signs to indicate a comment in .ini files. The last thing I wanted to do was to change code every time a new comment string indicator came along. As luck would have it, I was working with javaCC (http://www.webgain.com/products/java_cc/), which lets you build parser logic. As it turns out, the syntax for defining acceptable token values for the parser (Example 2) is exactly what I needed. By applying this concept to my IniFile class, users can dynamically define any comment indicator they want. To provide this functionality, I use a java.util.HashMap class named _commentTable to store the comment indicators. Access to _commentTable is provided via the public method addComment. Comments are defined in the HashMap table as name/value pairs (see Listing Six). Now, with one method invocation, you can define any comment indicator string (Listing Seven).

Finally, I set up a Boolean flag in the constructor (Listing Eight) to provide case sensitivity. This flag allows decision making to be done when calling internal methods such as hasKey() and setValue().

Conclusion

There are any number of things you can do to make IniFile more useful. For instance, try using a switch to the IniFile constructor, letting it automatically load "default" comments into the comment table. This would prevent users of the class from having to use the loadComment() method each time.

DDJ

Listing One

// constructor
public IniFile( String filePathName, boolean caseOn ) throws
IOException {
  this.setCaseOn(caseOn);
  this.setLineNumberRead(filePathName);
}
// turn case sensitivity off
IniFile inf = new IniFile(args[0],false);

Back to Article

Listing Two

// load the contents of the file passed in to the contructor
inf.loadFromFile();

Back to Article

Listing Three

 // create IniFile object from mark.ini
IniFile inf = new IniFile("c:\\mark.ini",false);
inf.loadFromFile();
// add a section in memory
IniSection sect = inf.addSection("fruit");
// add a key+value pair under the "fruit" section
sect.setValue("banana","yellow");
// write contents of memory out to mega.ini
inf.setFileName("c:\\mega.ini");
inf.flushToFile();

Back to Article

Listing Four

currentSection = new IniSection( name, this.getCaseOn() );
public void setValue (String key, String value){
String s;
  if (getCaseOn()){
    s = key.toUpperCase();
  }
  else{
    s = key;
  }
  if (!this.hasKey(key)){
    _map.put( s, value );
  }
}

Back to Article

Listing Five

1 # comment 1
2 <white space here
3 # comment 2
4 <white space here
5 [MyFirstSection]
6 <white space here
7 ; comment = 3 very bogus
8 banana = yellow
9 apple = red
10 [MyNextSection]

Back to Article

Listing Six

private HashMap _commentTable = new HashMap();
public void addComment(String name, String value){
   _commentTable.put(name,value);
}

Back to Article

Listing Seven

IniFile inf = new IniFile("c:\mark.ini",false);
inf.addComment("POUND","#
");
inf.addComment("SEMICOLON",";");
inf.addComment("DOUBLESLASH","//");

Back to Article

Listing Eight

public void setValue (String key, String
ue){
  String s;
  if (getCaseOn()){
     s = key.toUpperCase();
  }
  else{
    s = key;
  }
  if (!this.hasKey(key)){
    _map.put( s, value );
  }
}





Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.