Skip to content

sshtools/jini

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jini

Maven Build/Test JDK 17 Maven Central Coverage Status javadoc JPMS

A small Java library to read and write INI files.

  • Zero dependencies.
  • Configurable to be usable with most variants of the format.
  • Global sections.
  • Nested sections.
  • Line continuations
  • Configurable delimiters.
  • Configurable whitespace usage.
  • Multiple modes for handling of duplicate properties and sections.
  • Multiple modes for handling of multi-value keys
  • Multiple modes for handling of escape characters
  • Configurable case sensitivity.
  • Configurable order preservation.
  • JPMS compliant.
  • Requires JDK 11 or above (JDK 17 for tests).
  • Optional Preferences implementation.
  • String interpolation with configurable variable pattern.
  • Optional reflection based serialization and deserialization of objects.

WIP

  • Tests (see above badge for current coverage)

Installation

Available on Maven Central, so just add the following dependency to your project's pom.xml.

<dependency>
    <groupId>com.sshtools</groupId>
    <artifactId>jini-lib</artifactId>
    <version>0.4.0-SNAPSHOT</version>
</dependency>

See badge above for version available on Maven Central. Snapshot versions are in the Sonatype OSS Snapshot Repository.

<repository>
	<id>oss-snapshots</id>
	<url>https://oss.sonatype.org/content/repositories/snapshots</url>
	<snapshots />
	<releases>
		<enabled>false</enabled>
	</releases>
</repository>

JPMS

If you are using JPMS, add com.sshtools.jini to your module-info.java.

Build From Source

Using Apache Maven is recommended.

  • Clone this module
  • Change directory to where you cloned to
  • Run mvn package
  • Jar Artifacts will be in the target directory.

Usage

The general pattern for reading an INI document is ..

  • Create a configured INIReader via INIReader.Builder.
  • Get an INI instance using reader.read(...).
  • Query the INI instance for sections, properties etc.

And for writing an INI document ..

  • Either use an INI document you have obtained from an INIReader, create a default document (order preserved, case insensitive keys) using INI.create(), or use INI.Builder() to configure behaviour.
  • Create a configured INIWriter via INIWriter.Builder.
  • Write the instance to some target using INIWriter.write(..).

Example

Write A New INI Document

    var ini = INI.create();
    ini.put("Name", "Alice");
    ini.put("Age", 34);
    ini.put("Registered", false);
    
    var sec = ini.create("Address");
    sec.put("Street", "15 Stone Lane");
    sec.put("Area", "");
    sec.put("City", "Arbington");
    sec.put("County", "Inishire");
    sec.put("PostCode", "ABC 123");
    
    var wrt = new INIWriter.Builder().build();
    
    try(var out = Files.newBufferedWriter(Paths.get("data.ini"))) {
        wrt.write(ini, out);
    }

Read An INI Document From A File

    var ini = INI.fromFile(Paths.get("data.ini"));
    System.out.format("Name: %s%n". ini.get("Name")); 
    System.out.format("Age: %d%n". ini.getInt("Age"));
    if(ini.getBoolean("Registered"))
        System.out.println("Is registered%n");
    
    ini.sectionOr("Address").ifPresent(s -> {  
        System.out.println("Address");
        System.out.format("  Street: %s%n". s.get("Street"));
        System.out.format("  Area: %s%n". s.get("Area"));
        System.out.format("  City: %s%n". s.get("City"));
        System.out.format("  County: %s%n". s.get("County"));
        System.out.format("  PostCode: %s%n". s.get("PostCode"));
        System.out.format("  Tel: %s%n". s.get("PostCode", "N/A"));
    });
    

Preferences Backing Store

An optional java.util.prefs.Preferences implementation is available, which will replace the default when added to the CLASSPATH.

Instead of storing preferences in the registry, or XML files, or whatever your platform might use by default, all preferences would be stored in .ini files in appropriate locations.

Just add the following dependency to your project's pom.xml.

<dependency>
    <groupId>com.sshtools</groupId>
    <artifactId>jini-prefs</artifactId>
    <version>0.4.0-SNAPSHOT</version>
</dependency>

Usage

The .ini file backend will not be initialised until the first time you access a Preferences node. Files will not be written until the first .put() or .flush().

	var prefs = Preferences.userRoot();
	prefs.put("akey", "Some Value");

It is possible to configure INI based preferences if you do so before the first access to a Preferences node.

	var bldr = new INIStoreBuilder().
				withScope(Scope.USER).
				withoutAutoFlush().
				withName("jini.test").
				withPath(Files.createTempDirectory("jinitestdir"));
				
	var store = bldr.build();
	INIPreferences.configure(store);		
	var prefs = Preferences.userRoot();
	prefs.put("akey", "Some Value");
	System.out.println(prefs.get("akey", "No value!"));
	
	// ...

You can also use this builder make use of the Preferences API without using the static methods in Preferences such as systemRoot(). You can create as many roots as you like (to different file paths), and make use of them in any manner you like.

	var bldr = new INIStoreBuilder().
				withScope(Scope.USER).
				withoutAutoFlush().
				withName("jini.test").
				withPath(Files.createTempDirectory("jinitestdir"));
				
	try (var store = bldr.build()) {
		var prefs = store.root();
		prefs.put("akey", "Some Value");
		System.out.println(prefs.get("akey", "No value!"));
	}			

Note that a store is scoped, and should be closed when finished with.

Object Serialization and De-serialization

The optional jini-serialization module adds support for writing an object graph as human-readable INI files, as well as reading such INI files and recreating said object graph.

Just add the following dependency to your project's pom.xml.

<dependency>
    <groupId>com.sshtools</groupId>
    <artifactId>jini-serialization</artifactId>
    <version>0.4.0-SNAPSHOT</version>
</dependency>

Limitations

  • Collections and Maps must provide an itemType for their values for de-serialization to work.
  • Maps currently only support String keys, and values can only be primitive types.
  • The entire object graph is serialized (subject to include / exclude rules). There is no support for object references yet.

Usage

Serialization and de-serialization works using Java's reflection feature. It will look for accessible fields that are not otherwise configured to be excluded, inspect their value and convert it an entry in a INI section with a key and a string value. If the value is a complex object, it may create further INI sections and inspect the object for it's values, and so on.

Sometimes fields may be not be immediately accessible, for example if they are private. By default, Jini will try to make such fields accessible to reflection.

Modern Java has further restriction on accessing such fields, and it reflection may not be possible. In these cases, Jini will also look for accessor methods that can be reflected and provide the value. By default, it expects to use the JavaBean pattern, i.e. "getter" and "setter" methods.

Serialization - Writing An Object

The entry point to serializing an object can be via a (reusable) INISerializer which itself cannot be constructed directly, but instead is created by INISerializer.Builder. Once an INISerializer is obtained, you then call srlzr.serialize(myObject) which will return an INI object that you mean then write as normal (e.g. using an INIWriter).

Alternatively, you can use one of several convenience methods.

  • INISerializer.toINI(Object object) creates an INI from an Object.
  • INISerializer.write(Object object, String path) and INISerializer.writer(Object object, Path path) writes an object to a file.
  • INISerializer.write(Object object, Writer writer) writes the contents of the INI to a Writer.

If your object contains only primitives, String and other primitive objects, ByteBuffer, any other Object that follows follows the same rules, or arrays of such objects or primitives, then no additional code will be to allow serialization to happen.

class Address {
	String line1 = "99 Some Road";
	String city = "Nodnol";
}

class Person {
	String name = "Mr Crumbly";
	int age = 99;
	Address address = new Address();
	String[] telephones = new String[] { "123 456789", "987 654321" };
}

INISerialize.write(new Person(), "crumbly.ini");

If your object contains any other Object that does not fit this description, or a Collection or Map of any type, then you many need to add additional meta-data (such as itemType to overcome type-erasure) to allow Jini to serialize the object.

This can be done by either adding annotations to the target object, or using the programmatic callback API provided by INISerializer.Builder. This customisation also allows fields or methods to be excluded, have different keys used in the INI file and other behavioural changes.

De-serialization - Reading An Object

The entry point to de-serializing an object can be via a (reusable) INIDeserializer which itself cannot be constructed directly, but instead is created by INIDeserializer.Builder. Once an INIDeserializer is obtained, you then call desrlzr.deserialize(ini, MyObject.class) which will return an instance of type MyObject constructed from the data in an INI document that you obtained as normal (e.g. using an INIReader).

Alternatively, you can use one of several convenience methods.

  • INIDeserializer.fromINI(INI ini, MyObject.class) creates a MyObject from an INI.
  • INIDeserializer.read(String path, MyObject.class) and INISerializer.read(Path path, MyObject.class) reads an object from a file.
  • INISerializer.read(Reader reader, MyObject.class) reader INI contents from a Reader and construct a MyObject from it.
class Address {
	String line1;
	String city;
}

class Person {
	String name;
	int age;
	Address address;
	String[] telephones;
}

var person = INIDeserializer.read("crumbly.ini", Person.class);

The same rules apply as in serialization, i.e. when the type of an object cannot be derived any other way it must be provided programmatically or via an annotation. The same annotations used in serialization are also used for de-serialization.

De-serialization imposes some additional requirements and restrictions too.

  • All types that have no special handling, must have empty public constructors.
  • For Collection and Map types, if you want it to be of a particular type of collection or map, you should make sure it is initialized in construction (i.e. an initialized field, or created in a default constructor). In this case it must be a modifiable. If the field is null when deserializing, and data for the collection is present, then an unmodifiable variant of the collections will be automatically created and filled.

Changes

0.4.0

  • New jini-serialization module for object serialization and deserialization. See INISerializer and INIDeserializer.
  • duplicateSectionAction default is now DuplicateAction.APPEND. This means multiple sections with the same name by default will now all be available.
  • Multi-line string support for keys and values. Wrap strings in supported quote character, e.g ''' ..... ''' in a manner similar to Java.

0.3.3

  • Fixes for reloading.
  • Removal of value events not fired by INISet.
  • Removed schema types FLOAT, LOCATION and COLOR. These are now Discriminator enumeration instances. There are two discriminator implementations, TextDiscriminator to be used for Type.TEXT and NumberDiscriminator to be used for Type.NUMBER. The list of discriminators is now likely to grow instead of the Type.

0.3.2

  • More work on INISchema, can now wrap an INI to provide a proxied instance that guarantees correctness.
  • Created jini-config module that can be used to provide a monitored INI based configuration system for applications.

0.3.1

  • Added string interpolation features.
  • Renamed some getters to shorten them, and also remove an ambiguity with getAllOr().
  • Separated values separator wasn't being ignored in quotes.

0.3.0

  • Added EscapeMode for INIWriter and INIReader that allows altering behaviour of escaping.
  • java.util.prefs.Preferences implementation

0.2.5

  • Added Section.readOnly() and Document.readOnly() to create a read-only facades.
  • Added INI.empty() that returns a static empty and read-only document.
  • Events are fired from all parent Section or INI on value or child section change.
  • Added obtainSetion() that gets or creates sections given a path.

0.2.4

  • Added onValueUpdate() and onSectionUpdate() to listen for changes to document.

0.2.3

  • Added keys() method.
  • Made containsSection(String) now support testing for nested sections by changing the signature to containsSection(String...).

Credits

Uses LinkedCaseInsensitiveMap from Spring Utilities.

About

Java INI Parser and Writer

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages