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

io.scalecube.config.AbstractConfigProperty Maven / Gradle / Ivy

There is a newer version: 0.4.21.RC1
Show newest version
package io.scalecube.config;

import io.scalecube.config.source.LoadedConfigProperty;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract parent class for config property classes. Holds mutable state fields: {@link #value} the
 * parsed config property field, {@link #inputList} which is kind of a 'source' for parsed value.
 * Reload process may change volatile values of those fields, of course, if property value actually
 * changed and validation passed. Collections of validators and callbacks (of type {@link T}) are
 * defined here and only operations around them are shared to subclasses.
 *
 * @param  type of the property value
 */
abstract class AbstractConfigProperty {
  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigProperty.class);

  private static final String ERROR_VALIDATION_FAILED =
      "Validation failed on config property: '%s', failed value: %s";

  final String name;
  final Class propertyClass;
  final Collection> validators =
      new CopyOnWriteArraySet<>(); // of type Set for a reason
  final Collection> callbacks =
      new CopyOnWriteArraySet<>(); // of type Set for a reason

  private PropertyCallback propertyCallback; // initialized from subclass
  private volatile T value; // initialized from subclass, reset in callback
  private volatile List
      inputList; // initialized from subclass, reset in callback

  AbstractConfigProperty(String name, Class propertyClass) {
    this.name = name;
    this.propertyClass = propertyClass;
  }

  public final String name() {
    return name;
  }

  public final Optional value() {
    return Optional.ofNullable(value);
  }

  public final void addValidator(Predicate validator) {
    if (!validator.test(value)) {
      throw new IllegalArgumentException(String.format(ERROR_VALIDATION_FAILED, name, value));
    }
    validators.add(validator);
  }

  public final void addCallback(BiConsumer callback) {
    callbacks.add((t1, t2) -> invokeCallback(callback, t1, t2));
  }

  public final void addCallback(Executor executor, BiConsumer callback) {
    callbacks.add((t1, t2) -> executor.execute(() -> invokeCallback(callback, t1, t2)));
  }

  /**
   * Binds this config property instance to given {@link PropertyCallback}. The latter is shared
   * among config property instances of the same type.
   *
   * @param propertyCallback propertyCallback of certain type.
   */
  final void setPropertyCallback(PropertyCallback propertyCallback) {
    (this.propertyCallback = propertyCallback).addConfigProperty(this);
  }

  /**
   * This method is being called from subclasses to assign a {@link #value} with initial value.
   *
   * @see PropertyCallback#computeValue(List, AbstractConfigProperty)
   */
  final void computeValue(List inputList) {
    propertyCallback.computeValue(inputList, this);
  }

  /**
   * Takes new value, validates it, resets {@code this.value} field and optionally calling
   * callbacks.
   *
   * @param value1 new value to set; may be null.
   * @param inputList1 valueParser input list; contains additional info such as source, origin and
   *     string value representation which in fact had built up given {@code value1} param.
   * @param invokeCallbacks flag indicating whether it's needed to notify callbacks about changes.
   * @throws IllegalArgumentException in case new value fails against existing validators.
   */
  final void acceptValue(T value1, List inputList1, boolean invokeCallbacks) {
    if ((value == null && value1 == null) || isInputsEqual(inputList1)) {
      return;
    }

    if (!validators.stream().allMatch(input -> input.test(value1))) {
      throw new IllegalArgumentException(String.format(ERROR_VALIDATION_FAILED, name, value));
    }

    T t1 = value;
    T t2 = value = value1;

    inputList = inputList1;

    if (invokeCallbacks) {
      for (BiConsumer callback : callbacks) {
        callback.accept(t1, t2);
      }
    }
  }

  /**
   * Helper method which applies given {@code mapper} lambda to the {@link #inputList} (if any). For
   * example if one needs to retrieve more than just a {@link #value} info from this config
   * property, like source, origin and etc.
   */
  final Optional mapToString(Function, String> mapper) {
    return Optional.ofNullable(inputList).map(mapper);
  }

  private void invokeCallback(BiConsumer callback, T t1, T t2) {
    try {
      callback.accept(t1, t2);
    } catch (Exception e) {
      LOGGER.error(
          "Exception occurred on property-change callback: {}, "
              + "property name: '{}', oldValue: {}, newValue: {}, cause: {}",
          callback,
          name,
          t1,
          t2,
          e,
          e);
    }
  }

  protected boolean isMyProperty(LoadedConfigProperty property) {
    return this.name.equals(property.name());
  }

  private boolean isInputsEqual(List inputList1) {
    if ((inputList == null && inputList1 != null) || (inputList != null && inputList1 == null)) {
      return false;
    }

    Map> inputMap =
        inputList
            .stream()
            .collect(
                Collectors.toMap(LoadedConfigProperty::name, LoadedConfigProperty::valueAsString));

    Map> inputMap1 =
        inputList1
            .stream()
            .collect(
                Collectors.toMap(LoadedConfigProperty::name, LoadedConfigProperty::valueAsString));

    return inputMap.equals(inputMap1);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy