/* * Copyright 2014 Avanza Bank AB * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.avanza.astrix.config; import java.util.LinkedList; import java.util.Objects; import java.util.Optional; /** * A DynamicPropertyChain is a hierarchical set of properties. A property * is resolved to the first {@link DynamicConfigProperty} in the chain * that have a value set. If no property in the chain has a value then * the default value is used. * * The {@link DynamicPropertyChainListener} will be notified synchronously * when it is registered by calling {@link #bindTo(DynamicPropertyChainListener)}. * After the initial notification with the current value, the listener is notified * every time the resolved property changes, for instance when: * * <ul> * <li>A property with higher precedence than the current resolved property is set</li> * <li>The current resolved property is changed. In that case * the listener will be notified with the new value</li> * <li>The current resolved property is cleared. In that case * the listener will be notified with the new (possibly default) resolved value</li> * </ul> * * * @author Elias Lindholm (elilin) * * @param <T> */ final class DynamicPropertyChain<T> implements DynamicPropertyListener<DynamicConfigProperty<T>> { private final LinkedList<DynamicConfigProperty<T>> chain = new LinkedList<>(); private volatile Optional<PropertyChangeEventDispatcher> propertyChainListener; private final PropertyParser<T> parser; private final T defaultValue; private DynamicPropertyChain(T defaultValue, PropertyParser<T> parser) { this.defaultValue = defaultValue; this.parser = parser; this.propertyChainListener = Optional.empty(); } /** * Binds the resolved value of this chain to a given listener. The listener will be * notified synchronously with the current resolved value of this property chain, and * will later receive a notification each time the resolved of this chain changes. * */ void bindTo(DynamicPropertyChainListener<T> l) { this.propertyChainListener = Optional.of(new PropertyChangeEventDispatcher(l)); this.propertyChainListener.ifPresent(PropertyChangeEventDispatcher::init); } static <T> DynamicPropertyChain<T> createWithDefaultValue(T defaultValue, PropertyParser<T> parser) { return new DynamicPropertyChain<>(defaultValue, parser); } private T get() { return chain.stream() .filter(DynamicConfigProperty::isSet) .findFirst() .map(DynamicConfigProperty::get) .orElse(defaultValue); } @Override public void propertyChanged(DynamicConfigProperty<T> updatedProperty) { this.propertyChainListener.ifPresent(PropertyChangeEventDispatcher::propertyChanged); } DynamicConfigProperty<T> appendValue() { DynamicConfigProperty<T> property = DynamicConfigProperty.create(this, parser); chain.addLast(property); return property; } private class PropertyChangeEventDispatcher { private DynamicPropertyChainListener<T> listener; private T lastNotifiedState; public PropertyChangeEventDispatcher(DynamicPropertyChainListener<T> listener) { this.listener = listener; } private void init() { propertyChanged(); } private void propertyChanged() { T currentResolvedValue = get(); if (!Objects.equals(currentResolvedValue, lastNotifiedState)) { listener.propertyChanged(currentResolvedValue); lastNotifiedState = currentResolvedValue; } } } }