Metter (meta getter / setter) is an annotation processor for generating getter and setter suppliers.
- Supports Lombok annotations:
@Data
,@Getter
and@Setter
. - Supports inheritance (getters/setters of superclasses).
- Ignores private methods.
See examples.
Add this code to dependencies
section in your build.gradle
:
compileOnly 'dev.alexengrig:metter:0.1.1'
annotationProcessor 'dev.alexengrig:metter:0.1.1'
Add this code to dependencies
section in your pom.xml
:
<dependency>
<groupId>dev.alexengrig</groupId>
<artifactId>metter</artifactId>
<version>0.1.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
Specify the annotation processor to maven-compiler-plugin
plugin:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>dev.alexengrig</groupId>
<artifactId>metter</artifactId>
<version>0.1.1</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Add to your class @GetterSupplier
for to generate getters and/or
@SetterSupplier
for to generate setters:
import dev.alexengrig.metter.annotation.GetterSupplier;
import dev.alexengrig.metter.annotation.SetterSupplier;
@GetterSupplier
@SetterSupplier
public class Domain {
private int integer;
private boolean bool;
private String string;
public int getInteger() {
return integer;
}
public void setInteger(int integer) {
this.integer = integer;
}
public boolean isBool() {
return bool;
}
public void setBool(boolean bool) {
this.bool = bool;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
}
The generated suppliers have a default name consisting of a prefix as a class name,
and a suffix as the supplier name: ${CLASS_NAME}GetterSupplier
and ${CLASS_NAME}SetterSupplier
.
You can set a custom name using the annotation parameter value
.
All fields that have getters/setter will be added to
the map that DomainGetterSupplier
/DomainSetterSupplier
stores.
You can set included/excluded field names using the annotation parameters includeFields
/excludeFields
.
The generated suppliers implement the
Supplier
functional interface and to get the map of getters/setters, you need to call Supplier#get
:
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Function;
public class DomainService {
private final Map<String, Function<Domain, Object>> getterByField = new DomainGetterSupplier().get();
private final Map<String, BiConsumer<Domain, Object>> setterByField = new DomainSetterSupplier().get();
public void printFieldValues(Domain domain) {
getterByField.forEach((field, getter) -> System.out.println(field " = " getter.apply(domain)));
}
public void transfer(Domain from, Domain to) {
setterByField.forEach((field, setter) -> setter.accept(to, getterByField.get(field).apply(from)));
}
}
You can extend:
import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
public class CustomDomainGetterSupplier extends DomainGetterSupplier {
@Override
protected Map<String, Function<Domain, Object>> createMap() {
Map<String, Function<Domain, Object>> generatedMap = super.createMap();
generatedMap.put("name", domain -> "Name: domain.toString()");
return Collections.unmodifiableMap(generatedMap);
}
public Function<Domain, Object> getGetter(String field) {
return this.getterByField.get(field);
}
}
You can create a bean (e.g. Spring):
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfiguration {
@Bean
public Map<String, Function<Domain, Object>> getterByField() {
return new DomainGetterSupplier().get();
}
@Bean
public Map<String, BiConsumer<Domain, Object>> setterByField() {
return new DomainSetterSupplier().get();
}
}
An annotation for to generate a getters supplier.
Field | Type | Default | Description |
---|---|---|---|
value | String |
${CLASS_NAME}GetterSupplier |
Supplier class name |
includedFields | String[] |
empty | Array of fields to include in the supplier |
excludedFields | String[] |
empty | Array of fields to exclude in the supplier |
An annotation for to generate a setters supplier.
Field | Type | Default | Description |
---|---|---|---|
value | String |
${CLASS_NAME}SetterSupplier |
Supplier class name |
includedFields | String[] |
empty | Array of fields to include in the supplier |
excludedFields | String[] |
empty | Array of fields to exclude in the supplier |
Has a domain:
class Man {
private String name;
private int age;
/*Getters and Setters*/
}
Need a change journal:
// field: Old value -> New value
name: Tomas -> Tom
age: 18 -> 19
Each field:
import java.util.StringJoiner;
class ManualManChangeLogGenerator extends BaseChangeLogGenerator<Man> {
@Override
public String generate(Man man, Man newMan) {
StringJoiner joiner = new StringJoiner("\n");
if (!man.getName().equals(newMan.getName())) {
joiner.add(changeLog("name", man.getName(), newMan.getName()));
}
if (man.getAge() != newMan.getAge()) {
joiner.add(changeLog("age", man.getAge(), newMan.getAge()));
}
return joiner.toString();
}
}
Using Map
:
import java.util.*;
import java.util.function.Function;
class MapManChangeLogGenerator extends BaseChangeLogGenerator<Man> {
protected Map<String, Function<Man, Object>> getterByField = createMap();
protected Map<String, Function<Man, Object>> createMap() {
return new HashMap<String, Function<Man, Object>>() {{
put("name", Man::getName);
put("age", Man::getAge);
}};
}
@Override
public String generate(Man man, Man newMan) {
StringJoiner joiner = new StringJoiner("\n");
getterByField.forEach((field, getter) -> {
Object value = getter.apply(man);
Object newValue = getter.apply(newMan);
if (!Objects.equals(value, newValue)) {
joiner.add(changeLog(field, value, newValue));
}
});
return joiner.toString();
}
}
Each field via Java Reflection:
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;
class ReflectionManChangeLogGenerator extends MapManChangeLogGenerator {
@Override
protected Map<String, Function<Man, Object>> createMap() {
Field[] fields = Man.class.getDeclaredFields();
HashMap<String, Function<Man, Object>> map = new HashMap<>(fields.length);
for (Field field : fields) {
String fieldName = field.getName();
String capitalizedFieldName = getCapitalized(fieldName);
for (Method method : Man.class.getDeclaredMethods()) {
String methodName = method.getName();
if (isGetter(methodName, capitalizedFieldName)) {
map.put(fieldName, createGetter(method));
break;
}
}
}
return map;
}
private String getCapitalized(String name) {
return name.substring(0, 1).toUpperCase() name.substring(1);
}
private boolean isGetter(String methodName, String lastMethodNamePart) {
return (methodName.startsWith("get") && methodName.equals("get" lastMethodNamePart))
|| (methodName.startsWith("is") && methodName.equals("is" lastMethodNamePart));
}
private Function<Man, Object> createGetter(Method method) {
return instance -> {
try {
return method.invoke(instance);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalArgumentException(instance.toString(), e);
}
};
}
}
Add @GetterSupplier
annotation:
import dev.alexengrig.metter.annotation.GetterSupplier;
@GetterSupplier
class Man {/*...*/}
Using ManGetterSupplier
:
import java.util.Map;
import java.util.function.Function;
class GenerationManChangeLogGenerator extends MapManChangeLogGenerator {
@Override
protected Map<String, Function<Man, Object>> createMap() {
return new ManGetterSupplier().get();
}
}
If you add a new field to Man
,
then the reflection solution and the generation solution will continue to work,
unlike the manual solution.
The generation solution is faster than the reflection solution (reflection is slow, see benchmarks).
domainN
(16-128), where N
is number of fields.
generation
- using metter
; handling
- using MethodHandle
.
Benchmark Mode Cnt Score Error Units
GetterSupplierBenchmarks.get_allValuesOf_domain128_via_generation avgt 10 1849.651 ± 16.663 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain128_via_handling avgt 10 1984.062 ± 24.774 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain128_via_manually avgt 10 785.069 ± 5.008 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain128_via_map avgt 10 1988.466 ± 27.310 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain128_via_reflection avgt 10 2339.250 ± 11.748 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain16_via_generation avgt 10 199.299 ± 2.471 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain16_via_handling avgt 10 195.565 ± 1.666 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain16_via_manually avgt 10 40.004 ± 1.263 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain16_via_map avgt 10 195.722 ± 1.465 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain16_via_reflection avgt 10 225.331 ± 2.115 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain32_via_generation avgt 10 386.447 ± 3.224 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain32_via_handling avgt 10 354.387 ± 4.330 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain32_via_manually avgt 10 87.422 ± 4.101 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain32_via_map avgt 10 423.696 ± 3.109 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain32_via_reflection avgt 10 482.624 ± 10.645 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain64_via_generation avgt 10 868.321 ± 9.389 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain64_via_handling avgt 10 775.914 ± 11.301 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain64_via_manually avgt 10 247.175 ± 1.137 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain64_via_map avgt 10 965.654 ± 10.060 ns/op
GetterSupplierBenchmarks.get_allValuesOf_domain64_via_reflection avgt 10 1043.159 ± 10.393 ns/op
This project is licensed under Apache License, version 2.0.
JetBrains Mono typeface is licensed under Apache License, version 2.0 and it used in logo and preview.