{"id":21361982,"url":"https://github.com/kh-bd/lens4j","last_synced_at":"2025-08-11T17:44:10.114Z","repository":{"id":37941133,"uuid":"400829717","full_name":"kh-bd/lens4j","owner":"kh-bd","description":null,"archived":false,"fork":false,"pushed_at":"2024-02-28T18:31:01.000Z","size":636,"stargazers_count":15,"open_issues_count":6,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-29T21:03:01.902Z","etag":null,"topics":["java","library"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kh-bd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2021-08-28T15:43:31.000Z","updated_at":"2025-04-09T10:19:11.000Z","dependencies_parsed_at":"2024-02-28T19:49:25.798Z","dependency_job_id":null,"html_url":"https://github.com/kh-bd/lens4j","commit_stats":null,"previous_names":["khadanovichsergey/lens4j"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/kh-bd/lens4j","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kh-bd%2Flens4j","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kh-bd%2Flens4j/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kh-bd%2Flens4j/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kh-bd%2Flens4j/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kh-bd","download_url":"https://codeload.github.com/kh-bd/lens4j/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kh-bd%2Flens4j/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262667247,"owners_count":23345524,"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":["java","library"],"created_at":"2024-11-22T06:12:57.930Z","updated_at":"2025-06-29T21:03:07.713Z","avatar_url":"https://github.com/kh-bd.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lens4j is a lightweight lens library for Java\n\n[![CI](https://github.com/kh-bd/lens4j/actions/workflows/main-tests.yml/badge.svg)](https://github.com/kh-bd/lens4j/actions/workflows/main-tests.yml)\n\n# What is a lens?\n\n***\n\nTo understand what lens is and where to use it, consider following example:  \nOur domain contains several entities: `Payment`, `Account` and `Currency`.\n\n```java\nclass Currency {\n    String code;\n\n    // accessors here\n}\n\nclass Account {\n    String accountNumber;\n    Currency currency;\n\n    // accessors here\n}\n\nclass Payment {\n    Double amount;\n    Account payerAccount;\n    Account receiverAccount;\n\n    // accessors here\n}\n```\n\nWe have to implement a function to validate that payer account currency is not empty. A usual way to do that would look\nlike this:\n\n```java\nclass PaymentValidator {\n\n    boolean isPayerAccountCurrencyNotEmpty(Payment payment) {\n        if (payment == null) {\n            return false;\n        }\n\n        Account payerAccount = payment.getPayerAccount();\n        if (payerAccount == null) {\n            return false;\n        }\n\n        Currency currency = payerAccount.getCurrency();\n        if (currency == null) {\n            return false;\n        }\n\n        return currency.getCode() != null;\n    }\n}\n```\n\nSuch implementation has several drawbacks:\n\n* It is too easy to do it wrong. We have to check each property before dereference it.\n* The biggest part of the method is boilerplate and only last line is a real logic.\n\nLens is a simple functional interface with method `get`, which can extract some value of type `P` from instance of\ntype `O`.\n\n```java\ninterface ReadLens\u003cO, P\u003e {\n    P get(O object);\n}\n```\n\nSuppose we have a instance of type `ReadLens\u003cPayment, String\u003e` which encapsulate currency code extracting logic. Having\nsuch lens instance we can reimplement our function. I will look like this:\n\n```java\nclass PaymentValidator {\n\n    static final ReadLens\u003cPayment, String\u003e PAYER_ACCOUNT_CODE_LENS = ...;\n\n    boolean isPayerAccountCurrencyNotEmpty(Payment payment) {\n        return PAYER_ACCOUNT_CODE_LENS.get(payment) != null;\n    }\n}\n```\n\n# How to construct an instance of lens?\n\n***\nWe can construct lenses manually with combinator functions or use annotation processor to construct them at compile\ntime.\n\n## Constructing lenses manually\n\nThere are several combinator functions to combine lenses with each other: `endThenF` and `composeF`. These functions\nhave analogical semantic as `Function#endThen` and `Function#compose`. To construct `PAYER_ACCOUNT_CODE_LENS` we can do\nthe following:\n\n```java\nclass PaymentValidator {\n\n    static final ReadLens\u003cPayment, String\u003e PAYER_ACCOUNT_CODE_LENS =\n            Lenses.readLens(Payment::getPayerAccount)\n                    .andThenF(Accout::getCurrency)\n                    .andThenF(Currency::getCode);\n}\n```\n\nSometimes it is necessary to construct lenses by hand but most of the time we can do it automatically, at compile time.\n\n## Constructing lenses at compile time\n\nTo construct the same lens instance we can annotate our `Payment` class with\n`GenLenses` annotations.\n\n```java\n\n@GenLenses(lenses = @Lens(path = \"payerAccount.currency.code\"))\nclass Payment {\n    Double amount;\n    Account payerAccount;\n    Account receiverAccount;\n}\n```\n\n`PaymentLenses` factory class will be generated at compile time. It will look like this:\n\n```java\nfinal class PaymentLenses {\n\n    public static final ReadLens\u003cPayment, String\u003e PAYER_ACCOUNT_CODE_LENS =\n            Lenses.readLens(Payment::getPayerAccount)\n                    .andThen(Lenses.readLens(Accout::getCurrency))\n                    .andThen(Lenses.readLens(Currency::getCode));\n}\n```\n\nNow, we can use `PaymentLenses#PAYER_ACCOUNT_CODE_LENS` in our code\n\n```java\nclass PaymentValidator {\n\n    boolean isPayerAccountCurrencyNotEmpty(Payment payment) {\n        return PaymentLenses.PAYER_ACCOUNT_CODE_LENS.get(payment) != null;\n    }\n}\n```\n\n# Using lens4j\n\n***\n\n## Maven\n\nFor maven-based projects, add the following to your `pom.xml` file:\n\n```xml\n\u003c!-- version property --\u003e\n\u003cproperties\u003e\n    \u003clens4j.version\u003e${LATEST}\u003c/lens4j.version\u003e\n\u003c/properties\u003e\n```\n\n```xml\n\u003c!-- dependency for core api --\u003e\n\u003cdependencies\u003e\n    \u003cdependency\u003e\n        \u003cgroupId\u003edev.khbd.lens4j\u003c/groupId\u003e\n        \u003cartifactId\u003elens4j-core\u003c/artifactId\u003e\n        \u003cversion\u003e${lens4j.version}\u003c/version\u003e\n    \u003c/dependency\u003e\n\u003c/dependencies\u003e\n```\n\n```xml\n\u003c!-- processor configuration --\u003e\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n            \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n            \u003cconfiguration\u003e\n                \u003cannotationProcessorPaths\u003e\n                    \u003cpath\u003e\n                        \u003cgroupId\u003edev.khbd.lens4j\u003c/groupId\u003e\n                        \u003cartifactId\u003elens4j-processor\u003c/artifactId\u003e\n                        \u003cversion\u003e${lens4j.version}\u003c/version\u003e\n                    \u003c/path\u003e\n                \u003c/annotationProcessorPaths\u003e\n            \u003c/configuration\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\n## Versions\n\nWe are going to support separate version for each LTS release as long as that release is supported.\nIn the following table, you can find the latest lens4j version for each supported java version.\n\n| Java\u003cbr/\u003e version | Latest release                                                                                                                                                                                   |\n|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `1.8`             | [![Maven jdk1.8](https://img.shields.io/maven-central/v/dev.khbd.lens4j/lens4j?color=brightgreen\u0026versionSuffix=_jre1.8)](https://mvnrepository.com/artifact/dev.khbd.lens4j/lens4j/2.0.0_jre1.8) |\n| `11`              | [![Maven jdk11](https://img.shields.io/maven-central/v/dev.khbd.lens4j/lens4j?color=brightgreen\u0026versionSuffix=_jre11)](https://mvnrepository.com/artifact/dev.khbd.lens4j/lens4j/2.0.0_jre11)    |\n| `17`              | [![Maven jdk17](https://img.shields.io/maven-central/v/dev.khbd.lens4j/lens4j?color=brightgreen\u0026versionSuffix=_jre17)](https://mvnrepository.com/artifact/dev.khbd.lens4j/lens4j/2.0.0_jre17)    |\n| `21`               | [![Maven jdk21](https://img.shields.io/maven-central/v/dev.khbd.lens4j/lens4j?color=brightgreen\u0026versionSuffix=_jre21)](https://mvnrepository.com/artifact/dev.khbd.lens4j/lens4j/2.0.0_jre21)    |\n\n# Generating inlined lenses (experimental)\n\nLenses can be generated in different way, so called, inlined way.\nInlined generation is experimental and disabled by default.\nTo enable inlined generation set option `lenses.generate.inlined` to `true`.\n\nFor maven-based projects, add the following:\n\n```xml\n\u003c!-- processor configuration --\u003e\n\u003cbuild\u003e\n    \u003cplugins\u003e\n        \u003cplugin\u003e\n            \u003cgroupId\u003eorg.apache.maven.plugins\u003c/groupId\u003e\n            \u003cartifactId\u003emaven-compiler-plugin\u003c/artifactId\u003e\n            \u003cconfiguration\u003e\n                \u003cannotationProcessorPaths\u003e\n                    \u003cpath\u003e\n                        \u003cgroupId\u003edev.khbd.lens4j\u003c/groupId\u003e\n                        \u003cartifactId\u003elens4j-processor\u003c/artifactId\u003e\n                        \u003cversion\u003e${lens4j.version}\u003c/version\u003e\n                    \u003c/path\u003e\n                \u003c/annotationProcessorPaths\u003e\n                \u003ccompilerArgs\u003e\n                    \u003ccompilerArg\u003e-Alenses.generate.inlined=true\u003c/compilerArg\u003e\n                \u003c/compilerArgs\u003e\n            \u003c/configuration\u003e\n        \u003c/plugin\u003e\n    \u003c/plugins\u003e\n\u003c/build\u003e\n```\n\nInlined lenses are look like manually written code, so instead of such code\n\n```java\nfinal class PaymentLenses {\n\n    public static final ReadLens\u003cPayment, String\u003e PAYER_ACCOUNT_CODE_LENS =\n            Lenses.readLens(Payment::getPayerAccount)\n                    .andThen(Lenses.readLens(Accout::getCurrency))\n                    .andThen(Lenses.readLens(Currency::getCode));\n}\n```\n\nsomething like that will be generated:\n\n```java\n\nfinal class PaymentLenses {\n\n    public static final ReadLens\u003cPayment, String\u003e PAYER_ACCOUNT_CODE_LENS = new ReadLens\u003c\u003e() {\n        @Override\n        String get(Payment object) {\n            if (object == null) {\n                return null;\n            }\n            Account payerAccount = payment.getPayerAccount();\n            if (payerAccount == null) {\n                return null;\n            }\n            Currency currency = payerAccount.getCurrency();\n            if (currency == null) {\n                return null;\n            }\n            return currency.getCode();\n        }\n    };\n}\n```\n\nSee comparison between inlined and not-inlined\nlenses [here](https://jmh.morethan.io/?sources=https://raw.githubusercontent.com/kh-bd/lens4j/main/readme/benchmark/jmh_v_017_result.json,https://raw.githubusercontent.com/kh-bd/lens4j/main/readme/benchmark/jmh_latest_result.json).\n\n# Intellij IDEA support\n\n***\n\nTo add lens4j support to Intellij,\ninstall [Lens4j intellij plugin](https://github.com/kh-bd/lens4j-intellij-plugin)\n\n# Benchmarks\n\n***\n\nAll benchmarks were run on:\n\n- Machine: MacBook Pro 2015\n- Processor: 2.2 GHz Quad-Core Intel Core i7\n- Memory: 16 GB 1600MHz DDR3\n\nSee latest benchmark\nresult [here](https://jmh.morethan.io/?source=https://raw.githubusercontent.com/kh-bd/lens4j/main/readme/benchmark/jmh_latest_result.json)\n.\n\nAs you can see, generated lenses are as fast as manually written code, but lenses which were build\nmanually with `Lenses.compose` api are several times slower than generated ones.\nLenses' performance is a subject for father optimization. Any help is welcome :)\n\n## How to run benchmarks on your own machine?\n\n***\n\nTo run benchmarks do several steps:\n\n- pull project to your machine\n- run from root directory `mvn package -Pbenchmark`\n- go to `lens4j-benchmark/target` directory. `lens4j-benchmark-${version}-jar-with-dependencies.jar` should be generated\n- run\n  command `java -cp ./lens4j-benchmark-${version}-jar-with-dependencies.jar dev.khbd.lens4j.benchmark.BenchmarkRunner -rf json`\n- `jmh-result.json` report should be generated\n- view it through [jmh visualizer](https://jmh.morethan.io/)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkh-bd%2Flens4j","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkh-bd%2Flens4j","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkh-bd%2Flens4j/lists"}