📜 ⬆️ ⬇️

Calculation of property names at runtime in the Java language

Some tools may use property names in the form of String values. They usually exist as constants defined by literals. What is wrong? Here's what: during refactoring, property names can change, moreover, properties can disappear altogether. And in constants there will be old, irrelevant values.

You can hope for the attention of a tired developer, or for unwritten tests. But there is a more reliable way.

Below is an example of using a tool that was born when I got tired of running around the rake around Hibernate and its wonderful Criteria API.

 package ru.bdm.reflection; import junit.framework.Assert; import org.junit.Test; import java.util.Date; import java.util.List; import static ru.bdm.reflection.PathExtractor.Example; /** * User: D.Brusentsov * Date: 22.04.13 * Time: 20:21 */ public class PathExtractorUsageForHabrahabr { public static class Pet { private String name; private Human owner; //getters and setters omitted } public static class Human { private String name; private Date birth; private List<Human> relatives; //getters and setters omitted } @Test public void getPetName() { String name = PathExtractor.getPath(new Example<Pet>() { @Override public void example(Pet pet) { pet.getName(); } }); Assert.assertEquals("name", name); } @Test public void getPetOwnerName() { String ownerName = PathExtractor.getPath(new Example<Pet>() { @Override public void example(Pet pet) { pet.getOwner().getName(); } }); Assert.assertEquals("owner.name", ownerName); } @Test public void getPetOwnerRelativesBirth() { String ownerRelativesBirth = PathExtractor.getPath(new Example<Pet>() { @Override public void example(Pet pet) { PathExtractor.mask(pet.getOwner().getRelatives()).getBirth(); } }); Assert.assertEquals("owner.relatives.birth", ownerRelativesBirth); } } 

')
So, we have a class a class with properties. In order to dynamically calculate the name of the desired property, we pass to the PathExtractor.getPath method PathExtractor.getPath instance of an anonymous class that extends the Example interface. The Example interface defines the method of the same name, in the body of which you need to refer to the property whose name interests us.
If the property name is changed by hand, then the code that calculates this name stops compiling. If automatic refactoring tools are used, the code will change automatically. That is, we will find out about the error at the compilation stage or it will not happen at all.

Inside the PathExtractor.getPath method, a proxy is created, which is passed to the Example.example method. This proxy remembers all calls, and, if possible, returns a similar proxy as the result of each call. Thus it becomes possible to find out not only the name of a single property, but also to build a chain of names to any property of any level of nesting.

The code looks rather cumbersome, but switching to Java 8 with lambdas or at least an IDE capable of displaying anonymous nested classes as lambdas completely solves this problem.

Minuses:


Download the source code from Google Drive .

Update:

Inspired by the comment of the user vladimir_dolzhenko and the possibilities of Java 8, I slightly modified the tool. Now the usage example looks more concise:

 package ru.bdm.reflection; import org.junit.Test; import java.util.Date; import java.util.List; import static junit.framework.Assert.assertEquals; import static ru.bdm.reflection.PathExtractorJava8.of; /** * User: D.Brusentsov * Date: 22.04.13 * Time: 20:21 */ public class PathExtractorJava8UsageForHabrahabr { public static class Pet { private String name; private Human owner; //getters and setters omitted } public static class Human { private String name; private Date birth; private List<Human> relatives; //getters and setters omitted } @Test public void getPetName() { String name = of(Pet.class, Pet::getName).end(); assertEquals("name", name); } @Test public void getPetOwnerName() { String ownerName = of(Pet.class, Pet::getOwner).then(Human::getName).end(); assertEquals("owner.name", ownerName); } @Test public void getPetOwnerRelativesBirth() { String ownerRelativesBirth = of(Pet.class, Pet::getOwner) .thenMask(Human::getRelatives) .then(Human::getBirth).end(); assertEquals("owner.relatives.birth", ownerRelativesBirth); } } 


Download source from Google Drive .

Source: https://habr.com/ru/post/223381/


All Articles