com.github.helenusdriver.commons.collections.graph.ConcurrentHashDirectedGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.github.helenusdriver.commons.collections.graph.ConcurrentHashDirectedGraph.java

Source

/*
 * Copyright (C) 2015-2015 The Helenus Driver Project Authors.
 *
 * 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.github.helenusdriver.commons.collections.graph;

import java.io.Serializable;

import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.github.helenusdriver.commons.collections.DirectedGraph;

/**
 * The <code>ConcurrentHashDirectedGraph</code> class provides an implementation
 * of the {@link DirectedGraph} interface that uses a {@link ConcurrentHashMap}
 * to keep track of the graph.
 *
 * @copyright 2015-2015 The Helenus Driver Project Authors
 *
 * @author  The Helenus Driver Project Authors
 * @version 1 - Jan 15, 2015 - paouelle - Creation
 *
 * @param <T> the type of elements in this graph
 *
 * @since 2.0
 */
public class ConcurrentHashDirectedGraph<T> implements DirectedGraph<T>, Cloneable, Serializable {
    /**
     * Holds the serialVersionUID.
     *
     * @author paouelle
     */
    private static final long serialVersionUID = -8430194904167408569L;

    /**
     * The <code>HashNode</code> class provides an implementation for a graph node.
     *
     * @copyright 2015-2015 The Helenus Driver Project Authors
     *
     * @author  The Helenus Driver Project Authors
     * @version 1 - Jan 15, 2015 - paouelle - Creation
     *
     * @since 2.0
     */
    private class HashNode implements Node<T>, Cloneable, Serializable {
        /**
         * Holds the serialVersionUID.
         *
         * @author paouelle
         */
        private static final long serialVersionUID = 2377326682367937940L;

        /**
         * Holds the value for this node.
         *
         * @author paouelle
         */
        private final T value;

        /**
         * Holds the edges from this node.
         *
         * @author paouelle
         */
        @SuppressWarnings("synthetic-access")
        private final HashSet<HashNode> edges = new HashSet<>(Math.min(initialCapacity / 16, 8), loadFactor);

        /**
         * Instantiates a new <code>HashNode</code> object.
         *
         * @author paouelle
         *
         * @param value the value for this node.
         */
        HashNode(T value) {
            this.value = value;
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#getValue()
         */
        @Override
        public T getValue() {
            return value;
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#edgeExists(java.lang.Object)
         */
        @Override
        public boolean edgeExists(T dest) {
            @SuppressWarnings("synthetic-access")
            final HashNode node = graph.get(dest);

            if (node == null) {
                throw new NoSuchElementException("unknown destination node");
            }
            return edges.contains(node);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#getEdges()
         */
        @Override
        public Set<DirectedGraph.Node<T>> getEdges() {
            return Collections.unmodifiableSet(edges);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#edges()
         */
        @Override
        public Stream<DirectedGraph.Node<T>> edges() {
            return edges.stream().map(n -> n);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#add(java.lang.Object)
         */
        @Override
        public void add(T dest) {
            HashNode node = new HashNode(dest);
            @SuppressWarnings("synthetic-access")
            final HashNode old = graph.putIfAbsent(dest, node);

            if (old != null) { // we already had one so continue with it
                node = old;
            }
            edges.add(node);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#addEdge(java.lang.Object)
         */
        @Override
        public void addEdge(T dest) {
            @SuppressWarnings("synthetic-access")
            final HashNode node = graph.get(dest);

            if (node == null) {
                throw new NoSuchElementException("unknown destination node");
            }
            edges.add(node);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see com.github.helenusdriver.commons.collections.DirectedGraph.Node#removeEdge(java.lang.Object)
         */
        @Override
        public boolean removeEdge(T dest) {
            @SuppressWarnings("synthetic-access")
            final HashNode node = graph.get(dest);

            if (node == null) {
                throw new NoSuchElementException("unknown destination node");
            }
            return edges.remove(node);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return Objects.hashCode(value);
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        public boolean equals(Object obj) {
            return obj == this;
        }

        /**
         * {@inheritDoc}
         *
         * @author paouelle
         *
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return edges.stream().map(e -> String.valueOf(e.getValue()))
                    .collect(Collectors.joining("-", value + "=", ""));
        }
    }

    /**
     * Holds the load factor for this graph.
     *
     * @author paouelle
     */
    private final float loadFactor;

    /**
     * Holds the initial capacity for this graph.
     *
     * @author paouelle
     */
    private final int initialCapacity;

    /**
     * Holds the graph.
     *
     * @author paouelle
     */
    private final Map<T, HashNode> graph;

    /**
     * Holds the node set view of this graph.
     *
     * @author paouelle
     */
    private transient Set<Node<T>> nodeSet = null;

    /**
     * Instantiates a new <code>ConcurrentHashDirectedGraph</code> object with
     * the default initial capacity (16) and the default load factor (0.75).
     *
     * @author paouelle
     */
    public ConcurrentHashDirectedGraph() {
        this(16, 0.75f);
    }

    /**
     * Instantiates a new <code>ConcurrentHashDirectedGraph</code> object with the
     * specified initial capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity
     * @throws IllegalArgumentException if the initial capacity is negative
     */
    public ConcurrentHashDirectedGraph(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    /**
     * Instantiates a new <code>ConcurrentHashDirectedGraph</code> object with the
     * specified initial capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor he load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is non-positive
     */
    public ConcurrentHashDirectedGraph(int initialCapacity, float loadFactor) {
        this.loadFactor = loadFactor;
        this.initialCapacity = initialCapacity;
        this.graph = new ConcurrentHashMap<>(initialCapacity, loadFactor);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#size()
     */
    @Override
    public int size() {
        return graph.size();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#isEmpty()
     */
    @Override
    public boolean isEmpty() {
        return graph.isEmpty();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#contains(java.lang.Object)
     */
    @Override
    public boolean contains(Object o) {
        return graph.containsKey(o);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#iterator()
     */
    @Override
    public Iterator<T> iterator() {
        final Iterator<Map.Entry<T, HashNode>> i = graph.entrySet().iterator();

        return new Iterator<T>() {
            Map.Entry<T, HashNode> current = null;

            @Override
            public boolean hasNext() {
                return i.hasNext();
            }

            @Override
            public T next() {
                this.current = i.next();
                return current.getKey();
            }

            @Override
            @SuppressWarnings("synthetic-access")
            public void remove() {
                org.apache.commons.lang3.Validate.validState(current != null);
                i.remove();
                // we need to remove all edges to the removed node
                graph.values().stream().forEach(n -> n.edges.remove(current));
                this.current = null;
            }
        };
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Collection#stream()
     */
    @Override
    public Stream<T> stream() {
        return graph.keySet().stream();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Collection#parallelStream()
     */
    @Override
    public Stream<T> parallelStream() {
        return graph.keySet().parallelStream();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.lang.Iterable#forEach(java.util.function.Consumer)
     */
    @Override
    public void forEach(Consumer<? super T> action) {
        graph.keySet().forEach(action);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#toArray()
     */
    @Override
    public Object[] toArray() {
        return graph.keySet().toArray();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#toArray(java.lang.Object[])
     */
    @Override
    public <A> A[] toArray(A[] a) {
        return graph.keySet().toArray(a);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#get(java.lang.Object)
     */
    @Override
    public Node<T> get(T val) {
        return graph.get(val);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#add(java.lang.Object)
     */
    @Override
    public boolean add(T e) {
        return graph.putIfAbsent(e, new HashNode(e)) == null;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#remove(java.lang.Object)
     */
    @Override
    @SuppressWarnings("synthetic-access")
    public boolean remove(Object o) {
        final HashNode node = graph.remove(o);

        if (node != null) {
            // we need to remove all edges to the removed node
            graph.values().stream().forEach(n -> n.edges.remove(node));
            return true;
        }
        return false;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#containsAll(java.util.Collection)
     */
    @Override
    public boolean containsAll(Collection<?> c) {
        return graph.keySet().containsAll(c);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#addAll(java.util.Collection)
     */
    @Override
    public boolean addAll(Collection<? extends T> c) {
        boolean added = false;

        for (final T e : c) {
            if (add(e)) {
                added = true;
            }
        }
        return added;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#retainAll(java.util.Collection)
     */
    @Override
    public boolean retainAll(Collection<?> c) {
        boolean removed = false;

        for (final Iterator<T> i = iterator(); i.hasNext();) {
            if (!c.contains(i.next())) {
                i.remove();
                removed = true;
            }
        }
        return removed;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#removeAll(java.util.Collection)
     */
    @Override
    @SuppressWarnings("synthetic-access")
    public boolean removeAll(Collection<?> c) {
        boolean removed = false;

        for (final Iterator<Map.Entry<T, HashNode>> i = graph.entrySet().iterator(); i.hasNext();) {
            final Map.Entry<T, HashNode> e = i.next();

            if (c.contains(e.getKey())) {
                i.remove(); // remove the whole node
                removed = true;
            } else { // remove all edges to elements of c
                for (final Iterator<HashNode> j = e.getValue().edges.iterator(); j.hasNext();) {
                    if (c.contains(j.next().getValue())) { // remove the edge
                        j.remove();
                        removed = true;
                    }
                }
            }
        }
        return removed;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.util.Set#clear()
     */
    @Override
    public void clear() {
        graph.clear();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#nodeSet()
     */
    @Override
    @SuppressWarnings("synthetic-access")
    public Set<DirectedGraph.Node<T>> nodeSet() {
        final Set<Node<T>> ns;

        return (ns = nodeSet) == null ? (this.nodeSet = new NodeSet()) : ns;
    }

    @SuppressWarnings("javadoc")
    private final class NodeSet extends AbstractSet<Node<T>> {
        @Override
        @SuppressWarnings("synthetic-access")
        public final int size() {
            return graph.size();
        }

        @Override
        public final void clear() {
            ConcurrentHashDirectedGraph.this.clear();
        }

        @Override
        public final Iterator<Node<T>> iterator() {
            @SuppressWarnings("synthetic-access")
            final Iterator<Map.Entry<T, HashNode>> i = graph.entrySet().iterator();

            return new Iterator<Node<T>>() {
                Map.Entry<T, HashNode> current = null;

                @Override
                public boolean hasNext() {
                    return i.hasNext();
                }

                @Override
                public Node<T> next() {
                    this.current = i.next();
                    return current.getValue();
                }

                @Override
                @SuppressWarnings("synthetic-access")
                public void remove() {
                    org.apache.commons.lang3.Validate.validState(current != null);
                    i.remove();
                    // we need to remove all edges to the removed node
                    graph.values().stream().forEach(n -> n.edges.remove(current));
                    this.current = null;
                }
            };
        }

        @Override
        public final boolean contains(Object o) {
            if (!(o instanceof Node)) {
                return false;
            }
            return contains(((Node<?>) o).getValue());
        }

        @Override
        public final boolean remove(Object o) {
            if (o instanceof Node) {
                return remove(((Node<?>) o).getValue());
            }
            return false;
        }

        @Override
        @SuppressWarnings({ "rawtypes", "cast", "unchecked", "synthetic-access" })
        public final Spliterator<Node<T>> spliterator() {
            return (Spliterator<Node<T>>) (Spliterator) graph.values().spliterator();
        }

        @Override
        @SuppressWarnings("synthetic-access")
        public final void forEach(Consumer<? super Node<T>> action) {
            graph.values().forEach(action);
        }
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#add(java.lang.Object, java.lang.Object)
     */
    @Override
    public void add(T start, T dest) {
        HashNode sn = new HashNode(start);
        final HashNode old = graph.putIfAbsent(start, sn);

        if (old != null) { // we already had one so continue with it
            sn = old;
        }
        sn.add(dest);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#addEdge(java.lang.Object, java.lang.Object)
     */
    @Override
    public void addEdge(T start, T dest) {
        final HashNode sn = graph.get(start);

        if (sn == null) {
            throw new NoSuchElementException("unknown starting node");
        }
        sn.addEdge(dest);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#removeEdge(java.lang.Object, java.lang.Object)
     */
    @Override
    public boolean removeEdge(T start, T dest) {
        final HashNode sn = graph.get(start);

        if (sn == null) {
            throw new NoSuchElementException("unknown starting node");
        }
        return sn.removeEdge(dest);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#edgeExists(java.lang.Object, java.lang.Object)
     */
    @Override
    public boolean edgeExists(T start, T dest) {
        final HashNode sn = graph.get(start);

        if (sn == null) {
            throw new NoSuchElementException("unknown starting node");
        }
        return sn.edgeExists(dest);
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#edgesFrom(java.lang.Object)
     */
    @Override
    public Stream<T> edgesFrom(T node) {
        final HashNode n = graph.get(node);

        if (n == null) {
            throw new NoSuchElementException("unknown node");
        }
        return n.edges().map(e -> e.getValue());
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see com.github.helenusdriver.commons.collections.DirectedGraph#getEdgesFrom(java.lang.Object)
     */
    @Override
    public Set<T> getEdgesFrom(T node) {
        final HashNode n = graph.get(node);

        if (n == null) {
            throw new NoSuchElementException("unknown node");
        }
        return Collections
                .unmodifiableSet(n.getEdges().stream().map(e -> e.getValue()).collect(Collectors.toSet()));
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return super.hashCode();
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    /**
     * Returns a shallow copy of this <tt>ConcurrentHashDirectedGraph</tt>
     * instance: the values themselves are not cloned.
     *
     * @return a shallow copy of this graph
     */
    @Override
    public Object clone() {
        final ConcurrentHashDirectedGraph<T> clone = new ConcurrentHashDirectedGraph<>(initialCapacity, loadFactor);

        clone.addAll(graph.keySet());
        clone.nodeSet().forEach(n -> graph.get(n.getValue()).edges().forEach(e -> n.addEdge(e.getValue())));
        return clone;
    }

    /**
     * {@inheritDoc}
     *
     * @author paouelle
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return graph.values().stream()
                .map(n -> n.edges().map(e -> (e.getValue() == this) ? "(this graph)" : String.valueOf(e.getValue()))
                        .collect(Collectors.joining("-",
                                (n.getValue() == this) ? "(this graph)" : String.valueOf(n.getValue()) + '=', "")))
                .collect(Collectors.joining(", ", "{", "}"));
    }
}