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

com.netopyr.reduxfx.vscenegraph.impl.patcher.property.Accessors Maven / Gradle / Ivy

package com.netopyr.reduxfx.vscenegraph.impl.patcher.property;

import com.netopyr.reduxfx.vscenegraph.impl.patcher.NodeUtilities;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.Pane;
import io.vavr.collection.HashMap;
import io.vavr.collection.Map;
import io.vavr.control.Option;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

public class Accessors {

    private static final Lock lock = new ReentrantLock();
    private static final Lock nodeLock = new ReentrantLock();
    private static final Lock nodeListLock = new ReentrantLock();

    private static volatile Map accessorMap = HashMap.empty();
    private static volatile Map layoutAccessorMap = HashMap.empty();
    private static volatile Map nodeAccessorMap = HashMap.empty();
    private static volatile Map nodeListAccessorMap = HashMap.empty();

    private Accessors() {
    }

    public static void registerAccessor(Class clazz, String propertyName, Supplier accessor) {
        final PropertyKey propertyKey = new PropertyKey(clazz, propertyName);
        if (!accessorMap.containsKey(propertyKey)) {
            lock.lock();
            try {
                if (!accessorMap.containsKey(propertyKey)) {
                    final Accessor result = searchInCache(accessorMap, propertyKey).getOrElse(accessor);
                    accessorMap = cacheAccessor(accessorMap, clazz, propertyName, result);
                }
            } finally {
                lock.unlock();
            }
        }
    }

    public static void registerNodeAccessor(Class clazz, String propertyName, Supplier accessor) {
        final PropertyKey propertyKey = new PropertyKey(clazz, propertyName);
        if (!nodeAccessorMap.containsKey(propertyKey)) {
            nodeLock.lock();
            try {
                if (!nodeAccessorMap.containsKey(propertyKey)) {
                    final NodeAccessor result = searchInCache(nodeAccessorMap, propertyKey).getOrElse(accessor);
                    nodeAccessorMap = cacheAccessor(nodeAccessorMap, clazz, propertyName, result);
                }
            } finally {
                nodeLock.unlock();
            }
        }
    }

    public static void registerNodeListAccessor(Class clazz, String propertyName, Supplier accessor) {
        final PropertyKey propertyKey = new PropertyKey(clazz, propertyName);
        if (!nodeListAccessorMap.containsKey(propertyKey)) {
            nodeListLock.lock();
            try {
                if (!nodeListAccessorMap.containsKey(propertyKey)) {
                    final NodeListAccessor result = searchInCache(nodeListAccessorMap, propertyKey).getOrElse(accessor);
                    nodeListAccessorMap = cacheAccessor(nodeListAccessorMap, clazz, propertyName, result);
                }
            } finally {
                nodeListLock.unlock();
            }
        }
    }

    public static Option getAccessor(Object node, String propertyName) {
        final Class clazz = node.getClass();
        final PropertyKey propertyKey = new PropertyKey(clazz, propertyName);

        final Option result = accessorMap.get(propertyKey);
        if (result.isDefined()) {
            return result;
        }

        lock.lock();
        try {
            return accessorMap.get(propertyKey)
                    .orElse(() ->
                            searchInCache(accessorMap, propertyKey)
                                    .orElse(() -> createAccessor(clazz, propertyName))
                                    .peek(accessor ->
                                            accessorMap = cacheAccessor(accessorMap, clazz, propertyName, accessor)
                                    )
                    )
                    .orElse(() -> {
                        if (node instanceof Node) {
                            final Class parentClass = ((Node) node).getParent().getClass();
                            if (Pane.class.isAssignableFrom(parentClass)) {
                                final PropertyKey layoutKey = new PropertyKey(parentClass, propertyName);
                                return layoutAccessorMap.get(layoutKey)
                                        .orElse(() ->
                                                searchInCache(layoutAccessorMap, layoutKey)
                                                        .orElse(() -> createLayoutAccessor(parentClass, propertyName))
                                                        .peek(accessor -> cacheAccessor(layoutAccessorMap, parentClass, propertyName, accessor))
                                        );
                            }
                        }
                        return Option.none();
                    });
        } finally {
            lock.unlock();
        }
    }

    public static Option getNodeAccessor(Object node, String propertyName) {
        final Class clazz = node.getClass();
        final PropertyKey propertyKey = new PropertyKey(clazz, propertyName);

        final Option result = nodeAccessorMap.get(propertyKey);
        if (result.isDefined()) {
            return result;
        }

        nodeLock.lock();
        try {
            return nodeAccessorMap.get(propertyKey)
                    .orElse(() ->
                            searchInCache(nodeAccessorMap, propertyKey)
                                    .peek(accessor ->
                                            nodeAccessorMap = cacheAccessor(nodeAccessorMap, clazz, propertyName, accessor)
                                    )
                    );
        } finally {
            nodeLock.unlock();
        }
    }

    public static Option getNodeListAccessor(Object node, String propertyName) {
        final Class clazz = node.getClass();
        final PropertyKey propertyKey = new PropertyKey(clazz, propertyName);

        final Option result = nodeListAccessorMap.get(propertyKey);
        if (result.isDefined()) {
            return result;
        }

        nodeListLock.lock();
        try {
            return nodeListAccessorMap.get(propertyKey)
                    .orElse(() ->
                            searchInCache(nodeListAccessorMap, propertyKey)
                                    .peek(accessor ->
                                            nodeListAccessorMap = cacheAccessor(nodeListAccessorMap, clazz, propertyName, accessor)
                                    )
                    );
        } finally {
            nodeListLock.unlock();
        }
    }

    private static  Option searchInCache(Map map, PropertyKey propertyKey) {
        final String propertyName = propertyKey.name;
        for (Class clazz = propertyKey.clazz.getSuperclass(); clazz != null; clazz = clazz.getSuperclass()) {
            final Option accessor = map.get(new PropertyKey(clazz, propertyName));
            if (accessor.isDefined()) {
                return accessor;
            }
        }
        return Option.none();
    }

    private static  Map cacheAccessor(Map oldMap, Class nodeClass, String propertyName, T accessor) {
        Map map = oldMap;
        final Option getter = NodeUtilities.getGetterMethod(nodeClass, propertyName);
        map = map.put(new PropertyKey(nodeClass, propertyName), accessor);
        if (getter.isDefined()) {
            final Class declaringClass = getter.get().getDeclaringClass();
            if (! nodeClass.equals(declaringClass)) {
                for (Class clazz = nodeClass; !declaringClass.equals(clazz); clazz = clazz.getSuperclass()) {
                    map = map.put(new PropertyKey(clazz, propertyName), accessor);
                }
                map = map.put(new PropertyKey(declaringClass, propertyName), accessor);
            }
        }
        return map;
    }

    private static Option createAccessor(Class nodeClass, String propertyName) {
        final Option getterMethod = NodeUtilities.getGetterMethod(nodeClass, propertyName);
        final Option getter = getterMethod.flatMap(NodeUtilities::convertToMethodHandle);

        final Option> type = getterMethod.map(Method::getReturnType);
        if (type.isDefined() && Collection.class.isAssignableFrom(type.get())) {
            if (ObservableList.class.isAssignableFrom(type.get())) {
                final Option propertyGetter = NodeUtilities.getPropertyGetter(nodeClass, propertyName);
                return propertyGetter.isDefined() ?
                        propertyGetter.map(ListAccessor::new)
                        : getter.map(ListWithoutListenerAccessor::new);
            }
            return getter.map(CollectionAccessor::new);
        }

        if (NodeUtilities.getSetter(nodeClass, propertyName).isDefined()) {
            return NodeUtilities.getPropertyGetter(nodeClass, propertyName).map(PropertyAccessor::new)
                    .orElse(NodeUtilities.getSetter(nodeClass, propertyName).map(SetterAccessor::new));
        } else {
            return NodeUtilities.getPropertyGetter(nodeClass, propertyName).map(ReadOnlyPropertyAccessor::new);
        }
    }

    private static Option createLayoutAccessor(Class parentClass, String propertyName) {
        final Option layoutSetter = NodeUtilities.getLayoutSetter(parentClass, propertyName);
        return layoutSetter.isEmpty() ? Option.none() : Option.of(new LayoutConstraintAccessor(layoutSetter.get()));
    }

    private static final class PropertyKey {
        private final Class clazz;
        private final String name;

        private PropertyKey(Class clazz, String name) {
            this.clazz = clazz;
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;

            if (o == null || getClass() != o.getClass()) return false;

            PropertyKey that = (PropertyKey) o;

            return new EqualsBuilder()
                    .append(clazz, that.clazz)
                    .append(name, that.name)
                    .isEquals();
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder(17, 37)
                    .append(clazz)
                    .append(name)
                    .toHashCode();
        }

        @Override
        public String toString() {
            return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
                    .append("clazz", clazz)
                    .append("name", name)
                    .toString();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy