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

com.github.phantomthief.model.builder.impl.SimpleModelBuilder Maven / Gradle / Ivy

The newest version!
package com.github.phantomthief.model.builder.impl;

import static com.google.common.collect.HashMultimap.create;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static java.util.Spliterator.IMMUTABLE;
import static java.util.Spliterator.NONNULL;
import static java.util.Spliterator.ORDERED;
import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.ClassUtils.getAllInterfaces;
import static org.apache.commons.lang3.ClassUtils.getAllSuperclasses;
import static org.apache.commons.lang3.builder.ToStringBuilder.reflectionToString;
import static org.apache.commons.lang3.builder.ToStringStyle.SHORT_PREFIX_STYLE;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.slf4j.Logger;

import com.github.phantomthief.model.builder.ModelBuilder;
import com.github.phantomthief.model.builder.context.BuildContext;
import com.github.phantomthief.model.builder.context.impl.SimpleBuildContext;
import com.github.phantomthief.model.builder.util.MergeUtils;
import com.google.common.collect.SetMultimap;

/**
 * @author w.vela
 */
@SuppressWarnings("unchecked")
public class SimpleModelBuilder implements ModelBuilder {

    private static final Logger logger = getLogger(SimpleModelBuilder.class);

    // obj.class=>obj->(namespace,ids)
    private final SetMultimap, Function>>> idExtractors = create();
    // obj.class=>obj->(namespace,values)
    private final SetMultimap, Function>>> valueExtractors = create();
    // idNamespace=>(valueNamespace, ids->values)
    private final SetMultimap, Map>>> valueBuilders = create();
    // targetNamespace=>Function
    private final Map>> lazyBuilders = new HashMap<>();

    private final ConcurrentMap, Set>>>> cachedIdExtractors = new ConcurrentHashMap<>();
    private final ConcurrentMap, Set>>>> cachedValueExtractors = new ConcurrentHashMap<>();

    @Override
    public void buildMulti(Iterable sources, B buildContext) {
        if (sources == null) {
            return;
        }
        if (buildContext instanceof SimpleBuildContext) {
            SimpleBuildContext simpleBuildContext = (SimpleBuildContext) buildContext;
            lazyBuilders.forEach(simpleBuildContext::setupLazyNodeData);
        }

        Set pendingForBuilding = stream(sources).collect(toSet());

        while (!pendingForBuilding.isEmpty()) {
            logger.debug("building source:{}", pendingForBuilding);
            // namespace->ids
            Map> idsMap = new HashMap<>();
            // namespace->values
            Map> valuesMap = new HashMap<>();

            for (Object object : pendingForBuilding) {
                extract(object, buildContext, idsMap, valuesMap);
            }

            logger.debug("extracted data, id:{}", idsMap);
            logger.debug("extracted data, values:{}", valuesMap);
            valueBuild(idsMap, valuesMap, buildContext);
            logger.debug("after value build:{}", valuesMap);
            mergeToBuildContext(valuesMap, buildContext);

            pendingForBuilding = valuesMap.values().stream().flatMap(map -> map.values().stream())
                    .collect(toSet());
        }
    }

    private void mergeToBuildContext(Map> valuesMap, B buildContext) {
        valuesMap.forEach(
                (valueNamespace, values) -> buildContext.getData(valueNamespace).putAll(values));
    }

    private void valueBuild(Map> idsMap,
            Map> valuesMap, B buildContext) {
        idsMap.forEach((idNamespace, ids) -> valueBuilders.get(idNamespace)
                .forEach(valueBuilderWrapper -> {
                    Object valueNamespace = valueBuilderWrapper.getKey();
                    BiFunction, Map> valueBuilder = valueBuilderWrapper
                            .getValue();
                    Set needToBuildIds = filterIdSetOnBuild(ids, buildContext, valuesMap,
                            valueNamespace);
                    Map values = valueBuilder.apply(buildContext, needToBuildIds);
                    if (values != null) {
                        valuesMap.merge(valueNamespace, values, MergeUtils::merge);
                    }
                }));
    }

    private Set filterIdSetOnBuild(Set original, B buildContext,
            Map> valuesMap, Object valueNamespace) {
        Set buildContextExistIds = buildContext.getData(valueNamespace).keySet();
        Set valueMapExistIds = valuesMap
                .computeIfAbsent(valueNamespace, i -> new HashMap<>()).keySet();
        return original.stream() //
                .filter(id -> !buildContextExistIds.contains(id)) //
                .filter(id -> !valueMapExistIds.contains(id)) //
                .collect(toSet());
    }

    // return new found data.
    private void extract(Object obj, B buildContext, Map> idsMap,
            Map> valuesMap) {
        if (obj == null) {
            return;
        }
        cachedValueExtractors
                .computeIfAbsent(obj.getClass(),
                        t -> getAllSuperTypes(t).stream()
                                .flatMap(i -> valueExtractors.get(i).stream()).collect(toSet()))
                .forEach(valueExtractor -> {
                    KeyPair> values = valueExtractor.apply(obj);
                    Map filtered = filterValueMap(values, buildContext);
                    idsMap.merge(values.getKey(), new HashSet<>(filtered.keySet()),
                            MergeUtils::merge);
                    valuesMap.merge(values.getKey(), filtered, MergeUtils::merge);
                });
        cachedIdExtractors
                .computeIfAbsent(obj.getClass(), t -> getAllSuperTypes(t).stream()
                        .flatMap(i -> idExtractors.get(i).stream()).collect(toSet()))
                .forEach(idExtractor -> {
                    KeyPair> ids = idExtractor.apply(obj);
                    idsMap.merge(ids.getKey(), filterIdSet(ids, buildContext, valuesMap),
                            MergeUtils::merge);
                });
    }

    private Set filterIdSet(KeyPair> keyPair, B buildContext,
            Map> valuesMap) {
        Set buildContextExistIds = buildContext.getData(keyPair.getKey()).keySet();
        Set valueMapExistIds = valuesMap
                .computeIfAbsent(keyPair.getKey(), i -> new HashMap<>()).keySet();
        return keyPair.getValue().stream() //
                .filter(i -> !buildContextExistIds.contains(i)) //
                .filter(i -> !valueMapExistIds.contains(i)) //
                .collect(toSet());
    }

    private Map filterValueMap(KeyPair> keyPair,
            B buildContext) {
        Map buildContextData = buildContext.getData(keyPair.getKey());
        return keyPair.getValue().entrySet().stream()
                .filter(e -> !buildContextData.containsKey(e.getKey()))
                .collect(toMap(Entry::getKey, Entry::getValue));
    }

    /**
     * use {@link #extractId} or {@link #extractValue}
     */
    @Deprecated
    public  OnBuilder on(Class type) {
        return new OnBuilder<>(type);
    }

    /**
     * use {@link #valueFromSelf}
     */
    @Deprecated
    public  SimpleModelBuilder self(Class type, Function idExtractor) {
        SimpleModelBuilder.OnBuilder onBuilder = new OnBuilder<>(type);
        return onBuilder.new ExtractingValue(i -> i).id(idExtractor).to(type);
    }

    /**
     * use {@link #buildValue} or {@link #buildValueTo}
     */
    @Deprecated
    public BuildingBuilder build(Object idNamespace) {
        return new BuildingBuilder(idNamespace);
    }

    /**
     * use {@link #buildValue} or {@link #buildValueTo}
     */
    @Deprecated
    public  SimpleModelBuilder build(Object idNamespace,
            BiFunction, Map> valueBuilder) {
        return build(idNamespace).by(valueBuilder).to(idNamespace);
    }

    /**
     * use {@link #buildValue} or {@link #buildValueTo}
     */
    @Deprecated
    public  SimpleModelBuilder build(Object idNamespace,
            Function, Map> valueBuilder) {
        return build(idNamespace).by(valueBuilder).to(idNamespace);
    }

    /**
     * use {@link #lazyBuild}
     */
    @SuppressWarnings("rawtypes")
    @Deprecated
    public SimpleModelBuilder lazy(Lazy lazy) {
        lazyBuilders.put(lazy.targetNamespace(), buildContext -> (Map) ((BiFunction) lazy.builder())
                .apply(buildContext, buildContext.getData(lazy.sourceNamespace()).keySet()));
        return this;
    }

    public  SimpleModelBuilder valueFromSelf(Class type, Function idExtractor) {
        self(type, idExtractor);
        return this;
    }

    public  SimpleModelBuilder extractId(Class type, Function idExtractor,
            Object toIdNamespace) {
        on(type).id(idExtractor).to(toIdNamespace);
        return this;
    }

    public  SimpleModelBuilder extractValue(Class type,
            Function valueExtractor, Function idExtractor,
            Object toValueNamespace) {
        on(type).value(valueExtractor).id(idExtractor).to(toValueNamespace);
        return this;
    }

    public  SimpleModelBuilder buildValue(Object idNamespace,
            Function, Map> valueBuilder) {
        build(idNamespace, valueBuilder);
        return this;
    }

    public  SimpleModelBuilder buildValue(Object idNamespace,
            BiFunction, Map> valueBuilder) {
        build(idNamespace, valueBuilder);
        return this;
    }

    public  SimpleModelBuilder buildValueTo(Object idNamespace,
            Function, Map> valueBuilder, Object toValueNamespace) {
        build(idNamespace).by(valueBuilder).to(toValueNamespace);
        return this;
    }

    public  SimpleModelBuilder buildValueTo(Object idNamespace,
            BiFunction, Map> valueBuilder, Object toValueNamespace) {
        build(idNamespace).by(valueBuilder).to(toValueNamespace);
        return this;
    }

    public  SimpleModelBuilder lazyBuild(Object sourceNamespace,
            Function, Map> builder, Object targetNamespace) {
        lazy(LazyBuilder.on(sourceNamespace, builder, targetNamespace));
        return this;
    }

    public  SimpleModelBuilder lazyBuild(Object sourceNamespace,
            BiFunction, Map> builder, Object targetNamespace) {
        lazy(LazyBuilder.on(sourceNamespace, builder, targetNamespace));
        return this;
    }

    private Set> getAllSuperTypes(Class iface) {
        Set> classes = new HashSet<>();
        classes.add(iface);
        classes.addAll(getAllInterfaces(iface));
        classes.addAll(getAllSuperclasses(iface));
        return classes;
    }

    private  Stream stream(Iterable iterable) {
        return StreamSupport.stream(
                spliteratorUnknownSize(iterable.iterator(), (NONNULL | IMMUTABLE | ORDERED)),
                false);
    }

    // builder.onLazy(UserCache.class).fromId(ids->dao.build(ids)).to("test");

    @Override
    public String toString() {
        return reflectionToString(this, SHORT_PREFIX_STYLE);
    }

    interface Lazy {

        Object sourceNamespace();

        Object targetNamespace();

        BiFunction builder();
    }

    private static final class KeyPair implements Entry {

        private final Object key;
        private final V value;

        private KeyPair(Object key, V value) {
            this.key = key;
            this.value = value;
        }

        public Object getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(V value) {
            throw new UnsupportedOperationException();
        }
    }

    @Deprecated
    public final class OnBuilder {

        private final Class objType;

        private OnBuilder(Class objType) {
            this.objType = objType;
        }

        public ExtractingId id(Function idExtractor) {
            return new ExtractingId(idExtractor);
        }

        public ExtractingValue value(Function valueExtractor) {
            return new ExtractingValue(valueExtractor);
        }

        public  ExtractingValue value(Function> valueExtractor,
                Function idExtractor) {
            return new ExtractingValue(valueExtractor).id(idExtractor);
        }

        public final class ExtractingValue {

            private final Function valueExtractor;
            private Function idExtractor;

            private ExtractingValue(Function valueExtractor) {
                this.valueExtractor = (Function) valueExtractor;
            }

            public  ExtractingValue id(Function idExtractor) {
                this.idExtractor = (Function) idExtractor;
                return this;
            }

            public SimpleModelBuilder to(Object valueNamespace) {
                valueExtractors.put(objType, obj -> {
                    Object rawValue = valueExtractor.apply((E) obj);
                    Map value;
                    if (rawValue == null) {
                        value = emptyMap();
                    } else {
                        if (idExtractor != null) {
                            if (rawValue instanceof Iterable) {
                                value = stream((Iterable) rawValue)
                                        .collect(toMap(idExtractor::apply, identity()));
                            } else {
                                value = singletonMap(idExtractor.apply(rawValue), rawValue);
                            }
                        } else {
                            if (rawValue instanceof Map) {
                                value = (Map) rawValue;
                            } else {
                                logger.warn("invalid value extractor for:{}->{}", obj, rawValue);
                                value = emptyMap();
                            }
                        }
                    }
                    return new KeyPair<>(valueNamespace, value);
                });
                cachedValueExtractors.clear();
                return SimpleModelBuilder.this;
            }
        }

        public final class ExtractingId {

            private final Function idExtractor;

            private ExtractingId(Function idExtractor) {
                this.idExtractor = idExtractor;
            }

            public SimpleModelBuilder to(Object idNamespace) {
                idExtractors.put(objType, obj -> {
                    Object rawId = idExtractor.apply((E) obj);
                    Set ids;
                    if (rawId == null) {
                        ids = emptySet();
                    } else {
                        if (rawId instanceof Iterable) {
                            ids = stream((Iterable) rawId).collect(toSet());
                        } else {
                            ids = singleton(rawId);
                        }
                    }
                    return new KeyPair<>(idNamespace, ids);
                });
                cachedIdExtractors.clear();
                return SimpleModelBuilder.this;
            }
        }
    }

    @Deprecated
    public final class BuildingBuilder {

        private final Object idNamespace;

        private BuildingBuilder(Object idNamespace) {
            this.idNamespace = idNamespace;
        }

        @SuppressWarnings("rawtypes")
        public  BuildingValue by(Function, Map> valueBuilder) {
            return new BuildingValue<>((c, ids) -> (Map) valueBuilder.apply(ids));
        }

        @SuppressWarnings("rawtypes")
        public  BuildingValue by(BiFunction, Map> valueBuilder) {
            return new BuildingValue<>((BiFunction) valueBuilder);
        }

        public final class BuildingValue {

            private final BiFunction, Map> valueBuilderFunction;

            private BuildingValue(
                    BiFunction, Map> valueBuilderFunction) {
                this.valueBuilderFunction = valueBuilderFunction;
            }

            @SuppressWarnings("rawtypes")
            public SimpleModelBuilder to(Object valueNamespace) {
                valueBuilders.put(idNamespace, new KeyPair(valueNamespace, valueBuilderFunction));
                return SimpleModelBuilder.this;
            }
        }
    }
}