{"id":18892402,"url":"https://github.com/hf/immu","last_synced_at":"2025-10-17T18:07:29.729Z","repository":{"id":143824600,"uuid":"78173109","full_name":"hf/immu","owner":"hf","description":"Never write an immutable object by hand.","archived":false,"fork":false,"pushed_at":"2017-01-28T14:53:58.000Z","size":128,"stargazers_count":4,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-12-31T06:29:54.694Z","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":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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":"2017-01-06T04:16:41.000Z","updated_at":"2019-07-11T23:32:20.000Z","dependencies_parsed_at":null,"dependency_job_id":"c33d8609-a021-4c17-879e-f420a7a49ec2","html_url":"https://github.com/hf/immu","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hf%2Fimmu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hf%2Fimmu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hf%2Fimmu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hf%2Fimmu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hf","download_url":"https://codeload.github.com/hf/immu/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":239868619,"owners_count":19710474,"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-08T08:02:02.545Z","updated_at":"2025-10-17T18:07:24.680Z","avatar_url":"https://github.com/hf.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Immu\n\n[![Build Status](https://travis-ci.org/hf/immu.svg?branch=master)](https://travis-ci.org/hf/immu) [![codecov](https://codecov.io/gh/hf/immu/branch/master/graph/badge.svg)](https://codecov.io/gh/hf/immu)\n[ ![immu-compiler](https://api.bintray.com/packages/stojan/java/immu-compiler/images/download.svg) ](https://bintray.com/stojan/java/immu-compiler/_latestVersion) [ ![immu-annotations](https://api.bintray.com/packages/stojan/java/immu-annotations/images/download.svg) ](https://bintray.com/stojan/java/immu-annotations/_latestVersion)\n\nImmu is a small annotation processor that will allow you to never write\nanother builder and immutable object by hand.\n\nLife is too short to properly code and test data-holding, immutable objects,\nand the boilerplate they come with: builders, hash codes, equality, meaningful\ntoStrings, nullability, ...\n\nSo, one night I sat down and coded Immu. Its point is to be minimal and do\none thing well.\n\n## Quick Start\n\nAdd this to your `build.gradle`\n\n```groovy\nrepositories {\n    maven {\n        url  \"http://dl.bintray.com/stojan/java\" \n    }\n}\n\ndependencies {\n  compile 'me.stojan.immu:immu-annotations:LATEST_VERSION'\n  apt     'me.stojan.immu:immu-compiler:LATEST_VERSION'\n}\n```\n\nSubstitute `apt` for your favorite Gradle \n[annotation processor plugin](https://github.com/tbroyer/gradle-apt-plugin).\n\nAndroid's newer build tools support `annotationProcessor` instead of `apt` from \n[android-apt](https://bitbucket.org/hvisser/android-apt).\n\nJDK 8 or newer is required for `javac` although the generated source code will\nbe Java 1.5 compatible. But please, switch to the latest Java on your \nplatform.\n\nUsing Maven? Figure it out. :)\n\n## How?\n\nOK, so Immu has three annotations `@Immu`, `@SuperImmu` and `@Required`. You\ncan use these to describe a very strict immutable object structure.\n\n`@Immu` and `@SuperImmu` can only be used on interfaces that only have methods\nof the form `ReturnType name()`. Methods with parameters, type variables,\nor exceptions are not allowed and the processor will let you know.\n\n`@Immu` gives you:\n\n 1. A package-protected, final implementation of the `@Immu` interface.\n 2. A public, final, well-formed builder for the `@Immu` interface.\n\nInterfaces annotated with `@Immu` or `@SuperImmu` may extend other interfaces\nbut they either have to be themselves annotated, or they will have to be\nempty. The processor will let you know about this, too.\n\n`@SuperImmu` is like a super-class for `@Immu`. You can use `@SuperImmu`\ninterfaces to encapsulate data that is shared between other `@Immu`-s, but\nthat data is not necessarily standalone.\n\n`@Required` is for properties only. If a property is annotated with\n`@Required` it will have to be provided in builders and constructors. For\ndeclared types (things that are objects, basically) this means that they\nmust not be `null`, for primitive types (`int`, `byte`, ...) this means that\nthey will have to be provided at least once before building.\n\nThat's basically it. No bullshit.\n\n### Example\n\n```java\n@SuperImmu\npublic interface Animal {\n    @Required String name();\n    int eyes();\n}\n\n@SuperImmu\npublic interface LeggedAnimal extends Animal {\n    int legs();\n}\n\n@Immu\npublic interface Tweeter extends LeggedAnimal {\n    String twitter();\n}\n\n@Immu\npublic interface Octopus extends LeggedAnimal {\n    @Required String favoriteOcean();\n}\n\n...\n\nOctopus octocat = OctopusBuilder\n    .create()\n    .eyes(2)\n    .legs(8)\n    .favoriteOcean(\"Atlantic Ocean\")\n    .build(); // @Required will be checked here\n\nTweeter chirpy = TweeterBuilder\n    .create()\n    .twitter(/* may be null */ \"@archillect\")\n    .eyes(2)\n    .legs(2)\n    .build();\n\nOctopus octocatsBro = OctopusBuilder\n    .from(octocat)\n    .name(\"Octocat's Bro\")\n    .build();\n```\n\n## API freeze\n\nNo matter how much the implementation of the compiler (annotation processor) \nchanges in the future, these are the APIs that will **always** be exposed by \nImmu (versions above `0.0.X`).\n\nBuilders will always have the suffix `Builder` from the interface name.\n\n```java\n// a Builder for an @Immu annotated interface\npublic final class Builder {\n  // creates a new builder and initializes all builder values from the provided immu\n  public static Builder from(Immu immu) { /* ... */ }\n  \n  // creates a new empty builder\n  public static Builder create() { /* ... */ }\n  \n  // property setter for reference types / declared types\n  public Builder propertyName(PropertyType value) { /* ... */ }\n  \n  // property setter for primitive types\n  public Builder propertyName(primitive_type value) { /* ... */ }\n  \n  // builds a new Immu object from the values provided here, if there are any\n  // @Required properties, this method will throw a ValueNotProvidedException\n  // that will explain which property was not provided\n  public Object build() { /* ... */ }\n}\n```\n\nImplementations of the immutable interface will always be package-protected,\nand will always be prefixed with `Immutable` onto the interface name.\n\n```java\n// the immutable object implementation class\n/* package-protected */ final class Immutable implements TheImmuInterface, Immutable {\n  \n  // constructor for all values, will throw a ValueNotProvidedException if a\n  // reference / declared property has been annotated with @Required and was\n  // provided a value that is null\n  Immutable(/* ... */) { /* ... */ }\n  \n  // getter for primitive type properties\n  @Override public primitive_type propertyPrimitive() { /* ... */ }\n  \n  // getter for reference / declared properties\n  @Override public PropertyType propertyReference() { /* ... */ }\n  \n  // a standards compliant hash code, that is an XOR of all property values\n  // and most importantly, the starting value is \n  // TheImmuInterface.class.getCanonicalName().hashCode()\n  @Override public int hashCode() { /* ... */ }\n  \n  // generates a developer-readable toString with a very specific format\n  @Override public String toString() { /* ... */ }\n  \n  // an equals implementation that does equality checks on the TheImmuInterface, \n  // and not on the generated class\n  @Override public boolean equals(Object object) { /* ... */ }\n  \n  // will optimistically clear any cached values, like a cached toString() value\n  @Override void clear() { /* ... */ }\n}\n```\n\nOther features may be present, per release. However, these features will always\nbe available and will **never** change. \n\n### Why the `hashCode`?\n\n`@Immu` interfaces have a very strict structure that expresses: instances\nof this class with these properties, will forever be immutable and cannot\nbe changed. However, being an interface, the instances may be different.\nSay that they are different: then both of them should express the \nsame semantics as `@Immu`. In order to have those two differing instance\nimplementations have the same `hashCode`, they must use a common \nstarter value. `0` a choice, but it's too generic and will probably\nhave *worse* performance in hashed collections than a non-generic \nstarter value. It is natural that this common value should be derived\nfrom the class. That's why we are using the hash value of the interface's\ncanonical name as the starting value. We are not using the class-object's\n`hashCode` since that *may* be bound to the `ClassLoader` that owns the\nclass object.\n\n`hashCode` values may be cached.\n\n### About `toString`\n\nImmu will generate a developer-friendly `toString()` implementation with the\nfollowing structure:\n\n```text\nImmuInterface@0abcdefa{ propertyName = @null, propertyName = \u003cvalue\u003e@0abcdefa }\n```\n\nWhere `propertyName` is the name of the property. `@null` is a special value \nthat designates that the property had a value of `null`. \n\n`@0abcdefa` is an 8-character, hex value of `System.identityHashCode()` of the \nobject in question. \n\n`\u003cvalue\u003e@0abcdefa` is the value for the property as well as the \n`System.identityHashCode()` of the object in question. The location information \nwill not be present for primitive types (`int`, `byte`, ...).\n\n`toString` values are cached. They can be cleared by accessing the \n`immu.Immutable#clear()` method, but this is only recommended for \nmemory-constrained platforms such as Android, and even then it may not be \nnecessary (depending on how many objects you have).\n\n## Building, Contributing\n\nBuilding requires JDK8. It is recommended you use versions *above* \n`1.8.0_31` since there have been issues with hanging compiler tests including\ntroubles with inferring types in the code that uses the Java 8 stream APIs.\n\nThe tests exercise the full processor, and not every method is directly tested \nsince that would be impossible without forking `javac`. Therefore, the \nprocessor uses the excellent `com.google.testing.compile:compile-testing` \nlibrary from Google.\n\n## License\n\nCopyright \u0026copy; 2017 Stojan Dimitrovski\n\nLicensed under the MIT X11 License. See `LICENSE.txt` for the full text.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhf%2Fimmu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhf%2Fimmu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhf%2Fimmu/lists"}