Creating .INI Classes in Java

IniFile is a lightweight Java class that reads standard .INI text files into memory, lets you manipulate their contents, and stores their corresponding key/value pairs in memory.


August 01, 2003
URL:http://www.drdobbs.com/jvm/creating-ini-classes-in-java/184405408

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

Aug03: Creating .INI Classes in Java


; this is a comment
[MGDSFAXGEN]
UserName
SpoolFileName=c:\winnt\temp\zetafax.spl
# this is also a comment
InstructFileLocation=INSTRUCT
FaxFormat=FINE
TopMargin=0.01
BottomMargin=0.01
LeftMargin=0.01
RightMargin=0.01
ReportPath=c:\prodmgds\

Example 1: Typical .ini file.

Aug03: Creating .INI Classes in Java


TOKEN :
{
| <USCORE: "_"
| <PERIOD: "."
| <HYPHEN: "-"
| <EQUALS: "="
| <LBRACK: "["
| <RBRACK: "]"

Example 2: Syntax for defining acceptable token values for the parser.

Aug03: Creating .INI Classes in Java

Figure 1: UML for the IniFile class.

Aug03: Creating .INI Classes in Java

Figure 2: UML for the IniSection class.

Aug03: Creating .INI Classes in Java

// NAME
//   opctrnm.ini - Counter Loading file
// DESCRIPTION
//   File that contains description of counters 
//   to be loaded into the registry.
//RELATED DOCUMENTS

Figure 3: C++-style comments in an .ini file.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.