0f737aadd94b4ca7382fea71e610d39c50abfff9
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / IndexedForest.java
1 /* ownCloud Android client application
2 * Copyright (C) 2015 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 package com.owncloud.android.files.services;
19
20 import android.accounts.Account;
21 import android.util.Pair;
22
23 import com.owncloud.android.datamodel.OCFile;
24
25 import java.io.File;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31
32 /**
33 * Helper structure to keep the trees of folders containing any file downloading or synchronizing.
34 *
35 * A map provides the indexation based in hashing.
36 *
37 * A tree is created per account.
38 *
39 * @author David A. Velasco
40 */
41 public class IndexedForest<V> {
42
43 private ConcurrentMap<String, Node<V>> mMap = new ConcurrentHashMap<String, Node<V>>();
44
45 private class Node<V> {
46 String mKey = null;
47 Node<V> mParent = null;
48 Set<Node<V>> mChildren = new HashSet<Node<V>>(); // TODO be careful with hash()
49 V mPayload = null;
50
51 // payload is optional
52 public Node(String key, V payload) {
53 if (key == null) {
54 throw new IllegalArgumentException("Argument key MUST NOT be null");
55 }
56 mKey = key;
57 mPayload = payload;
58 }
59
60 public Node<V> getParent() {
61 return mParent;
62 };
63
64 public Set<Node<V>> getChildren() {
65 return mChildren;
66 }
67
68 public String getKey() {
69 return mKey;
70 }
71
72 public V getPayload() {
73 return mPayload;
74 }
75
76 public void addChild(Node<V> child) {
77 mChildren.add(child);
78 child.setParent(this);
79 }
80
81 private void setParent(Node<V> parent) {
82 mParent = parent;
83 }
84
85 public boolean hasChildren() {
86 return mChildren.size() > 0;
87 }
88
89 public void removeChild(Node<V> removed) {
90 mChildren.remove(removed);
91 }
92 }
93
94
95 public /* synchronized */ Pair<String, String> putIfAbsent(Account account, String remotePath, V value) {
96 String targetKey = buildKey(account, remotePath);
97 Node<V> valuedNode = new Node(targetKey, value);
98 mMap.putIfAbsent(
99 targetKey,
100 valuedNode
101 );
102
103 String currentPath = remotePath, parentPath = null, parentKey = null;
104 Node<V> currentNode = valuedNode, parentNode = null;
105 boolean linked = false;
106 while (!OCFile.ROOT_PATH.equals(currentPath) && !linked) {
107 parentPath = new File(currentPath).getParent();
108 if (!parentPath.endsWith(OCFile.PATH_SEPARATOR)) {
109 parentPath += OCFile.PATH_SEPARATOR;
110 }
111 parentKey = buildKey(account, parentPath);
112 parentNode = mMap.get(parentKey);
113 if (parentNode == null) {
114 parentNode = new Node(parentKey, null);
115 parentNode.addChild(currentNode);
116 mMap.put(parentKey, parentNode);
117 } else {
118 parentNode.addChild(currentNode);
119 linked = true;
120 }
121 currentPath = parentPath;
122 currentNode = parentNode;
123 }
124
125 String linkedTo = OCFile.ROOT_PATH;
126 if (linked) {
127 linkedTo = parentNode.getKey().substring(account.name.length());
128 }
129 return new Pair<String, String>(targetKey, linkedTo);
130 };
131
132 public /* synchronized */ Pair<V, String> remove(Account account, String remotePath) {
133 String targetKey = buildKey(account, remotePath);
134 Node<V> firstRemoved = mMap.remove(targetKey);
135 String unlinkedFrom = null;
136
137 if (firstRemoved != null) {
138 /// remove children
139 removeDescendants(firstRemoved);
140
141 /// remove ancestors if only here due to firstRemoved
142 Node<V> removed = firstRemoved;
143 Node<V> parent = removed.getParent();
144 boolean unlinked = false;
145 while (parent != null) {
146 parent.removeChild(removed);
147 if (!parent.hasChildren()) {
148 removed = mMap.remove(parent.getKey());
149 parent = removed.getParent();
150 } else {
151 break;
152 }
153 }
154
155 if (parent != null) {
156 unlinkedFrom = parent.getKey().substring(account.name.length());
157 }
158 }
159
160 if (firstRemoved != null) {
161 return new Pair<V, String>(firstRemoved.getPayload(), unlinkedFrom);
162 } else {
163 return new Pair<V, String>(null, unlinkedFrom);
164 }
165
166 }
167
168 private void removeDescendants(Node<V> removed) {
169 Iterator<Node<V>> childrenIt = removed.getChildren().iterator();
170 Node<V> child = null;
171 while (childrenIt.hasNext()) {
172 child = childrenIt.next();
173 mMap.remove(child.getKey());
174 removeDescendants(child);
175 }
176 }
177
178 public boolean contains(Account account, String remotePath) {
179 String targetKey = buildKey(account, remotePath);
180 return mMap.containsKey(targetKey);
181 }
182
183 public /* synchronized */ V get(String key) {
184 Node<V> node = mMap.get(key);
185 if (node != null) {
186 return node.getPayload();
187 } else {
188 return null;
189 }
190 }
191
192
193 /**
194 * Builds a key to index files
195 *
196 * @param account Account where the file to download is stored
197 * @param remotePath Path of the file in the server
198 */
199 private String buildKey(Account account, String remotePath) {
200 return account.name + remotePath;
201 }
202
203
204
205 }