class BuilderPropertySetter {

  public void set(Object builder, String propertyName, String propertyValue) {

    // By convention, builder methods that return child builders have names ending with this.
    final String CHILD_BUILDER_ACCESSOR_SUFFIX = "Config";

    try {
      final List<String> propertyComponents = Arrays.asList(propertyName.split("\\.", -1));
      final List<String> pathToBuilder = propertyComponents.subList(0, propertyComponents.size() - 1);
      final String setterName = propertyComponents.get(propertyComponents.size() - 1);

      for (String pathComponent : pathToBuilder) {
        try {
          final String childBuilderAccessor = pathComponent + CHILD_BUILDER_ACCESSOR_SUFFIX;
          builder = builder.getClass().getMethod(childBuilderAccessor).invoke(builder);
        } catch (NoSuchMethodException e) {
          throw InvalidArgumentException.fromMessage("Method not found: " + e.getMessage(), e);
        }
      }

      final List<Method> candidates = Arrays.stream(builder.getClass().getMethods())
          .filter(m -> m.getName().equals(setterName))
          .filter(m -> m.getParameterCount() == 1)
          .collect(Collectors.toList());

      if (candidates.isEmpty()) {
        throw InvalidArgumentException.fromMessage("No one-arg setter for property \"" + propertyName + "\" in " + builder.getClass());
      }

      int remainingCandidates = candidates.size();
      final List<Throwable> failedCandidates = new ArrayList<>();
      for (Method setter : candidates) {
        try {
          final Object convertedValue = typeRegistry.convert(propertyValue, setter.getGenericParameterTypes()[0]);
          setter.invoke(builder, convertedValue);
        } catch (Throwable t) {
          if (candidates.size() == 1) {
            throw t;
          }
          failedCandidates.add(t);
          if (--remainingCandidates == 0) {
            final InvalidArgumentException e = InvalidArgumentException.fromMessage(
                "Found multiple one-arg setters for property \"" + propertyName + "\" in "
                    + builder.getClass() + " but none accepted the value \"" + propertyValue + "\".");
            failedCandidates.forEach(e::addSuppressed);
            throw e;
          }
        }
      }

    } catch (IllegalAccessException e) {
      throw InvalidArgumentException.fromMessage("Failed to access the corresponding builder method.", e);

    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      throw (cause instanceof RuntimeException) ? (RuntimeException) cause : new RuntimeException(cause);
    }
  }

}