{"id":21387193,"url":"https://github.com/sshtools/jini","last_synced_at":"2025-03-16T12:20:23.797Z","repository":{"id":184185462,"uuid":"671448621","full_name":"sshtools/jini","owner":"sshtools","description":"Java INI Parser and Writer","archived":false,"fork":false,"pushed_at":"2024-04-17T10:53:05.000Z","size":303,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-04-24T06:38:51.987Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sshtools.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-27T10:42:19.000Z","updated_at":"2024-04-30T00:26:33.349Z","dependencies_parsed_at":"2023-12-13T12:30:19.479Z","dependency_job_id":"18959850-218d-4a50-9d09-f3df3d418525","html_url":"https://github.com/sshtools/jini","commit_stats":null,"previous_names":["sshtools/jini"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fjini","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fjini/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fjini/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sshtools%2Fjini/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sshtools","download_url":"https://codeload.github.com/sshtools/jini/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243865424,"owners_count":20360442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-22T12:12:06.143Z","updated_at":"2025-03-16T12:20:23.779Z","avatar_url":"https://github.com/sshtools.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jini\n\n![Maven Build/Test JDK 17](https://github.com/sshtools/jini/actions/workflows/maven.yml/badge.svg)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.sshtools/jini/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.sshtools/jini)\n[![Coverage Status](https://coveralls.io/repos/github/sshtools/jini/badge.svg)](https://coveralls.io/github/sshtools/jini)\n[![javadoc](https://javadoc.io/badge2/com.sshtools/jini/javadoc.svg)](https://javadoc.io/doc/com.sshtools/jini)\n![JPMS](https://img.shields.io/badge/JPMS-com.sshtools.jini-purple) \n\nA small Java library to read and write [INI](https://en.wikipedia.org/wiki/INI_file) files. \n \n * Zero dependencies.\n * Configurable to be usable with most variants of the format.\n * Global sections.\n * Nested sections.\n * Line continuations\n * Configurable delimiters.\n * Configurable whitespace usage.\n * Multiple modes for handling of duplicate properties and sections.\n * Multiple modes for handling of multi-value keys\n * Multiple modes for handling of escape characters\n * Configurable case sensitivity.\n * Configurable order preservation.\n * JPMS compliant.\n * Requires JDK 11 or above (JDK 17 for tests).\n * Optional [Preferences](#preferences-backing-store) implementation.\n * String interpolation with configurable variable pattern.\n * Optional reflection based serialization and deserialization of objects.\n \n## WIP\n\n * Tests (see above badge for current coverage)\n \n## Installation\n\nAvailable on Maven Central, so just add the following dependency to your project's `pom.xml`.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.sshtools\u003c/groupId\u003e\n    \u003cartifactId\u003ejini-lib\u003c/artifactId\u003e\n    \u003cversion\u003e0.4.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n_See badge above for version available on Maven Central. Snapshot versions are in the [Sonatype OSS Snapshot Repository](https://oss.sonatype.org/content/repositories/snapshots/)._\n\n```xml\n\u003crepository\u003e\n\t\u003cid\u003eoss-snapshots\u003c/id\u003e\n\t\u003curl\u003ehttps://oss.sonatype.org/content/repositories/snapshots\u003c/url\u003e\n\t\u003csnapshots /\u003e\n\t\u003creleases\u003e\n\t\t\u003cenabled\u003efalse\u003c/enabled\u003e\n\t\u003c/releases\u003e\n\u003c/repository\u003e\n```\n### JPMS\n\nIf you are using [JPMS](https://en.wikipedia.org/wiki/Java_Platform_Module_System), add `com.sshtools.jini` to your `module-info.java`.\n\n### Build From Source\n\nUsing [Apache Maven](maven.apache.org/) is recommended.\n\n * Clone this module\n * Change directory to where you cloned to\n * Run `mvn package`\n * Jar Artifacts will be in the `target` directory.\n \n## Usage\n\nThe general pattern for reading an INI document is ..\n\n * Create a configured `INIReader` via `INIReader.Builder`.\n * Get an `INI` instance using `reader.read(...)`.\n * Query the `INI` instance for sections, properties etc.\n \nAnd for writing an INI document ..\n\n * Either use an `INI` document you have obtained from an `INIReader`, create a default \n   document (order preserved, case insensitive keys) using `INI.create()`, or use `INI.Builder()` to configure behaviour.\n * Create a configured `INIWriter` via `INIWriter.Builder`.\n * Write the instance to some target using `INIWriter.write(..)`.\n \n## Example\n\n\n### Write A New INI Document\n\n```java\n    var ini = INI.create();\n    ini.put(\"Name\", \"Alice\");\n    ini.put(\"Age\", 34);\n    ini.put(\"Registered\", false);\n    \n    var sec = ini.create(\"Address\");\n    sec.put(\"Street\", \"15 Stone Lane\");\n    sec.put(\"Area\", \"\");\n    sec.put(\"City\", \"Arbington\");\n    sec.put(\"County\", \"Inishire\");\n    sec.put(\"PostCode\", \"ABC 123\");\n    \n    var wrt = new INIWriter.Builder().build();\n    \n    try(var out = Files.newBufferedWriter(Paths.get(\"data.ini\"))) {\n        wrt.write(ini, out);\n    }\n```\n\n### Read An INI Document From A File\n\n```java\n    var ini = INI.fromFile(Paths.get(\"data.ini\"));\n    System.out.format(\"Name: %s%n\". ini.get(\"Name\")); \n    System.out.format(\"Age: %d%n\". ini.getInt(\"Age\"));\n    if(ini.getBoolean(\"Registered\"))\n        System.out.println(\"Is registered%n\");\n    \n    ini.sectionOr(\"Address\").ifPresent(s -\u003e {  \n        System.out.println(\"Address\");\n        System.out.format(\"  Street: %s%n\". s.get(\"Street\"));\n        System.out.format(\"  Area: %s%n\". s.get(\"Area\"));\n        System.out.format(\"  City: %s%n\". s.get(\"City\"));\n        System.out.format(\"  County: %s%n\". s.get(\"County\"));\n        System.out.format(\"  PostCode: %s%n\". s.get(\"PostCode\"));\n        System.out.format(\"  Tel: %s%n\". s.get(\"PostCode\", \"N/A\"));\n    });\n    \n```\n\n## Preferences Backing Store\n\nAn optional [java.util.prefs.Preferences](https://docs.oracle.com/javase/8/docs/api/java/util/prefs/Preferences.html) implementation is available, which will\nreplace the default when added to the CLASSPATH. \n\nInstead 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.\n\nJust add the following dependency to your project's `pom.xml`.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.sshtools\u003c/groupId\u003e\n    \u003cartifactId\u003ejini-prefs\u003c/artifactId\u003e\n    \u003cversion\u003e0.4.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Usage\n\nThe `.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()`. \n\n```java\n\tvar prefs = Preferences.userRoot();\n\tprefs.put(\"akey\", \"Some Value\");\n```\n\nIt is possible to configure INI based preferences if you do so *before* the first access to\na `Preferences` node.\n\n```java\n\tvar bldr = new INIStoreBuilder().\n\t\t\t\twithScope(Scope.USER).\n\t\t\t\twithoutAutoFlush().\n\t\t\t\twithName(\"jini.test\").\n\t\t\t\twithPath(Files.createTempDirectory(\"jinitestdir\"));\n\t\t\t\t\n\tvar store = bldr.build();\n\tINIPreferences.configure(store);\t\t\n\tvar prefs = Preferences.userRoot();\n\tprefs.put(\"akey\", \"Some Value\");\n\tSystem.out.println(prefs.get(\"akey\", \"No value!\"));\n\t\n\t// ...\n```\n\nYou 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.\n\n```java\n\tvar bldr = new INIStoreBuilder().\n\t\t\t\twithScope(Scope.USER).\n\t\t\t\twithoutAutoFlush().\n\t\t\t\twithName(\"jini.test\").\n\t\t\t\twithPath(Files.createTempDirectory(\"jinitestdir\"));\n\t\t\t\t\n\ttry (var store = bldr.build()) {\n\t\tvar prefs = store.root();\n\t\tprefs.put(\"akey\", \"Some Value\");\n\t\tSystem.out.println(prefs.get(\"akey\", \"No value!\"));\n\t}\t\t\t\n```\n\nNote that a `store` is scoped, and should be closed when finished with.\n\n## Object Serialization and De-serialization\n\nThe 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.\n\nJust add the following dependency to your project's `pom.xml`.\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.sshtools\u003c/groupId\u003e\n    \u003cartifactId\u003ejini-serialization\u003c/artifactId\u003e\n    \u003cversion\u003e0.4.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Limitations\n\n * Collections and Maps must provide an `itemType` for their values for de-serialization to work. \n * Maps currently only support `String` keys, and values can only be primitive types. \n * The entire object graph is serialized (subject to include / exclude rules). There is no support for object references yet.\n\n### Usage\n\nSerialization 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.\n\nSometimes 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.\n\nModern 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. \n\n#### Serialization - Writing An Object\n\nThe 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`). \n\nAlternatively, you can use one of several convenience methods.\n\n * `INISerializer.toINI(Object object)` creates an `INI` from an `Object`. \n * `INISerializer.write(Object object, String path)` and `INISerializer.writer(Object object, Path path)` writes an object to a file.\n * `INISerializer.write(Object object, Writer writer)` writes the contents of the `INI` to a `Writer`. \n\nIf 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.\n\n```java\nclass Address {\n\tString line1 = \"99 Some Road\";\n\tString city = \"Nodnol\";\n}\n\nclass Person {\n\tString name = \"Mr Crumbly\";\n\tint age = 99;\n\tAddress address = new Address();\n\tString[] telephones = new String[] { \"123 456789\", \"987 654321\" };\n}\n\nINISerialize.write(new Person(), \"crumbly.ini\");\n```\n\nIf 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.\n\nThis 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 `key`s used in the INI file and other behavioural changes. \n\n#### De-serialization - Reading An Object\n\nThe 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`). \n\nAlternatively, you can use one of several convenience methods.\n\n * `INIDeserializer.fromINI(INI ini, MyObject.class)` creates a `MyObject` from an `INI`. \n * `INIDeserializer.read(String path, MyObject.class)` and `INISerializer.read(Path path, MyObject.class)` reads an object from a file.\n * `INISerializer.read(Reader reader, MyObject.class)` reader INI contents from a `Reader` and construct a `MyObject` from it. \n\n```java\nclass Address {\n\tString line1;\n\tString city;\n}\n\nclass Person {\n\tString name;\n\tint age;\n\tAddress address;\n\tString[] telephones;\n}\n\nvar person = INIDeserializer.read(\"crumbly.ini\", Person.class);\n```\n\nThe 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.\n\nDe-serialization imposes some additional requirements and restrictions too.\n\n * All types that have no special handling, must have empty public constructors.\n * 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.\n\n## Changes\n\n### 0.4.0\n * New `jini-serialization` module for object serialization and deserialization. See `INISerializer` and `INIDeserializer`.\n * `duplicateSectionAction` default is now `DuplicateAction.APPEND`. This means multiple sections with the same name by default will now all be available.\n * Multi-line string support for keys and values. Wrap strings in supported quote character, e.g `''' ..... '''` in a manner similar to Java. \n\n### 0.3.3\n\n * Fixes for reloading.\n * Removal of value events not fired by `INISet`.\n * 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`.\n\n\n### 0.3.2\n\n * More work on `INISchema`, can now wrap an `INI` to provide a proxied instance that guarantees correctness.\n * Created `jini-config` module that can be used to provide a monitored INI based configuration system for applications.\n\n### 0.3.1\n\n * Added string interpolation features.\n * Renamed some getters to shorten them, and also remove an ambiguity with `getAllOr()`.\n * Separated values separator wasn't being ignored in quotes.\n\n### 0.3.0\n\n * Added `EscapeMode` for `INIWriter` and `INIReader` that allows altering behaviour of escaping.\n * `java.util.prefs.Preferences` implementation\n\n### 0.2.5\n\n * Added `Section.readOnly()` and `Document.readOnly()` to create a read-only facades.\n * Added `INI.empty()` that returns a static empty and read-only document. \n * Events are fired from all parent `Section` or `INI` on value or child section change. \n * Added `obtainSetion()` that gets or creates sections given a path.\n\n### 0.2.4\n\n * Added `onValueUpdate()` and `onSectionUpdate()` to listen for changes to document.\n\n### 0.2.3\n\n * Added `keys()` method.\n * Made `containsSection(String)` now support testing for nested sections by changing the signature to `containsSection(String...)`.\n\n## Credits\n\nUses [LinkedCaseInsensitiveMap](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/LinkedCaseInsensitiveMap.html) from Spring Utilities.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshtools%2Fjini","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsshtools%2Fjini","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsshtools%2Fjini/lists"}