org.klojang.invoke.SetterFactory Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of klojang-invoke Show documentation
Show all versions of klojang-invoke Show documentation
Klojang Invoke is a Java module focused on path-based object access and dynamic
invocation. Its central classes are the Path class and the PathWalker class. The
Path class captures a path through an object graph. For example
"employee.address.city". The PathWalker class lets you read from and write to
a wide variety of types using Path objects.
The newest version!
package org.klojang.invoke;
import org.klojang.check.Check;
import org.klojang.util.ArrayMethods;
import org.klojang.util.ClassMethods;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.Character.isUpperCase;
import static java.lang.Character.toLowerCase;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Map.Entry;
import static org.klojang.check.CommonChecks.empty;
/**
* Assembles, caches and supplies {@link Setter setters} for classes.
*
* @author Ayco Holleman
*/
public final class SetterFactory {
/**
* The one and only instance of {@code SetterFactory}.
*/
public static final SetterFactory INSTANCE = new SetterFactory();
private final Map, Map> cache = new HashMap<>();
private SetterFactory() { }
/**
* Returns the public {@link Setter setters} for the specified class. The returned
* {@code Map} maps property names to {@code Setter} instances.
*
* @param clazz The class for which to retrieve the public setters
* @return The public setters of the specified class
* @throws IllegalAssignmentException If the does not have any public setters
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public Map getSetters(Class> clazz) {
Map setters = cache.get(clazz);
if (setters == null) {
List methods = getMethods(clazz);
Check.that(methods).isNot(empty(), () -> new NoPublicSettersException(clazz));
Entry[] entries = new Entry[methods.size()];
int i = 0;
for (Method m : methods) {
String prop = getPropertyNameFromSetter(m);
entries[i++] = Map.entry(prop, new Setter(m, prop));
}
cache.put(clazz, setters = Map.ofEntries(entries));
}
return setters;
}
private static List getMethods(Class> beanClass) {
Method[] methods = beanClass.getMethods();
List setters = new ArrayList<>();
for (Method m : methods) {
if (!beanClass.isRecord()
&& !isStatic(m.getModifiers())
&& m.getParameterCount() == 1
&& m.getReturnType() == void.class
&& isValidSetterName(m)
) {
setters.add(m);
}
}
return setters;
}
private static String getPropertyNameFromSetter(Method m) {
String n = m.getName();
if (n.startsWith("set") && isUpperCase(n.charAt(3))) {
return extractName(n, 3);
}
throw notAProperty(m);
}
private static String extractName(String n, int from) {
StringBuilder sb = new StringBuilder(n.length() - 3);
sb.append(n.substring(from));
sb.setCharAt(0, toLowerCase(sb.charAt(0)));
return sb.toString();
}
private static IllegalArgumentException notAProperty(Method m) {
String fmt = "method %s %s(%s) in class %s is not a setter";
String rt = ClassMethods.simpleClassName(m.getReturnType());
String clazz = ClassMethods.className(m.getDeclaringClass());
String params = ArrayMethods.implode(m.getParameterTypes(),
ClassMethods::simpleClassName);
String msg = String.format(fmt, rt, m.getName(), params, clazz);
return new IllegalArgumentException(msg);
}
private static boolean isValidSetterName(Method m) {
String n = m.getName();
return n.length() > 3 && n.startsWith("set") && isUpperCase(n.charAt(3));
}
}