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

org.finos.legend.pure.m4.tools.GraphNodeIterable Maven / Gradle / Ivy

There is a newer version: 5.17.0
Show newest version
// Copyright 2020 Goldman Sachs
//
// 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 org.finos.legend.pure.m4.tools;

import org.eclipse.collections.api.LazyIterable;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.factory.Sets;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.set.MutableSet;
import org.eclipse.collections.impl.set.mutable.SynchronizedMutableSet;
import org.eclipse.collections.impl.utility.Iterate;
import org.finos.legend.pure.m4.ModelRepository;
import org.finos.legend.pure.m4.coreinstance.CoreInstance;

import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.Objects;
import java.util.Spliterator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * An iterable that iterates through the nodes of a graph, starting from a given set of nodes and traversing to
 * connected nodes. The traversal of the graph can be controlled by providing a
 * {@link java.util.function.Function function} from nodes to {@link GraphWalkFilterResult GraphWalkFilterResults}.
 */
public class GraphNodeIterable extends AbstractLazySpliterable
{
    private final ImmutableList startingNodes;
    private final BiPredicate keyFilter;
    private final Function nodeFilter;

    private GraphNodeIterable(ImmutableList startingNodes, BiPredicate keyFilter, Function nodeFilter)
    {
        this.startingNodes = Lists.immutable.withAll(startingNodes);
        this.keyFilter = (keyFilter == null) ? (n, k) -> true : keyFilter;
        this.nodeFilter = nodeFilter;
    }

    @Override
    public int size()
    {
        return computeClosure().size();
    }

    @Override
    public boolean isEmpty()
    {
        return this.startingNodes.isEmpty() || ((this.nodeFilter != null) && !stream().findAny().isPresent());
    }
    
    @Override
    public CoreInstance getAny()
    {
        if (this.startingNodes.isEmpty())
        {
            return null;
        }
        if (this.nodeFilter == null)
        {
            return this.startingNodes.getAny();
        }
        return super.getAny();
    }

    @Override
    public CoreInstance getFirst()
    {
        if (this.startingNodes.isEmpty())
        {
            return null;
        }
        if (this.nodeFilter == null)
        {
            return this.startingNodes.getFirst();
        }
        return super.getFirst();
    }

    @Override
    public boolean contains(Object object)
    {
        return (object instanceof CoreInstance) && super.contains(object);
    }

    @Override
    public LazyIterable distinct()
    {
        return this;
    }

    @Override
    public MutableList toList()
    {
        return Lists.mutable.withAll(computeClosure());
    }

    @Override
    public MutableSet toSet()
    {
        return computeClosure();
    }

    @Override
    public Object[] toArray()
    {
        return computeClosure().toArray();
    }

    @Override
    public  E[] toArray(E[] array)
    {
        return computeClosure().toArray(array);
    }

    @Override
    public Spliterator spliterator()
    {
        return new GraphNodeSpliterator(this.startingNodes, this.keyFilter, this.nodeFilter);
    }

    private MutableSet computeClosure()
    {
        return computeClosure(this.startingNodes, this.keyFilter, this.nodeFilter);
    }

    @Deprecated
    public static GraphNodeIterable fromNode(CoreInstance startingNode)
    {
        return builder().withStartingNode(startingNode).build();
    }

    @Deprecated
    public static GraphNodeIterable fromNode(CoreInstance startingNode, Function filter)
    {
        return builder().withStartingNode(startingNode).withNodeFilter(filter).build();
    }

    @Deprecated
    public static GraphNodeIterable fromNodes(CoreInstance... startingNodes)
    {
        return builder().withStartingNodes(startingNodes).build();
    }

    @Deprecated
    public static GraphNodeIterable fromNodes(Iterable startingNodes)
    {
        return builder().withStartingNodes(startingNodes).build();
    }

    @Deprecated
    public static GraphNodeIterable fromNodes(Iterable startingNodes, Function filter)
    {
        return builder().withStartingNodes(startingNodes).withNodeFilter(filter).build();
    }

    public static GraphNodeIterable fromModelRepository(ModelRepository repository)
    {
        return fromModelRepository(repository, null, null);
    }

    public static GraphNodeIterable fromModelRepository(ModelRepository repository, Function nodeFilter)
    {
        return fromModelRepository(repository, null, nodeFilter);
    }

    public static GraphNodeIterable fromModelRepository(ModelRepository repository, BiPredicate keyFilter, Function nodeFilter)
    {
        return builder()
                .withStartingNodes(repository.getTopLevels())
                .withKeyFilter(keyFilter)
                .withNodeFilter(nodeFilter)
                .build();
    }

    public static MutableSet allInstancesFromRepository(ModelRepository repository)
    {
        return allConnectedInstances(repository.getTopLevels());
    }

    public static MutableSet allConnectedInstances(Iterable startingNodes)
    {
        return allConnectedInstances(startingNodes, null, null);
    }

    public static MutableSet allConnectedInstances(Iterable startingNodes, BiPredicate keyFilter)
    {
        return allConnectedInstances(startingNodes, keyFilter, null);
    }

    public static MutableSet allConnectedInstances(Iterable startingNodes, Function nodeFilter)
    {
        return allConnectedInstances(startingNodes, null, nodeFilter);
    }

    public static MutableSet allConnectedInstances(Iterable startingNodes, BiPredicate keyFilter, Function nodeFilter)
    {
        return computeClosure(startingNodes, keyFilter, nodeFilter);
    }

    private static MutableSet computeClosure(Iterable startingNodes, BiPredicate keyFilter, Function nodeFilter)
    {
        GraphNodeSpliterator spliterator = new GraphNodeSpliterator(startingNodes, keyFilter, nodeFilter);
        spliterator.forEachRemaining(n ->
        {
            // Do nothing: collect instances by side effect
        });
        return spliterator.visited;
    }

    private static class GraphNodeSpliterator implements Spliterator
    {
        private final Deque deque;
        private MutableSet visited;
        private final BiPredicate keyFilter;
        private final Function nodeFilter;

        private GraphNodeSpliterator(Deque deque, MutableSet visited, BiPredicate keyFilter, Function nodeFilter)
        {
            this.deque = deque;
            this.visited = visited;
            this.keyFilter = (keyFilter == null) ? (n, k) -> true : keyFilter;
            this.nodeFilter = nodeFilter;
        }

        private GraphNodeSpliterator(Iterable startingNodes, BiPredicate keyFilter, Function nodeFilter)
        {
            this(Iterate.addAllTo(startingNodes, new ArrayDeque<>()), Sets.mutable.empty(), keyFilter, nodeFilter);
        }

        @Override
        public boolean tryAdvance(Consumer action)
        {
            while (!this.deque.isEmpty())
            {
                CoreInstance node = this.deque.pollFirst();
                if (this.visited.add(node))
                {
                    GraphWalkFilterResult filterResult = filterNode(node);
                    if (filterResult.shouldContinue())
                    {
                        node.getKeys().forEach(key ->
                        {
                            if (this.keyFilter.test(node, key))
                            {
                                node.getValueForMetaPropertyToMany(key).forEach(v ->
                                {
                                    if (!this.visited.contains(v))
                                    {
                                        this.deque.addLast(v);
                                    }
                                });
                            }
                        });
                    }
                    if (filterResult.shouldAccept())
                    {
                        action.accept(node);
                        return true;
                    }
                }
            }
            return false;
        }

        @Override
        public Spliterator trySplit()
        {
            if (this.deque.size() < 2)
            {
                return null;
            }

            int splitSize = this.deque.size() / 2;
            Deque newDeque = new ArrayDeque<>(splitSize);
            for (int i = 0; i < splitSize; i++)
            {
                newDeque.addFirst(this.deque.pollLast());
            }
            // If we are going to split, we need to make sure the visited set is synchronized
            if (!(this.visited instanceof SynchronizedMutableSet))
            {
                this.visited = SynchronizedMutableSet.of(this.visited, this.visited);
            }
            return new GraphNodeSpliterator(newDeque, this.visited, this.keyFilter, this.nodeFilter);
        }

        @Override
        public long estimateSize()
        {
            return this.deque.isEmpty() ? 0L : Long.MAX_VALUE;
        }

        @Override
        public long getExactSizeIfKnown()
        {
            return this.deque.isEmpty() ? 0L : -1L;
        }

        @Override
        public int characteristics()
        {
            return NONNULL | DISTINCT;
        }

        private GraphWalkFilterResult filterNode(CoreInstance node)
        {
            if (this.nodeFilter != null)
            {
                GraphWalkFilterResult result = this.nodeFilter.apply(node);
                if (result != null)
                {
                    return result;
                }
            }
            return GraphWalkFilterResult.ACCEPT_AND_CONTINUE;
        }
    }

    public static Builder builder()
    {
        return new Builder();
    }

    public static class Builder
    {
        private final MutableList startNodes = Lists.mutable.empty();
        private BiPredicate keyFilter;
        private Function nodeFilter;

        private Builder()
        {
        }

        public Builder withKeyFilter(BiPredicate keyFilter)
        {
            this.keyFilter = keyFilter;
            return this;
        }

        public Builder withNodeFilter(Function nodeFilter)
        {
            this.nodeFilter = nodeFilter;
            return this;
        }

        public Builder withStartingNode(CoreInstance node)
        {
            this.startNodes.add(Objects.requireNonNull(node));
            return this;
        }

        public Builder withStartingNodes(Iterable nodes)
        {
            nodes.forEach(this::withStartingNode);
            return this;
        }

        public Builder withStartingNodes(CoreInstance... startingNodes)
        {
            return withStartingNodes(Arrays.asList(startingNodes));
        }

        public GraphNodeIterable build()
        {
            return new GraphNodeIterable(this.startNodes.toImmutable(), this.keyFilter, this.nodeFilter);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy