All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.klojang.invoke.SetterFactory Maven / Gradle / Ivy

Go to download

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));
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy