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

com.speedment.tool.config.AbstractDocumentProperty Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, Speedment, Inc. All Rights Reserved.
 *
 * 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.speedment.tool.config;

import com.speedment.common.function.FloatSupplier;
import com.speedment.common.function.OptionalBoolean;
import com.speedment.common.mapstream.MapStream;
import com.speedment.runtime.config.Document;
import static com.speedment.runtime.config.util.DocumentUtil.toStringHelper;
import com.speedment.runtime.core.util.OptionalUtil;
import com.speedment.tool.config.component.DocumentPropertyComponent;
import com.speedment.tool.config.util.NumericProperty;
import com.speedment.tool.config.util.SimpleNumericProperty;
import static java.util.Collections.newSetFromMap;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.*;
import java.util.stream.Stream;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.FloatProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import static javafx.collections.FXCollections.observableList;
import static javafx.collections.FXCollections.observableMap;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;

/**
 *
 * @param   the type of the implementing class
 * 
 * @author  Emil Forslund
 * @since   2.3.0
 */
public abstract class AbstractDocumentProperty> 
    implements DocumentProperty {
 
    private final Map config;
    private final transient ObservableMap> properties;
    private final transient ObservableMap>> children;
    
    /**
     * Invalidation listeners required by the {@code Observable} interface.
     */
    private final transient Set listeners;
    
    protected AbstractDocumentProperty() {
        this.config     = new ConcurrentHashMap<>();
        this.properties = observableMap(new ConcurrentHashMap<>());
        this.children   = observableMap(new ConcurrentHashMap<>());
        this.listeners  = newSetFromMap(new ConcurrentHashMap<>());
    }

    @Override
    public final Map getData() {
        return config;
    }
    
    @Override
    @Deprecated
    public final void put(String key, Object val) {
        throw new UnsupportedOperationException(
            "Observable config documents does not support the put()-operation " +
            "directly. Instead you should request the appropriate property or " +
            "observable list for the specific key and modify it."
        );
    }

    @Override
    public final Optional get(String key) {
        @SuppressWarnings("unchecked")
        final Property prop = (Property) properties.get(key);
        if (prop == null) {
            return Optional.empty();
        } else {
            return Optional.ofNullable(prop.getValue());
        }
    }

    @Override
    public final OptionalBoolean getAsBoolean(String key) {
        final BooleanProperty prop = (BooleanProperty) properties.get(key);
        if (prop == null) {
            return OptionalBoolean.empty();
        } else {
            return OptionalBoolean.ofNullable(prop.getValue());
        }
    }

    @Override
    public final OptionalLong getAsLong(String key) {
        final LongProperty prop = (LongProperty) properties.get(key);
        if (prop == null) {
            return OptionalLong.empty();
        } else {
            return OptionalUtil.ofNullable(prop.getValue());
        }
    }

    @Override
    public final OptionalDouble getAsDouble(String key) {
        final DoubleProperty prop = (DoubleProperty) properties.get(key);
        if (prop == null) {
            return OptionalDouble.empty();
        } else {
            return OptionalUtil.ofNullable(prop.getValue());
        }
    }

    @Override
    public final OptionalInt getAsInt(String key) {
        final IntegerProperty prop = (IntegerProperty) properties.get(key);
        if (prop == null) {
            return OptionalInt.empty();
        } else {
            return OptionalUtil.ofNullable(prop.getValue());
        }
    }

    @Override
    public final Optional getAsString(String key) {
        final StringProperty prop = (StringProperty) properties.get(key);
        if (prop == null) {
            return Optional.empty();
        } else {
            return Optional.ofNullable(prop.getValue());
        }
    }
    
    @Override
    public final StringProperty stringPropertyOf(String key, Supplier ifEmpty) {
        return (StringProperty) properties.computeIfAbsent(key, k -> prepare(k, new SimpleStringProperty(), ifEmpty.get()));
    }
    
    @Override
    public final BooleanProperty booleanPropertyOf(String key, BooleanSupplier ifEmpty) {
        return (BooleanProperty) properties.computeIfAbsent(key, k -> prepare(k, new SimpleBooleanProperty(), ifEmpty.getAsBoolean()));
    }
    
    @Override
    public final IntegerProperty integerPropertyOf(String key, IntSupplier ifEmpty) {
        return numericPropertyOf(key, ifEmpty::getAsInt, NumericProperty::asIntegerProperty);
    }

    @Override
    public final LongProperty longPropertyOf(String key, LongSupplier ifEmpty) {
        return numericPropertyOf(key, ifEmpty::getAsLong, NumericProperty::asLongProperty);
    }

    @Override
    public final DoubleProperty doublePropertyOf(String key, DoubleSupplier ifEmpty) {
        return numericPropertyOf(key, ifEmpty::getAsDouble, NumericProperty::asDoubleProperty);
    }
    
    @Override
    public final FloatProperty floatPropertyOf(String key, FloatSupplier ifEmpty) {
        return numericPropertyOf(key, ifEmpty::getAsFloat, NumericProperty::asFloatProperty);
    }
    
    private > T numericPropertyOf(String key, Supplier ifEmpty, Function wrapper) {
        final NumericProperty property = (NumericProperty) properties
            .computeIfAbsent(key, k -> 
                prepare(k, new SimpleNumericProperty(), ifEmpty.get())
            );
        
        return wrapper.apply(property);
    }

    @Override
    public final  ObservableList observableListOf(String key) {
        @SuppressWarnings("unchecked")
        final ObservableList list = (ObservableList)
            children.computeIfAbsent(key, k -> 
                addListeners(k, observableList(new CopyOnWriteArrayList<>()))
            );
        
        return list;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public final ObservableMap> childrenProperty() {
        return
            (ObservableMap>) 
            (ObservableMap)
            children
        ;
    }

    @Override
    public final Stream children() {
        return MapStream.of(children)
            .sortedByKey(Comparator.naturalOrder())
            .flatMapValue(ObservableList::stream)
            .values();
    }

    @Override
    @Deprecated
    public final 

Stream children(String key, BiFunction, T> constructor) { throw new UnsupportedOperationException("children() shall not be called from a Property"); } @Override public final void invalidate() { listeners.forEach(l -> l.invalidated(this)); getParent().map(DocumentProperty.class::cast) .ifPresent(DocumentProperty::invalidate); } @Override public final void addListener(InvalidationListener listener) { listeners.add(listener); } @Override public final void removeListener(InvalidationListener listener) { listeners.remove(listener); } @Override public String toString() { return toStringHelper(this); } /** * An overridable method used to get the full key path with the specified * trail. This is used to locate the appropriate constructor. * * @param key the key to end with (can be null) * @return the constructor */ protected abstract List keyPathEndingWith(String key); /** * Creates a new child on the specified key with the specified data and * returns it. This method can be overriden by subclasses to create better * implementations. *

* Warning! This method is only intended to be called internally and does * not properly configure created children in the responsive model. * * @param documentPropertyComponent the documentPropertyComponent instance * @param key the key to create the child on * @return the created child */ protected final DocumentProperty createChild(DocumentPropertyComponent documentPropertyComponent, String key) { return documentPropertyComponent .getConstructor(keyPathEndingWith(key)) .create(this); } /** * Adds a listener to the specified property so that changes to it are * reflected down to the source map. * * @param the type of the property * @param key the key of the property * @param property the property to listen to * @param initialValue the initial value of this property * @return the same property but with listener attached */ private Property prepare(String key, Property property, T initialValue) { final ChangeListener change = (ob, oldValue, newValue) -> { if (newValue != null) { config.put(key, newValue); } else { config.remove(key); } invalidate(); }; property.setValue(initialValue); property.addListener(change); change.changed(property, null, initialValue); return property; } /** * Adds a listener to the specified list so that changes to it are reflected * down to the source map. * * @param key the key where the list is located * @param list the list to add listeners to * @return the same list but with listener attached */ private ObservableList> addListeners(String key, ObservableList> list) { // When an observable children list under a specific key is // modified, the new children must be inserted into the source // equivalent as well. list.addListener((ListChangeListener.Change listChange) -> { while (listChange.next()) { if (listChange.wasAdded()) { // Find or create a children list in the source map // for the specified key @SuppressWarnings("unchecked") final List> source = (List>) config.computeIfAbsent( key, k -> new CopyOnWriteArrayList<>() ); // Add a reference to the map for every child listChange.getAddedSubList().stream() .map(DocumentProperty::getData) // Exactly the same map as is used in the property .forEachOrdered(source::add); } if (listChange.wasRemoved()) { @SuppressWarnings("unchecked") final List> source = (List>) config.computeIfAbsent( key, k -> new CopyOnWriteArrayList<>() ); listChange.getRemoved().stream() .map(DocumentProperty::getData) .forEach(removed -> source.removeIf(e -> e == removed) // Identity ); } } invalidate(); }); return list; } }