• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.support.design.widget;
18 
19 import android.support.annotation.NonNull;
20 import android.support.annotation.Nullable;
21 import android.support.v4.util.Pools;
22 import android.support.v4.util.SimpleArrayMap;
23 
24 import java.util.ArrayList;
25 import java.util.HashSet;
26 import java.util.List;
27 
28 /**
29  * A class which represents a simple directed acyclic graph.
30  */
31 final class DirectedAcyclicGraph<T> {
32     private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10);
33     private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>();
34 
35     private final ArrayList<T> mSortResult = new ArrayList<>();
36     private final HashSet<T> mSortTmpMarked = new HashSet<>();
37 
38     /**
39      * Add a node to the graph.
40      *
41      * <p>If the node already exists in the graph then this method is a no-op.</p>
42      *
43      * @param node the node to add
44      */
addNode(@onNull T node)45     void addNode(@NonNull T node) {
46         if (!mGraph.containsKey(node)) {
47             mGraph.put(node, null);
48         }
49     }
50 
51     /**
52      * Returns true if the node is already present in the graph, false otherwise.
53      */
contains(@onNull T node)54     boolean contains(@NonNull T node) {
55         return mGraph.containsKey(node);
56     }
57 
58     /**
59      * Add an edge to the graph.
60      *
61      * <p>Both the given nodes should already have been added to the graph through
62      * {@link #addNode(Object)}.</p>
63      *
64      * @param node the parent node
65      * @param incomingEdge the node which has is an incoming edge to {@code node}
66      */
addEdge(@onNull T node, @NonNull T incomingEdge)67     void addEdge(@NonNull T node, @NonNull T incomingEdge) {
68         if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) {
69             throw new IllegalArgumentException("All nodes must be present in the graph before"
70                     + " being added as an edge");
71         }
72 
73         ArrayList<T> edges = mGraph.get(node);
74         if (edges == null) {
75             // If edges is null, we should try and get one from the pool and add it to the graph
76             edges = getEmptyList();
77             mGraph.put(node, edges);
78         }
79         // Finally add the edge to the list
80         edges.add(incomingEdge);
81     }
82 
83     /**
84      * Get any incoming edges from the given node.
85      *
86      * @return a list containing any incoming edges, or null if there are none.
87      */
88     @Nullable
getIncomingEdges(@onNull T node)89     List getIncomingEdges(@NonNull T node) {
90         return mGraph.get(node);
91     }
92 
93     /**
94      * Get any outgoing edges for the given node (i.e. nodes which have an incoming edge
95      * from the given node).
96      *
97      * @return a list containing any outgoing edges, or null if there are none.
98      */
99     @Nullable
getOutgoingEdges(@onNull T node)100     List<T> getOutgoingEdges(@NonNull T node) {
101         ArrayList<T> result = null;
102         for (int i = 0, size = mGraph.size(); i < size; i++) {
103             ArrayList<T> edges = mGraph.valueAt(i);
104             if (edges != null && edges.contains(node)) {
105                 if (result == null) {
106                     result = new ArrayList<>();
107                 }
108                 result.add(mGraph.keyAt(i));
109             }
110         }
111         return result;
112     }
113 
hasOutgoingEdges(@onNull T node)114     boolean hasOutgoingEdges(@NonNull T node) {
115         for (int i = 0, size = mGraph.size(); i < size; i++) {
116             ArrayList<T> edges = mGraph.valueAt(i);
117             if (edges != null && edges.contains(node)) {
118                 return true;
119             }
120         }
121         return false;
122     }
123 
124     /**
125      * Clears the internal graph, and releases resources to pools.
126      */
clear()127     void clear() {
128         for (int i = 0, size = mGraph.size(); i < size; i++) {
129             ArrayList<T> edges = mGraph.valueAt(i);
130             if (edges != null) {
131                 poolList(edges);
132             }
133         }
134         mGraph.clear();
135     }
136 
137     /**
138      * Returns a topologically sorted list of the nodes in this graph. This uses the DFS algorithm
139      * as described by Cormen et al. (2001). If this graph contains cyclic dependencies then this
140      * method will throw a {@link RuntimeException}.
141      *
142      * <p>The resulting list will be ordered such that index 0 will contain the node at the bottom
143      * of the graph. The node at the end of the list will have no dependencies on other nodes.</p>
144      */
145     @NonNull
getSortedList()146     ArrayList<T> getSortedList() {
147         mSortResult.clear();
148         mSortTmpMarked.clear();
149 
150         // Start a DFS from each node in the graph
151         for (int i = 0, size = mGraph.size(); i < size; i++) {
152             dfs(mGraph.keyAt(i), mSortResult, mSortTmpMarked);
153         }
154 
155         return mSortResult;
156     }
157 
dfs(final T node, final ArrayList<T> result, final HashSet<T> tmpMarked)158     private void dfs(final T node, final ArrayList<T> result, final HashSet<T> tmpMarked) {
159         if (result.contains(node)) {
160             // We've already seen and added the node to the result list, skip...
161             return;
162         }
163         if (tmpMarked.contains(node)) {
164             throw new RuntimeException("This graph contains cyclic dependencies");
165         }
166         // Temporarily mark the node
167         tmpMarked.add(node);
168         // Recursively dfs all of the node's edges
169         final ArrayList<T> edges = mGraph.get(node);
170         if (edges != null) {
171             for (int i = 0, size = edges.size(); i < size; i++) {
172                 dfs(edges.get(i), result, tmpMarked);
173             }
174         }
175         // Unmark the node from the temporary list
176         tmpMarked.remove(node);
177         // Finally add it to the result list
178         result.add(node);
179     }
180 
181     /**
182      * Returns the size of the graph
183      */
size()184     int size() {
185         return mGraph.size();
186     }
187 
188     @NonNull
getEmptyList()189     private ArrayList<T> getEmptyList() {
190         ArrayList<T> list = mListPool.acquire();
191         if (list == null) {
192             list = new ArrayList<>();
193         }
194         return list;
195     }
196 
poolList(@onNull ArrayList<T> list)197     private void poolList(@NonNull ArrayList<T> list) {
198         list.clear();
199         mListPool.release(list);
200     }
201 }