1 package pl.polidea.treeview;
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.HashSet;
12 import android.database.DataSetObserver;
15 * In-memory manager of tree state.
20 public class InMemoryTreeStateManager<T> implements TreeStateManager<T> {
21 private static final long serialVersionUID = 1L;
22 private final Map<T, InMemoryTreeNode<T>> allNodes = new HashMap<T, InMemoryTreeNode<T>>();
23 private final InMemoryTreeNode<T> topSentinel = new InMemoryTreeNode<T>(
24 null, null, -1, true);
25 private transient List<T> visibleListCache = null; // lasy initialised
26 private transient List<T> unmodifiableVisibleList = null;
27 private boolean visibleByDefault = true;
28 private final transient Set<DataSetObserver> observers = new HashSet<DataSetObserver>();
30 private synchronized void internalDataSetChanged() {
31 visibleListCache = null;
32 unmodifiableVisibleList = null;
33 for (final DataSetObserver observer : observers) {
39 * If true new nodes are visible by default.
41 * @param visibleByDefault
42 * if true, then newly added nodes are expanded by default
44 public void setVisibleByDefault(final boolean visibleByDefault) {
45 this.visibleByDefault = visibleByDefault;
48 private InMemoryTreeNode<T> getNodeFromTreeOrThrow(final T id) {
50 throw new NodeNotInTreeException("(null)");
52 final InMemoryTreeNode<T> node = allNodes.get(id);
54 throw new NodeNotInTreeException(id.toString());
59 private InMemoryTreeNode<T> getNodeFromTreeOrThrowAllowRoot(final T id) {
63 return getNodeFromTreeOrThrow(id);
66 private void expectNodeNotInTreeYet(final T id) {
67 final InMemoryTreeNode<T> node = allNodes.get(id);
69 throw new NodeAlreadyInTreeException(id.toString(), node.toString());
74 public synchronized TreeNodeInfo<T> getNodeInfo(final T id) {
75 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrow(id);
76 final List<InMemoryTreeNode<T>> children = node.getChildren();
77 boolean expanded = false;
78 if (!children.isEmpty() && children.get(0).isVisible()) {
81 return new TreeNodeInfo<T>(id, node.getLevel(), !children.isEmpty(),
82 node.isVisible(), expanded);
86 public synchronized List<T> getChildren(final T id) {
87 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
88 return node.getChildIdList();
92 public synchronized T getParent(final T id) {
93 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
94 return node.getParent();
97 private boolean getChildrenVisibility(final InMemoryTreeNode<T> node) {
99 final List<InMemoryTreeNode<T>> children = node.getChildren();
100 if (children.isEmpty()) {
101 visibility = visibleByDefault;
103 visibility = children.get(0).isVisible();
109 public synchronized void addBeforeChild(final T parent, final T newChild,
110 final T beforeChild) {
111 expectNodeNotInTreeYet(newChild);
112 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(parent);
113 final boolean visibility = getChildrenVisibility(node);
114 // top nodes are always expanded.
115 if (beforeChild == null) {
116 final InMemoryTreeNode<T> added = node.add(0, newChild, visibility);
117 allNodes.put(newChild, added);
119 final int index = node.indexOf(beforeChild);
120 final InMemoryTreeNode<T> added = node.add(index == -1 ? 0 : index,
121 newChild, visibility);
122 allNodes.put(newChild, added);
125 internalDataSetChanged();
130 public synchronized void addAfterChild(final T parent, final T newChild,
131 final T afterChild) {
132 expectNodeNotInTreeYet(newChild);
133 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(parent);
134 final boolean visibility = getChildrenVisibility(node);
135 if (afterChild == null) {
136 final InMemoryTreeNode<T> added = node.add(
137 node.getChildrenListSize(), newChild, visibility);
138 allNodes.put(newChild, added);
140 final int index = node.indexOf(afterChild);
141 final InMemoryTreeNode<T> added = node.add(
142 index == -1 ? node.getChildrenListSize() : index, newChild,
144 allNodes.put(newChild, added);
147 internalDataSetChanged();
152 public synchronized void removeNodeRecursively(final T id) {
153 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
154 final boolean visibleNodeChanged = removeNodeRecursively(node);
155 final T parent = node.getParent();
156 final InMemoryTreeNode<T> parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
157 parentNode.removeChild(id);
158 if (visibleNodeChanged) {
159 internalDataSetChanged();
163 private boolean removeNodeRecursively(final InMemoryTreeNode<T> node) {
164 boolean visibleNodeChanged = false;
165 for (final InMemoryTreeNode<T> child : node.getChildren()) {
166 if (removeNodeRecursively(child)) {
167 visibleNodeChanged = true;
170 node.clearChildren();
171 if (node.getId() != null) {
172 allNodes.remove(node.getId());
173 if (node.isVisible()) {
174 visibleNodeChanged = true;
177 return visibleNodeChanged;
180 private void setChildrenVisibility(final InMemoryTreeNode<T> node,
181 final boolean visible, final boolean recursive) {
182 for (final InMemoryTreeNode<T> child : node.getChildren()) {
183 child.setVisible(visible);
185 setChildrenVisibility(child, visible, true);
191 public synchronized void expandDirectChildren(final T id) {
192 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
193 setChildrenVisibility(node, true, false);
194 internalDataSetChanged();
198 public synchronized void expandEverythingBelow(final T id) {
199 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
200 setChildrenVisibility(node, true, true);
201 internalDataSetChanged();
205 public synchronized void collapseChildren(final T id) {
206 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
207 if (node == topSentinel) {
208 for (final InMemoryTreeNode<T> n : topSentinel.getChildren()) {
209 setChildrenVisibility(n, false, true);
212 setChildrenVisibility(node, false, true);
214 internalDataSetChanged();
218 public synchronized T getNextSibling(final T id) {
219 final T parent = getParent(id);
220 final InMemoryTreeNode<T> parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
221 boolean returnNext = false;
222 for (final InMemoryTreeNode<T> child : parentNode.getChildren()) {
224 return child.getId();
226 if (child.getId().equals(id)) {
234 public synchronized T getPreviousSibling(final T id) {
235 final T parent = getParent(id);
236 final InMemoryTreeNode<T> parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
237 final T previousSibling = null;
238 for (final InMemoryTreeNode<T> child : parentNode.getChildren()) {
239 if (child.getId().equals(id)) {
240 return previousSibling;
247 public synchronized boolean isInTree(final T id) {
248 return allNodes.containsKey(id);
252 public synchronized int getVisibleCount() {
253 return getVisibleList().size();
257 public synchronized List<T> getVisibleList() {
259 if (visibleListCache == null) {
260 visibleListCache = new ArrayList<T>(allNodes.size());
262 currentId = getNextVisible(currentId);
263 if (currentId == null) {
266 visibleListCache.add(currentId);
270 if (unmodifiableVisibleList == null) {
271 unmodifiableVisibleList = Collections
272 .unmodifiableList(visibleListCache);
274 return unmodifiableVisibleList;
277 public synchronized T getNextVisible(final T id) {
278 final InMemoryTreeNode<T> node = getNodeFromTreeOrThrowAllowRoot(id);
279 if (!node.isVisible()) {
282 final List<InMemoryTreeNode<T>> children = node.getChildren();
283 if (!children.isEmpty()) {
284 final InMemoryTreeNode<T> firstChild = children.get(0);
285 if (firstChild.isVisible()) {
286 return firstChild.getId();
289 final T sibl = getNextSibling(id);
293 T parent = node.getParent();
295 if (parent == null) {
298 final T parentSibling = getNextSibling(parent);
299 if (parentSibling != null) {
300 return parentSibling;
302 parent = getNodeFromTreeOrThrow(parent).getParent();
307 public synchronized void registerDataSetObserver(
308 final DataSetObserver observer) {
309 observers.add(observer);
313 public synchronized void unregisterDataSetObserver(
314 final DataSetObserver observer) {
315 observers.remove(observer);
319 public int getLevel(final T id) {
320 return getNodeFromTreeOrThrow(id).getLevel();
324 public Integer[] getHierarchyDescription(final T id) {
325 final int level = getLevel(id);
326 final Integer[] hierarchy = new Integer[level + 1];
327 int currentLevel = level;
329 T parent = getParent(currentId);
330 while (currentLevel >= 0) {
331 hierarchy[currentLevel--] = getChildren(parent).indexOf(currentId);
333 parent = getParent(parent);
338 private void appendToSb(final StringBuilder sb, final T id) {
340 final TreeNodeInfo<T> node = getNodeInfo(id);
341 final int indent = node.getLevel() * 4;
342 final char[] indentString = new char[indent];
343 Arrays.fill(indentString, ' ');
344 sb.append(indentString);
345 sb.append(node.toString());
346 sb.append(Arrays.asList(getHierarchyDescription(id)).toString());
349 final List<T> children = getChildren(id);
350 for (final T child : children) {
351 appendToSb(sb, child);
356 public synchronized String toString() {
357 final StringBuilder sb = new StringBuilder();
358 appendToSb(sb, null);
359 return sb.toString();
363 public synchronized void clear() {
365 topSentinel.clearChildren();
366 internalDataSetChanged();
370 public void refresh() {
371 internalDataSetChanged();