{"id":19816279,"url":"https://github.com/cronn/reflection-util","last_synced_at":"2025-04-05T18:09:37.410Z","repository":{"id":39906931,"uuid":"109373538","full_name":"cronn/reflection-util","owner":"cronn","description":"Java Reflection Utility Classes","archived":false,"fork":false,"pushed_at":"2024-12-29T14:04:08.000Z","size":909,"stargazers_count":61,"open_issues_count":2,"forks_count":9,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-29T17:11:15.571Z","etag":null,"topics":["java","method-reference","reflection"],"latest_commit_sha":null,"homepage":"","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/cronn.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":"2017-11-03T08:52:46.000Z","updated_at":"2024-12-29T14:04:11.000Z","dependencies_parsed_at":"2024-06-26T11:58:58.666Z","dependency_job_id":"f2fe438d-2014-4fa9-a25b-11834c4e51c1","html_url":"https://github.com/cronn/reflection-util","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronn%2Freflection-util","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronn%2Freflection-util/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronn%2Freflection-util/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cronn%2Freflection-util/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cronn","download_url":"https://codeload.github.com/cronn/reflection-util/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247378149,"owners_count":20929297,"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","method-reference","reflection"],"created_at":"2024-11-12T10:09:03.905Z","updated_at":"2025-04-05T18:09:37.391Z","avatar_url":"https://github.com/cronn.png","language":"Java","readme":"[![CI](https://github.com/cronn/reflection-util/workflows/CI/badge.svg)](https://github.com/cronn/reflection-util/actions)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/de.cronn/reflection-util/badge.svg)](http://maven-badges.herokuapp.com/maven-central/de.cronn/reflection-util)\n[![Apache 2.0](https://img.shields.io/github/license/cronn/reflection-util.svg)](http://www.apache.org/licenses/LICENSE-2.0)\n[![codecov](https://codecov.io/gh/cronn/reflection-util/branch/main/graph/badge.svg?token=KD1WJK5ZFK)](https://codecov.io/gh/cronn/reflection-util)\n[![Valid Gradle Wrapper](https://github.com/cronn/reflection-util/workflows/Validate%20Gradle%20Wrapper/badge.svg)](https://github.com/cronn/reflection-util/actions/workflows/gradle-wrapper-validation.yml)\n\n# cronn reflection-util #\n\nUtility classes that simplify common use cases of Java Reflection.\n\nWe ship the utility classes `PropertyUtils`, `ClassUtils`, and `ImmutableProxy` that are described in the following sections.\n\n## PropertyUtils ##\n\nReplacement for `org.apache.commons.beanutils.PropertyUtils` with deterministic behaviour\nand support to retrieve [PropertyDescriptors][property-descriptor] via Java 8 method references.\n\nExample:\n\n```java\nclass MyPojo\n{\n    private Long number;\n    // getters and setters\n}\n```\n\n```java\nPropertyDescriptor numberProperty = PropertyUtils.getPropertyDescriptor(MyPojo.class, MyPojo::getNumber);\nMyPojo pojo = new MyPojo();\nPropertyUtils.write(pojo, numberProperty, 12345L);\nLong number = PropertyUtils.read(pojo, numberProperty);\nassertEquals(12345L, number);\n```\n\n### Support for records ###\n\nRecords of Java 14 and newer are also supported by `PropertyUtils`.\n\nExample:\n\n```java\nrecord Point(int x, int y) {}\n\nString propertyName = PropertyUtils.getPropertyName(Point.class, Point::x);\nassertEquals(\"x\", propertyName);\n```\n\n## ClassUtils ##\n\n### Obtaining the method name\n\n`ClassUtils` can be used to obtain the method name in a type-safe way. We currently support getter-like methods as well as methods that return `void`.\n\n#### Example\n\n```java\ninterface MyInterface\n{\n    void doSomething();\n\n    int getSomething();\n}\n```\n\n```java\nString methodName = ClassUtils.getMethodName(MyInterface.class, MyInterface::doSomething);\nassertEquals(\"doSomething\", methodName);\n```\n\n```java\nString methodName = ClassUtils.getMethodName(MyInterface.class, MyInterface::getSomething);\nassertEquals(\"getSomething\", methodName);\n```\n\nNote: `ClassUtils.getMethodName` does not support **static** methods.\n\n### Getting the \"real\" class of a proxy\n\n`ClassUtils.getRealClass(…)` can be used to obtain the underlying \"real\" class of a proxy.\nIts typical use-case is as drop-in replacement for `object.getClass()` when `object` is _potentially_ a proxy, for example\nwhen working with JPA/Hibernate.\n\nWe currently support [Java][java-proxy], Byte Buddy, Hibernate and cglib/javassist proxies.\n\n#### Example\n\n```java\nClass\u003c?\u003e[] interfaces = { MyInterface.class };\nObject proxy = Proxy.newProxyInstance(getClass().getClassLoader(), interfaces, (p, method, args) -\u003e null);\nassertEquals(MyInterface.class, ClassUtils.getRealClass(proxy));\n```\n\n## ImmutableProxy ##\n\nIt’s sometimes desirable to make objects immutable to prevent programming mistakes,\nsuch as the accidental modification of an object that is passed to another method.\n\nIn some cases the class itself cannot be made immutable. For example, because\nmutability is required by the JPA provider, the JSON serialization library,\nor the class is not owned by you.\n\nCreating deep clones might come to the rescue, however, the negative\nperformance impact might not be acceptable and you cannot detect that the clone\nis accidentally modified.\n\nIn such cases, `ImmutableProxy` can be used to create a deep but lightweight\nread-only view of a [POJO](pojo) that follows the [JavaBean\nconventions](java-bean-conventions).\nInvocation of getters and read-only methods is allowed but other methods such\nas setters are rejected by default.\n\nExample:\n\n```java\nclass MyPojo\n{\n    private String name;\n    private List\u003cMyPojo\u003e children = new ArrayList\u003c\u003e();\n    // getters and setters\n}\n```\n```java\nMyPojo original = new MyPojo();\noriginal.setName(\"original\");\nMyPojo immutableProxy = ImmutableProxy.create(original);\nimmutableProxy.getName()    // ✔ returns \"original\"\nimmutableProxy.setName(\"…\") // ✖ throws UnsupportedOperationException\n```\n\nBy default, `ImmutableProxy` wraps the return value of a getter in a immutable proxy:\n\n```java\noriginal.getChildren().add(new MyPojo(\"child\"));\nimmutableProxy.getChildren().size()  // ✔ returns 1\n\nMyPojo firstChild = immutableProxy.getChildren().get(0);\nfirstChild.getName()    // ✔ returns \"child\"\nfirstChild.setName(\"…\") // ✖ throws UnsupportedOperationException\n\nimmutableProxy.getChildren().clear() // ✖ throws UnsupportedOperationException\n```\n\nSome methods need to be annotated with `@ReadOnly`\nif `ImmutableProxy` incorrectly considers the method as mutating:\n\n```java\nclass MyPojo\n{\n    List\u003cString\u003e elements;\n\n    @ReadOnly\n    int size() {\n        return elements.size();\n    }\n}\n```\n\nAs a final word of warning, please note that `ImmutableProxy` follows a best-effort approach but cannot _guarantee_ to detect all possible modifications.\nFor example, it cannot detect that a getter actually modifies the state as a side-effect.\n\n### Support for records ###\n\nRecords of Java 14 and newer are also supported by `ImmutableProxy`.\n\nExample:\n\n```java\nrecord Point(int x, int y) {}\n\nclass MyPojo\n{\n    private String name;\n    private Point point;\n    // getters and setters\n}\n\nMyPojo original = new MyPojo();\noriginal.setPoint(new Point(1, 2));\n\nMyPojo immutableProxy = ImmutableProxy.create(original);\n\n// The Point record is detected to be (deeply) immutable, so ImmutableProxy can directly return the value\nassertSame(immutableProxy.getPoint(), original.getPoint());\n```\n\nIf the record class is (potentially) not deeply immutable, ImmutableProxy can be told to clone the records on-the-fly.\n\nExample:\n\n```java\nrecord WrappedList(List\u003cString\u003e values) {}\n\nclass MyPojo\n{\n    private WrappedList list;\n    // getters and setters\n}\n\nMyPojo original = new MyPojo();\noriginal.setList(new WrappedList(List.of(\"a\", \"b\", \"c\")));\n\nMyPojo immutableProxy = ImmutableProxy.create(original, ImmutableProxyOption.ALLOW_CLONING_RECORDS);\nimmutableProxy.getList().values().get(0)   // ✔ returns \"a\"\nimmutableProxy.getList().values().clear()  // ✖ throws UnsupportedOperationException\n```\n\n### JMH Benchmark\n\nTo get a rough idea about the performance impact of the `ImmutableProxy` method interception,\nwe include a JMH benchmark: [ImmutableProxyBenchmark.java](src/test/java/de/cronn/reflection/util/immutable/ImmutableProxyBenchmark.java).\n\nThe benchmark compares direct method invocation of simple fields vs. method invocations through the immutable proxy.\nBelow you can find one of the benchmark results as conducted on a Thinkpad T480s with an Intel i7-8650U CPU running on Linux `5.16.2`.\n\n```\n# JMH version: 1.34\n# VM version: JDK 17.0.1, OpenJDK 64-Bit Server VM, 17.0.1+12\n\n[…]\n\n# Run complete. Total time: 00:41:57\n\nREMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on\nwhy the numbers are the way they are. […]\n\nBenchmark                                 Mode  Cnt      Score       Error  Units\nImmutableProxyBenchmark.unproxiedEquals  thrpt    3  47106,090 ± 22356,782  ops/s\nImmutableProxyBenchmark.proxiedEquals    thrpt    3  11537,742 ±  4097,233  ops/s\n```\n\nIt shows that the invocation of the `equals()` method is about 4-5 times slower when routed through the `ImmutableProxy`.\nHowever, please note that the benchmark itself runs an inner loop with 10000 cycles.\nThis actually gives us 10000 * 11537,742 ≈ 115 million invocations per second!\n\n## Usage ##\nAdd the following Maven dependency to your project:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ede.cronn\u003c/groupId\u003e\n    \u003cartifactId\u003ereflection-util\u003c/artifactId\u003e\n    \u003cversion\u003e2.17.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Requirements ##\n\n- Java 17+\n\n## Related Work ##\n\n- [Apache Commons BeanUtils](apache-commons-beanutils)\n- [Jodd Methref](jodd-methref)\n- `ImmutableProxy`: [Immutator](https://github.com/verhas/immutator)\n\n[apache-commons-beanutils]: http://commons.apache.org/proper/commons-beanutils/\n[property-descriptor]: https://docs.oracle.com/javase/10/docs/api/java/beans/PropertyDescriptor.html\n[jodd-methref]: https://jodd.org/ref/methref.html\n[pojo]: https://en.wikipedia.org/wiki/Plain_old_Java_object\n[java-bean-conventions]: https://en.wikipedia.org/wiki/JavaBeans#JavaBean_conventions\n[verhas/immutator]: https://github.com/verhas/immutator\n[java-proxy]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Proxy.html\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcronn%2Freflection-util","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcronn%2Freflection-util","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcronn%2Freflection-util/lists"}