comparison src/com/tmate/hgkit/ll/StatusCollector.java @ 55:05829a70b30b

Status operation extracted into separate, cache-friendly class
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Mon, 17 Jan 2011 04:45:09 +0100
parents
children 576d6e8a09f6
comparison
equal deleted inserted replaced
54:fd4f2c98995b 55:05829a70b30b
1 /*
2 * Copyright (c) 2011 Artem Tikhomirov
3 */
4 package com.tmate.hgkit.ll;
5
6 import java.util.Collection;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.LinkedHashMap;
10 import java.util.LinkedList;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.TreeSet;
14
15 /**
16 * RevisionWalker?
17 * @author artem
18 */
19 public class StatusCollector {
20
21 private final HgRepository repo;
22 private final Map<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
23
24 public StatusCollector(HgRepository hgRepo) {
25 this.repo = hgRepo;
26 cache = new HashMap<Integer, ManifestRevisionInspector>();
27 }
28
29 private ManifestRevisionInspector get(int rev) {
30 ManifestRevisionInspector i = cache.get(rev);
31 if (i == null) {
32 i = new ManifestRevisionInspector(rev, rev);
33 cache.put(rev, i);
34 repo.getManifest().walk(rev, rev, i);
35 }
36 return i;
37 }
38
39 public void walk(int rev1, int rev2, Inspector inspector) {
40 if (rev1 == rev2) {
41 throw new IllegalArgumentException();
42 }
43 // in fact, rev1 and rev2 are often next (or close) to each other,
44 // thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
45
46 ManifestRevisionInspector r1, r2;
47 if (!cache.containsKey(rev1) && !cache.containsKey(rev2) && Math.abs(rev1 - rev2) < 5 /*subjective equivalent of 'close enough'*/) {
48 int minRev = rev1 < rev2 ? rev1 : rev2;
49 int maxRev = minRev == rev1 ? rev2 : rev1;
50 r1 = r2 = new ManifestRevisionInspector(minRev, maxRev);
51 for (int i = minRev; i <= maxRev; i++) {
52 cache.put(i, r1);
53 }
54 repo.getManifest().walk(minRev, maxRev, r1);
55 } else {
56 r1 = get(rev1);
57 r2 = get(rev2);
58 }
59
60 TreeSet<String> r1Files = new TreeSet<String>(r1.files(rev1));
61 for (String fname : r2.files(rev2)) {
62 if (r1Files.remove(fname)) {
63 Nodeid nidR1 = r1.nodeid(rev1, fname);
64 Nodeid nidR2 = r2.nodeid(rev2, fname);
65 String flagsR1 = r1.flags(rev1, fname);
66 String flagsR2 = r2.flags(rev2, fname);
67 if (nidR1.equals(nidR2) && ((flagsR2 == null && flagsR1 == null) || flagsR2.equals(flagsR1))) {
68 inspector.clean(fname);
69 } else {
70 inspector.modified(fname);
71 }
72 } else {
73 inspector.added(fname);
74 }
75 }
76 for (String left : r1Files) {
77 inspector.removed(left);
78 }
79 // inspector.done() if useful e.g. in UI client
80 }
81
82 public Record status(int rev1, int rev2) {
83 Record rv = new Record();
84 walk(rev1, rev2, rv);
85 return rv;
86 }
87
88 public interface Inspector {
89 void modified(String fname);
90 void added(String fname);
91 void copied(String fnameOrigin, String fnameAdded); // if copied files of no interest, should delegate to self.added(fnameAdded);
92 void removed(String fname);
93 void clean(String fname);
94 void missing(String fname); // aka deleted (tracked by Hg, but not available in FS any more
95 void unknown(String fname); // not tracked
96 void ignored(String fname);
97 }
98
99 // XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense
100 public static class Record implements Inspector {
101 private List<String> modified, added, removed, clean, missing, unknown, ignored;
102 private Map<String, String> copied;
103
104 public List<String> getModified() {
105 return proper(modified);
106 }
107
108 public List<String> getAdded() {
109 return proper(added);
110 }
111
112 public List<String> getRemoved() {
113 return proper(removed);
114 }
115
116 public Map<String,String> getCopied() {
117 if (copied == null) {
118 return Collections.emptyMap();
119 }
120 return Collections.unmodifiableMap(copied);
121 }
122
123 public List<String> getClean() {
124 return proper(clean);
125 }
126
127 public List<String> getMissing() {
128 return proper(missing);
129 }
130
131 public List<String> getUnknown() {
132 return proper(unknown);
133 }
134
135 public List<String> getIgnored() {
136 return proper(ignored);
137 }
138
139 private List<String> proper(List<String> l) {
140 if (l == null) {
141 return Collections.emptyList();
142 }
143 return Collections.unmodifiableList(l);
144 }
145
146 //
147 //
148
149 public void modified(String fname) {
150 modified = doAdd(modified, fname);
151 }
152
153 public void added(String fname) {
154 added = doAdd(added, fname);
155 }
156
157 public void copied(String fnameOrigin, String fnameAdded) {
158 if (copied == null) {
159 copied = new LinkedHashMap<String, String>();
160 }
161 copied.put(fnameOrigin, fnameAdded);
162 }
163
164 public void removed(String fname) {
165 removed = doAdd(removed, fname);
166 }
167
168 public void clean(String fname) {
169 clean = doAdd(clean, fname);
170 }
171
172 public void missing(String fname) {
173 missing = doAdd(missing, fname);
174 }
175
176 public void unknown(String fname) {
177 unknown = doAdd(unknown, fname);
178 }
179
180 public void ignored(String fname) {
181 ignored = doAdd(ignored, fname);
182 }
183
184 private static List<String> doAdd(List<String> l, String s) {
185 if (l == null) {
186 l = new LinkedList<String>();
187 }
188 l.add(s);
189 return l;
190 }
191 }
192
193 public /*XXX private, actually. Made public unless repo.statusLocal finds better place*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
194 private final HashMap<String, Nodeid>[] idsMap;
195 private final HashMap<String, String>[] flagsMap;
196 private final int baseRevision;
197 private int r = -1; // cursor
198
199 /**
200 * [minRev, maxRev]
201 * @param minRev - inclusive
202 * @param maxRev - inclusive
203 */
204 @SuppressWarnings("unchecked")
205 public ManifestRevisionInspector(int minRev, int maxRev) {
206 baseRevision = minRev;
207 int range = maxRev - minRev + 1;
208 idsMap = new HashMap[range];
209 flagsMap = new HashMap[range];
210 }
211
212 public Collection<String> files(int rev) {
213 if (rev < baseRevision || rev >= baseRevision + idsMap.length) {
214 throw new IllegalArgumentException();
215 }
216 return idsMap[rev - baseRevision].keySet();
217 }
218
219 public Nodeid nodeid(int rev, String fname) {
220 if (rev < baseRevision || rev >= baseRevision + idsMap.length) {
221 throw new IllegalArgumentException();
222 }
223 return idsMap[rev - baseRevision].get(fname);
224 }
225
226 public String flags(int rev, String fname) {
227 if (rev < baseRevision || rev >= baseRevision + idsMap.length) {
228 throw new IllegalArgumentException();
229 }
230 return flagsMap[rev - baseRevision].get(fname);
231 }
232
233 //
234
235 public boolean next(Nodeid nid, String fname, String flags) {
236 idsMap[r].put(fname, nid);
237 flagsMap[r].put(fname, flags);
238 return true;
239 }
240
241 public boolean end(int revision) {
242 assert revision == r + baseRevision;
243 r = -1;
244 return revision+1 < baseRevision + idsMap.length;
245 }
246
247 public boolean begin(int revision, Nodeid nid) {
248 if (revision < baseRevision || revision >= baseRevision + idsMap.length) {
249 throw new IllegalArgumentException();
250 }
251 r = revision - baseRevision;
252 idsMap[r] = new HashMap<String, Nodeid>();
253 flagsMap[r] = new HashMap<String, String>();
254 return true;
255 }
256 }
257
258 }