comparison src/org/tmatesoft/hg/repo/HgBundle.java @ 673:545b1d4cc11d

Refactor HgBundle.GroupElement (clear experimental mark), resolve few technical debt issues
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 12 Jul 2013 20:14:24 +0200
parents fba85bc1dfb8
children
comparison
equal deleted inserted replaced
672:d2552e6a5af6 673:545b1d4cc11d
15 * contact TMate Software at support@hg4j.com 15 * contact TMate Software at support@hg4j.com
16 */ 16 */
17 package org.tmatesoft.hg.repo; 17 package org.tmatesoft.hg.repo;
18 18
19 import java.io.File; 19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException; 20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.util.ConcurrentModificationException; 22 import java.util.ConcurrentModificationException;
23 23
24 import org.tmatesoft.hg.core.HgIOException; 24 import org.tmatesoft.hg.core.HgIOException;
25 import org.tmatesoft.hg.core.Nodeid; 25 import org.tmatesoft.hg.core.Nodeid;
26 import org.tmatesoft.hg.core.SessionContext; 26 import org.tmatesoft.hg.core.SessionContext;
27 import org.tmatesoft.hg.internal.ByteArrayChannel; 27 import org.tmatesoft.hg.internal.ByteArrayChannel;
28 import org.tmatesoft.hg.internal.ByteArrayDataAccess; 28 import org.tmatesoft.hg.internal.ByteArrayDataAccess;
29 import org.tmatesoft.hg.internal.Callback; 29 import org.tmatesoft.hg.internal.Callback;
30 import org.tmatesoft.hg.internal.ChangesetParser;
30 import org.tmatesoft.hg.internal.DataAccess; 31 import org.tmatesoft.hg.internal.DataAccess;
32 import org.tmatesoft.hg.internal.DataAccessInputStream;
31 import org.tmatesoft.hg.internal.DataAccessProvider; 33 import org.tmatesoft.hg.internal.DataAccessProvider;
32 import org.tmatesoft.hg.internal.DataSerializer;
33 import org.tmatesoft.hg.internal.DigestHelper; 34 import org.tmatesoft.hg.internal.DigestHelper;
34 import org.tmatesoft.hg.internal.EncodingHelper; 35 import org.tmatesoft.hg.internal.EncodingHelper;
35 import org.tmatesoft.hg.internal.Experimental; 36 import org.tmatesoft.hg.internal.Experimental;
36 import org.tmatesoft.hg.internal.FileUtils;
37 import org.tmatesoft.hg.internal.InflaterDataAccess; 37 import org.tmatesoft.hg.internal.InflaterDataAccess;
38 import org.tmatesoft.hg.internal.Internals; 38 import org.tmatesoft.hg.internal.Internals;
39 import org.tmatesoft.hg.internal.Lifecycle; 39 import org.tmatesoft.hg.internal.Lifecycle;
40 import org.tmatesoft.hg.internal.Patch; 40 import org.tmatesoft.hg.internal.Patch;
41 import org.tmatesoft.hg.repo.HgChangelog.ChangesetParser;
42 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; 41 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
43 import org.tmatesoft.hg.util.Adaptable; 42 import org.tmatesoft.hg.util.Adaptable;
44 import org.tmatesoft.hg.util.CancelledException; 43 import org.tmatesoft.hg.util.CancelledException;
45 44
46 /** 45 /**
52 * @author TMate Software Ltd. 51 * @author TMate Software Ltd.
53 */ 52 */
54 @Experimental(reason="API is not stable") 53 @Experimental(reason="API is not stable")
55 public class HgBundle { 54 public class HgBundle {
56 55
57 private final File bundleFile; 56 final File bundleFile;
58 private final DataAccessProvider accessProvider; 57 private final DataAccessProvider accessProvider;
59 private final SessionContext ctx; 58 final SessionContext ctx;
60 private final EncodingHelper fnDecorer; 59 private final EncodingHelper fnDecorer;
61 private Lifecycle.BasicCallback flowControl; 60 private Lifecycle.BasicCallback flowControl;
62 61
63 HgBundle(SessionContext sessionContext, DataAccessProvider dap, File bundle) { 62 HgBundle(SessionContext sessionContext, DataAccessProvider dap, File bundle) {
64 ctx = sessionContext; 63 ctx = sessionContext;
108 /** 107 /**
109 * Get changes recorded in the bundle that are missing from the supplied repository. 108 * Get changes recorded in the bundle that are missing from the supplied repository.
110 * @param hgRepo repository that shall possess base revision for this bundle 109 * @param hgRepo repository that shall possess base revision for this bundle
111 * @param inspector callback to get each changeset found 110 * @param inspector callback to get each changeset found
112 */ 111 */
113 public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgRuntimeException { 112 public void changes(final HgRepository hgRepo, final HgChangelog.Inspector inspector) throws HgIOException, HgRuntimeException {
114 Inspector bundleInsp = new Inspector() { 113 Inspector bundleInsp = new Inspector() {
115 DigestHelper dh = new DigestHelper(); 114 DigestHelper dh = new DigestHelper();
116 boolean emptyChangelog = true; 115 boolean emptyChangelog = true;
117 private DataAccess prevRevContent; 116 private DataAccess prevRevContent;
118 private int revisionIndex; 117 private int revisionIndex;
119 private ChangesetParser csetBuilder; 118 private ChangesetParser csetBuilder;
120 119
121 public void changelogStart() { 120 public void changelogStart() {
122 emptyChangelog = true; 121 emptyChangelog = true;
123 revisionIndex = 0; 122 revisionIndex = 0;
124 csetBuilder = new ChangesetParser(hgRepo, true); 123 csetBuilder = new ChangesetParser(hgRepo, new HgChangelog.RawCsetFactory(true));
125 } 124 }
126 125
127 public void changelogEnd() { 126 public void changelogEnd() {
128 if (emptyChangelog) { 127 if (emptyChangelog) {
129 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log? 128 throw new IllegalStateException("No changelog group in the bundle"); // XXX perhaps, just be silent and/or log?
151 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3 150 a6f39e595b2b54f56304470269a936ead77f5725 9429c7bd1920fab164a9d2b621d38d57bcb49ae0 30bd389788464287cee22ccff54c330a4b715de5 a6f39e595b2b54f56304470269a936ead77f5725; patches:3
152 fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3 151 fd4f2c98995beb051070630c272a9be87bef617d 30bd389788464287cee22ccff54c330a4b715de5 0000000000000000000000000000000000000000 fd4f2c98995beb051070630c272a9be87bef617d; patches:3
153 152
154 To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e 153 To recreate 30bd..e5, one have to take content of 9429..e0, not its p1 f1db..5e
155 */ 154 */
156 public boolean element(GroupElement ge) throws HgRuntimeException { 155 public boolean element(GroupElement ge) throws IOException, HgRuntimeException {
157 emptyChangelog = false; 156 emptyChangelog = false;
158 HgChangelog changelog = hgRepo.getChangelog(); 157 HgChangelog changelog = hgRepo.getChangelog();
159 try { 158 try {
160 if (prevRevContent == null) { 159 if (prevRevContent == null) {
161 if (ge.firstParent().isNull() && ge.secondParent().isNull()) { 160 if (ge.firstParent().isNull() && ge.secondParent().isNull()) {
170 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap. 169 // extra byte[] (inside ByteArrayChannel) duplication just for the sake of subsequent ByteArrayDataChannel wrap.
171 prevRevContent = new ByteArrayDataAccess(bac.toArray()); 170 prevRevContent = new ByteArrayDataAccess(bac.toArray());
172 } 171 }
173 } 172 }
174 // 173 //
175 byte[] csetContent = ge.apply(prevRevContent); 174 byte[] csetContent = ge.patch().apply(prevRevContent, -1);
176 dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid? 175 dh = dh.sha1(ge.firstParent(), ge.secondParent(), csetContent); // XXX ge may give me access to byte[] content of nodeid directly, perhaps, I don't need DH to be friend of Nodeid?
177 if (!ge.node().equalsTo(dh.asBinary())) { 176 if (!ge.node().equalsTo(dh.asBinary())) {
178 throw new HgInvalidStateException(String.format("Integrity check failed on %s, node: %s", bundleFile, ge.node().shortNotation())); 177 throw new HgInvalidStateException(String.format("Integrity check failed on %s, node: %s", bundleFile, ge.node().shortNotation()));
179 } 178 }
180 ByteArrayDataAccess csetDataAccess = new ByteArrayDataAccess(csetContent); 179 RawChangeset cs = csetBuilder.parse(csetContent);
181 RawChangeset cs = csetBuilder.parse(csetDataAccess);
182 inspector.next(revisionIndex++, ge.node(), cs); 180 inspector.next(revisionIndex++, ge.node(), cs);
183 prevRevContent.done(); 181 prevRevContent.done();
184 prevRevContent = csetDataAccess.reset(); 182 prevRevContent = new ByteArrayDataAccess(csetContent);
185 } catch (CancelledException ex) { 183 } catch (CancelledException ex) {
186 return false; 184 return false;
187 } catch (IOException ex) {
188 throw new HgInvalidFileException("Invalid bundle file", ex, bundleFile); // TODO post-1.0 revisit exception handling
189 } catch (HgInvalidDataFormatException ex) { 185 } catch (HgInvalidDataFormatException ex) {
190 throw new HgInvalidControlFileException("Invalid bundle file", ex, bundleFile); 186 throw new HgInvalidControlFileException("Invalid bundle file", ex, bundleFile);
191 } 187 }
192 return true; 188 return true;
193 } 189 }
215 void fileStart(String name) throws HgRuntimeException; 211 void fileStart(String name) throws HgRuntimeException;
216 212
217 void fileEnd(String name) throws HgRuntimeException; 213 void fileEnd(String name) throws HgRuntimeException;
218 214
219 /** 215 /**
220 * XXX desperately need exceptions here
221 * @param element data element, instance might be reused, don't keep a reference to it or its raw data 216 * @param element data element, instance might be reused, don't keep a reference to it or its raw data
222 * @return <code>true</code> to continue 217 * @return <code>true</code> to continue
223 */ 218 * @throws IOException propagated exception from {@link GroupElement#data()}
224 boolean element(GroupElement element) throws HgRuntimeException; 219 * @throws HgRuntimeException propagated exception (subclass thereof) to indicate issues with the library. <em>Runtime exception</em>
220 */
221 boolean element(GroupElement element) throws IOException, HgRuntimeException;
225 } 222 }
226 223
227 /** 224 /**
228 * @param inspector callback to visit changelog entries 225 * @param inspector callback to visit changelog entries
229 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 226 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
230 * @throws IllegalArgumentException if inspector argument is null 227 * @throws IllegalArgumentException if inspector argument is null
231 */ 228 */
232 public void inspectChangelog(Inspector inspector) throws HgRuntimeException { 229 public void inspectChangelog(Inspector inspector) throws HgIOException, HgRuntimeException {
233 if (inspector == null) { 230 if (inspector == null) {
234 throw new IllegalArgumentException(); 231 throw new IllegalArgumentException();
235 } 232 }
236 final Lifecycle lifecycle = lifecycleSetUp(inspector); 233 final Lifecycle lifecycle = lifecycleSetUp(inspector);
237 DataAccess da = null; 234 DataAccess da = null;
238 try { 235 try {
239 da = getDataStream(); 236 da = getDataStream();
240 internalInspectChangelog(da, inspector); 237 internalInspectChangelog(da, inspector);
241 } catch (IOException ex) { 238 } catch (IOException ex) {
242 throw new HgInvalidFileException("Bundle.inspectChangelog failed", ex, bundleFile); 239 throw new HgIOException("Failed to inspect changelog in the bundle", ex, bundleFile);
243 } finally { 240 } finally {
244 if (da != null) { 241 if (da != null) {
245 da.done(); 242 da.done();
246 } 243 }
247 lifecycleTearDown(lifecycle); 244 lifecycleTearDown(lifecycle);
251 /** 248 /**
252 * @param inspector callback to visit manifest entries 249 * @param inspector callback to visit manifest entries
253 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 250 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
254 * @throws IllegalArgumentException if inspector argument is null 251 * @throws IllegalArgumentException if inspector argument is null
255 */ 252 */
256 public void inspectManifest(Inspector inspector) throws HgRuntimeException { 253 public void inspectManifest(Inspector inspector) throws HgIOException, HgRuntimeException {
257 if (inspector == null) { 254 if (inspector == null) {
258 throw new IllegalArgumentException(); 255 throw new IllegalArgumentException();
259 } 256 }
260 final Lifecycle lifecycle = lifecycleSetUp(inspector); 257 final Lifecycle lifecycle = lifecycleSetUp(inspector);
261 DataAccess da = null; 258 DataAccess da = null;
265 return; 262 return;
266 } 263 }
267 skipGroup(da); // changelog 264 skipGroup(da); // changelog
268 internalInspectManifest(da, inspector); 265 internalInspectManifest(da, inspector);
269 } catch (IOException ex) { 266 } catch (IOException ex) {
270 throw new HgInvalidFileException("Bundle.inspectManifest failed", ex, bundleFile); 267 throw new HgIOException("Failed to inspect manifest in the bundle", ex, bundleFile);
271 } finally { 268 } finally {
272 if (da != null) { 269 if (da != null) {
273 da.done(); 270 da.done();
274 } 271 }
275 lifecycleTearDown(lifecycle); 272 lifecycleTearDown(lifecycle);
279 /** 276 /**
280 * @param inspector callback to visit file entries 277 * @param inspector callback to visit file entries
281 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 278 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
282 * @throws IllegalArgumentException if inspector argument is null 279 * @throws IllegalArgumentException if inspector argument is null
283 */ 280 */
284 public void inspectFiles(Inspector inspector) throws HgRuntimeException { 281 public void inspectFiles(Inspector inspector) throws HgIOException, HgRuntimeException {
285 if (inspector == null) { 282 if (inspector == null) {
286 throw new IllegalArgumentException(); 283 throw new IllegalArgumentException();
287 } 284 }
288 final Lifecycle lifecycle = lifecycleSetUp(inspector); 285 final Lifecycle lifecycle = lifecycleSetUp(inspector);
289 DataAccess da = null; 286 DataAccess da = null;
297 return; 294 return;
298 } 295 }
299 skipGroup(da); // manifest 296 skipGroup(da); // manifest
300 internalInspectFiles(da, inspector); 297 internalInspectFiles(da, inspector);
301 } catch (IOException ex) { 298 } catch (IOException ex) {
302 throw new HgInvalidFileException("Bundle.inspectFiles failed", ex, bundleFile); 299 throw new HgIOException("Failed to inspect files in the bundle", ex, bundleFile);
303 } finally { 300 } finally {
304 if (da != null) { 301 if (da != null) {
305 da.done(); 302 da.done();
306 } 303 }
307 lifecycleTearDown(lifecycle); 304 lifecycleTearDown(lifecycle);
311 /** 308 /**
312 * @param inspector visit complete bundle (changelog, manifest and file entries) 309 * @param inspector visit complete bundle (changelog, manifest and file entries)
313 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> 310 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
314 * @throws IllegalArgumentException if inspector argument is null 311 * @throws IllegalArgumentException if inspector argument is null
315 */ 312 */
316 public void inspectAll(Inspector inspector) throws HgRuntimeException { 313 public void inspectAll(Inspector inspector) throws HgIOException, HgRuntimeException {
317 if (inspector == null) { 314 if (inspector == null) {
318 throw new IllegalArgumentException(); 315 throw new IllegalArgumentException();
319 } 316 }
320 final Lifecycle lifecycle = lifecycleSetUp(inspector); 317 final Lifecycle lifecycle = lifecycleSetUp(inspector);
321 DataAccess da = null; 318 DataAccess da = null;
329 if (flowControl.isStopped()) { 326 if (flowControl.isStopped()) {
330 return; 327 return;
331 } 328 }
332 internalInspectFiles(da, inspector); 329 internalInspectFiles(da, inspector);
333 } catch (IOException ex) { 330 } catch (IOException ex) {
334 throw new HgInvalidFileException("Bundle.inspectAll failed", ex, bundleFile); 331 throw new HgIOException("Failed to inspect bundle", ex, bundleFile);
335 } finally { 332 } finally {
336 if (da != null) { 333 if (da != null) {
337 da.done(); 334 da.done();
338 } 335 }
339 lifecycleTearDown(lifecycle); 336 lifecycleTearDown(lifecycle);
451 da.skip(len - 4); // sizeof(int) 448 da.skip(len - 4); // sizeof(int)
452 len = da.isEmpty() ? 0 : da.readInt(); 449 len = da.isEmpty() ? 0 : da.readInt();
453 } 450 }
454 } 451 }
455 452
456 @Experimental(reason="Cumbersome API, rawData and apply with byte[] perhaps need replacement with ByteChannel/ByteBuffer, and better Exceptions. Perhaps, shall split into interface and impl") 453 /**
457 public static class GroupElement { 454 * Describes single element (a.k.a. chunk) of the group, either changelog, manifest or a file.
455 */
456 public static final class GroupElement {
458 private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192 457 private final byte[] header; // byte[80] takes 120 bytes, 4 Nodeids - 192
459 private final DataAccess dataAccess; 458 private final DataAccess dataAccess;
459 private final Nodeid deltaBase;
460 private Patch patches; 460 private Patch patches;
461 private final Nodeid deltaBase; 461
462
463 GroupElement(byte[] fourNodeids, Nodeid deltaBaseRev, DataAccess rawDataAccess) { 462 GroupElement(byte[] fourNodeids, Nodeid deltaBaseRev, DataAccess rawDataAccess) {
464 assert fourNodeids != null && fourNodeids.length == 80; 463 assert fourNodeids != null && fourNodeids.length == 80;
465 header = fourNodeids; 464 header = fourNodeids;
466 deltaBase = deltaBaseRev; 465 deltaBase = deltaBaseRev;
467 dataAccess = rawDataAccess; 466 dataAccess = rawDataAccess;
505 */ 504 */
506 public Nodeid patchBase() { 505 public Nodeid patchBase() {
507 return deltaBase == null ? firstParent() : deltaBase; 506 return deltaBase == null ? firstParent() : deltaBase;
508 } 507 }
509 508
510 public byte[] rawDataByteArray() throws IOException { // XXX IOException or HgInvalidFileException? 509 /**
511 return rawData().byteArray(); 510 * Read data of the group element.
512 } 511 * Note, {@link InputStream streams} obtained from several calls to this method
513 512 * can't be read simultaneously.
514 public byte[] apply(byte[] baseContent) throws IOException { 513 *
515 return apply(new ByteArrayDataAccess(baseContent)); 514 * @return stream to access content of this group element, never <code>null</code>
516 } 515 */
517 516 public InputStream data() {
518 /*package-local*/ DataAccess rawData() { 517 return new DataAccessInputStream(dataAccess);
519 return dataAccess;
520 } 518 }
521 519
522 /*package-local*/ Patch patch() throws IOException { 520 /*package-local*/ Patch patch() throws IOException {
523 if (patches == null) { 521 if (patches == null) {
524 dataAccess.reset(); 522 dataAccess.reset();
526 patches.read(dataAccess); 524 patches.read(dataAccess);
527 } 525 }
528 return patches; 526 return patches;
529 } 527 }
530 528
531 /*package-local*/ byte[] apply(DataAccess baseContent) throws IOException {
532 return patch().apply(baseContent, -1);
533 }
534
535 public String toString() { 529 public String toString() {
536 int patchCount; 530 int patchCount;
537 try { 531 try {
538 patchCount = patch().count(); 532 patchCount = patch().count();
539 } catch (IOException ex) { 533 } catch (IOException ex) {
541 patchCount = -1; 535 patchCount = -1;
542 } 536 }
543 return String.format("%s %s %s %s; patches:%d\n", node().shortNotation(), firstParent().shortNotation(), secondParent().shortNotation(), cset().shortNotation(), patchCount); 537 return String.format("%s %s %s %s; patches:%d\n", node().shortNotation(), firstParent().shortNotation(), secondParent().shortNotation(), cset().shortNotation(), patchCount);
544 } 538 }
545 } 539 }
546
547 @Experimental(reason="Work in progress, not an API")
548 public class BundleSerializer implements DataSerializer.DataSource {
549
550 public void serialize(DataSerializer out) throws HgIOException, HgRuntimeException {
551 FileInputStream fis = null;
552 try {
553 fis = new FileInputStream(HgBundle.this.bundleFile);
554 byte[] buffer = new byte[8*1024];
555 int r;
556 while ((r = fis.read(buffer, 0, buffer.length)) > 0) {
557 out.write(buffer, 0, r);
558 }
559
560 } catch (IOException ex) {
561 throw new HgIOException("Failed to serialize bundle", HgBundle.this.bundleFile);
562 } finally {
563 new FileUtils(HgBundle.this.ctx.getLog(), this).closeQuietly(fis, HgBundle.this.bundleFile);
564 }
565 }
566
567 public int serializeLength() throws HgRuntimeException {
568 return Internals.ltoi(HgBundle.this.bundleFile.length());
569 }
570 }
571 } 540 }