Mercurial > jhg
comparison src/org/tmatesoft/hg/internal/Patch.java @ 583:47dfa0ec7e35
Effective revlog patching
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 24 Apr 2013 15:39:53 +0200 |
| parents | 243202f1bda5 |
| children | ed243b668502 |
comparison
equal
deleted
inserted
replaced
| 582:90df078d6418 | 583:47dfa0ec7e35 |
|---|---|
| 84 public int count() { | 84 public int count() { |
| 85 return data.size(); | 85 return data.size(); |
| 86 } | 86 } |
| 87 | 87 |
| 88 // number of bytes this patch will add (or remove, if negative) from the base revision | 88 // number of bytes this patch will add (or remove, if negative) from the base revision |
| 89 private int patchSizeDelta() { | 89 public int patchSizeDelta() { |
| 90 int rv = 0; | 90 int rv = 0; |
| 91 int prevEnd = 0; | 91 int prevEnd = 0; |
| 92 for (int i = 0, x = data.size(); i < x; i++) { | 92 for (int i = 0, x = data.size(); i < x; i++) { |
| 93 final int start = starts.get(i); | 93 final int start = starts.get(i); |
| 94 final int len = data.get(i).length; | 94 final int len = data.get(i).length; |
| 180 private void add(Patch p, int i) { | 180 private void add(Patch p, int i) { |
| 181 add(p.starts.get(i), p.ends.get(i), p.data.get(i)); | 181 add(p.starts.get(i), p.ends.get(i), p.data.get(i)); |
| 182 } | 182 } |
| 183 | 183 |
| 184 /*package-local*/ void add(int start, int end, byte[] d) { | 184 /*package-local*/ void add(int start, int end, byte[] d) { |
| 185 // FIXME if start == end(-1), merge data | |
| 186 if (start == end && d.length == 0) { | |
| 187 System.currentTimeMillis(); | |
| 188 return; | |
| 189 } | |
| 185 starts.add(start); | 190 starts.add(start); |
| 186 ends.add(end); | 191 ends.add(end); |
| 187 data.add(d); | 192 data.add(d); |
| 188 } | 193 } |
| 189 | 194 |
| 195 // copies [start..end) bytes from the d | |
| 190 private static byte[] subarray(byte[] d, int start, int end) { | 196 private static byte[] subarray(byte[] d, int start, int end) { |
| 191 byte[] r = new byte[end-start+1]; | 197 byte[] r = new byte[end-start]; |
| 192 System.arraycopy(d, start, r, 0, r.length); | 198 System.arraycopy(d, start, r, 0, r.length); |
| 193 return r; | 199 return r; |
| 194 } | 200 } |
| 195 | 201 |
| 196 /** | 202 /** |
| 197 * Modify this patch with subsequent patch | 203 * Modify this patch with subsequent patch |
| 198 */ | 204 */ |
| 199 private /*SHALL BE PUBLIC ONCE TESTING ENDS*/ Patch apply(Patch another) { | 205 public /*SHALL BE PUBLIC ONCE TESTING ENDS*/ Patch apply(Patch another) { |
| 200 Patch r = new Patch(); | 206 Patch r = new Patch(); |
| 201 int p1TotalAppliedDelta = 0; // value to add to start and end indexes of the older patch to get their values as if | 207 int p1TotalAppliedDelta = 0; // value to add to start and end indexes of the older patch to get their values as if |
| 202 // in the patched text, iow, directly comparable with respective indexes from the newer patch. | 208 // in the patched text, iow, directly comparable with respective indexes from the newer patch. |
| 203 int p1EntryStart = 0, p1EntryEnd = 0, p1EntryLen = 0; | 209 int p1EntryStart = 0, p1EntryEnd = 0, p1EntryLen = 0; |
| 204 byte[] p1Data = null; | 210 byte[] p1Data = null; |
| 209 int p2EntryStart = another.starts.get(p2); | 215 int p2EntryStart = another.starts.get(p2); |
| 210 int p2EntryEnd = another.ends.get(p2); | 216 int p2EntryEnd = another.ends.get(p2); |
| 211 final int p2EntryRange = p2EntryEnd - p2EntryStart; | 217 final int p2EntryRange = p2EntryEnd - p2EntryStart; |
| 212 final byte[] p2Data = another.data.get(p2); | 218 final byte[] p2Data = another.data.get(p2); |
| 213 boolean insideP2entry = false; | 219 boolean insideP2entry = false; |
| 214 int p2EntryStartOffset = -1; | 220 // when we iterate p1 elements within single p2, we need to remember where p2 |
| 221 // shall ultimately start in terms of p1 | |
| 222 int p2EntrySavedStart = -1; | |
| 215 /// | 223 /// |
| 216 p1EntryStart = p1EntryEnd = p1EntryLen = 0; | |
| 217 p1Data = null; | |
| 218 | 224 |
| 219 L1: while (p1 < p1Max) { | 225 L1: while (p1 < p1Max) { |
| 220 if (!insideP1entry) { | 226 if (!insideP1entry) { |
| 221 p1EntryStart = starts.get(p1); | 227 p1EntryStart = starts.get(p1); |
| 222 p1EntryEnd = ends.get(p1); | 228 p1EntryEnd = ends.get(p1); |
| 227 final int p1EntryDelta = p1EntryLen - (p1EntryEnd - p1EntryStart); // number of actually inserted(+) or deleted(-) chars | 233 final int p1EntryDelta = p1EntryLen - (p1EntryEnd - p1EntryStart); // number of actually inserted(+) or deleted(-) chars |
| 228 final int p1EntryAppliedStart = p1TotalAppliedDelta + p1EntryStart; | 234 final int p1EntryAppliedStart = p1TotalAppliedDelta + p1EntryStart; |
| 229 final int p1EntryAppliedEnd = p1EntryAppliedStart + p1EntryLen; // end of j'th patch entry in the text which is source for p2 | 235 final int p1EntryAppliedEnd = p1EntryAppliedStart + p1EntryLen; // end of j'th patch entry in the text which is source for p2 |
| 230 | 236 |
| 231 if (insideP2entry) { | 237 if (insideP2entry) { |
| 232 if (p2EntryEnd < p1EntryAppliedStart) { | 238 if (p2EntryEnd <= p1EntryAppliedStart) { |
| 233 r.add(p2EntryStart - p2EntryStartOffset, p2EntryEnd - p1TotalAppliedDelta, p2Data); | 239 r.add(p2EntrySavedStart, p2EntryEnd - p1TotalAppliedDelta, p2Data); |
| 234 insideP2entry = false; | 240 insideP2entry = false; |
| 235 continue L0; | 241 continue L0; |
| 236 } | 242 } |
| 237 if (p2EntryEnd >= p1EntryAppliedEnd) { | 243 if (p2EntryEnd >= p1EntryAppliedEnd) { |
| 238 // when p2EntryEnd == p1EntryAppliedEnd, I assume p1TotalAppliedDelta can't be used for p2EntryEnd to get it to p1 range, but rather shall be | 244 // when p2EntryEnd == p1EntryAppliedEnd, I assume p1TotalAppliedDelta can't be used for p2EntryEnd to get it to p1 range, but rather shall be |
| 240 insideP1entry = false; | 246 insideP1entry = false; |
| 241 p1++; | 247 p1++; |
| 242 p1TotalAppliedDelta += p1EntryDelta; | 248 p1TotalAppliedDelta += p1EntryDelta; |
| 243 continue L1; | 249 continue L1; |
| 244 } | 250 } |
| 245 // p1EntryAppliedStart <= p2EntryEnd < p1EntryAppliedEnd | 251 // p1EntryAppliedStart < p2EntryEnd < p1EntryAppliedEnd |
| 246 r.add(p2EntryStart - p2EntryStartOffset, p2EntryEnd - p1TotalAppliedDelta, p2Data); | 252 // can add up to p1EntryEnd here (not only to p1EntryStart), but decided |
| 247 p1EntryStart = p2EntryEnd - p1TotalAppliedDelta; | 253 // to leave p1 intact here, to avoid delta/range recalculation |
| 248 final int p1DataPartShift = p2EntryEnd - p1EntryAppliedStart + 1; | 254 r.add(p2EntrySavedStart, p1EntryStart, p2Data); |
| 249 if (p1DataPartShift >= p1EntryLen) { | 255 // consume part of p1 overlapped by current p2 |
| 250 p1EntryLen = 0; | 256 final int p1DataPartShift = p2EntryEnd - p1EntryAppliedStart; |
| 251 p1Data = new byte[0]; | 257 // p2EntryEnd < p1EntryAppliedEnd ==> p2EntryEnd < p1EntryAppliedStart + p1EntryLen |
| 252 } else { | 258 // ==> p2EntryEnd - p1EntryAppliedStart < p1EntryLen |
| 253 p1EntryLen -= p1DataPartShift; | 259 assert p1DataPartShift < p1EntryLen; |
| 254 p1Data = subarray(p1Data, p1DataPartShift, p1Data.length); | 260 p1EntryLen -= p1DataPartShift; |
| 255 } | 261 p1Data = subarray(p1Data, p1DataPartShift, p1Data.length); |
| 262 p1TotalAppliedDelta += p1DataPartShift; | |
| 256 insideP1entry = true; | 263 insideP1entry = true; |
| 257 insideP2entry = false; | 264 insideP2entry = false; |
| 258 continue L0; | 265 continue L0; |
| 259 } | 266 } |
| 260 | 267 |
| 263 // completely independent, copy and continue | 270 // completely independent, copy and continue |
| 264 r.add(p1EntryStart, p1EntryEnd, p1Data); | 271 r.add(p1EntryStart, p1EntryEnd, p1Data); |
| 265 insideP1entry = false; | 272 insideP1entry = false; |
| 266 p1++; | 273 p1++; |
| 267 // fall-through to get p1TotalAppliedDelta incremented | 274 // fall-through to get p1TotalAppliedDelta incremented |
| 268 } else { // SKETCH: II or III | 275 } else { // SKETCH: II or III - p2 start inside p1 range |
| 269 // remember, p1EntryDelta may be negative | 276 // remember, p1EntryDelta may be negative |
| 270 // shall break j'th entry into few | 277 // shall break j'th entry into few |
| 271 // fix p1's end/length | 278 // fix p1's end/length |
| 272 // p1EntryAppliedStart < p2EntryStart < p1EntryAppliedEnd | 279 // p1EntryAppliedStart < p2EntryStart < p1EntryAppliedEnd, or, alternatively |
| 273 int s = p2EntryStart - p1TotalAppliedDelta; // p2EntryStart in p1 scale. Is within p1 range | 280 // p2EntryStart is from (p1EntryAppliedStart .. p1EntryAppliedStart + p1EntryLen) |
| 274 if (s > p1EntryEnd) { | 281 int p1DataPartEnd = p2EntryStart - p1EntryAppliedStart; |
| 275 s = p1EntryEnd; | 282 assert p1DataPartEnd < p1EntryLen; |
| 276 } | 283 r.add(p1EntryStart, p1EntryEnd, subarray(p1Data, 0, p1DataPartEnd)); |
| 277 int p1DataPartEnd = p2EntryStart - p1EntryAppliedStart; // index, not count. <= (p1EntryEnd-p1EntryStart). | 284 if (p2EntryEnd <= p1EntryAppliedEnd) { // p2 fits completely into p1 |
| 278 // add what left from p1 | 285 r.add(p1EntryEnd, p1EntryEnd, p2Data); |
| 279 if (p1DataPartEnd < p1EntryLen) { | 286 // p2 consumed, p1 has p1EntryLen - p1DataPartEnd - p2EntryRange bytes left to *insert* |
| 280 r.add(p1EntryStart, s, subarray(p1Data, 0, p1DataPartEnd)); | 287 insideP1entry = true; |
| 288 p1EntryStart = p1EntryEnd; | |
| 289 p1EntryLen -= p1DataPartEnd; | |
| 290 p1EntryLen -= p2EntryRange; | |
| 291 // p2EntryEnd <= p1EntryAppliedEnd ==> p2EntryEnd <= p1EntryAppliedStart + p1EntryLen | |
| 292 // -p2EntryStart ==> p2EntryRange <= p1EntryAppliedStart-p2EntryStart + p1EntryLen | |
| 293 // p1EntryAppliedStart-p2EntryStart = -p1DataPartEnd ==> p2EntryRange <= p1EntryLen - p1DataEndPart | |
| 294 // +p1DataEndPart ==> p2EntryRange + p1DataEndPart <= p1EntryLen | |
| 295 assert p1EntryLen >= 0; | |
| 296 // p1EntryLen==0 with insideP1entry == true is nor really good here (gives empty patch elements x;x;0), | |
| 297 // however changing <= to < in p2EntryEnd <= p1EntryAppliedEnd above leads to errors | |
| 298 p1Data = subarray(p1Data, p1DataPartEnd+p2EntryRange, p1Data.length); | |
| 299 // augment total delta with p1EntryDelta part already consumed (p1EntryLen is pure insertion left for the next step) | |
| 300 p1TotalAppliedDelta += (p1EntryDelta - p1EntryLen); | |
| 301 continue L0; | |
| 281 } else { | 302 } else { |
| 282 p1DataPartEnd = p1EntryLen-1; // record factual number of p1 bytes we consumed. | 303 // p1 is consumed, take next |
| 283 r.add(p1EntryStart, s, p1Data); | |
| 284 } | |
| 285 p1TotalAppliedDelta += p1DataPartEnd - (s - p1EntryStart); // (s2 - (s1+delta)) - (s2 - delta - s1) = s2-s1-delta-s2+delta+s1 = 0, unless p1DataPartEnd >= p1Data.length | |
| 286 p1EntryLen -= (p1DataPartEnd+1); | |
| 287 if (p2EntryEnd < p1EntryAppliedEnd) { | |
| 288 // SKETCH: III | |
| 289 insideP1entry = true; | |
| 290 // p2 completely fits into changes of p1 | |
| 291 int e = p2EntryEnd - p1TotalAppliedDelta; // p2EntryEnd in p1 scale | |
| 292 if (e > p1EntryEnd) { | |
| 293 // any index past p1 end shall be calculated with respect to p1 end, thus it's unsafe to go past p1 end (there may be more p1 entries there) | |
| 294 e = p1EntryEnd; | |
| 295 } | |
| 296 r.add(s, e, p2Data); // add p2 | |
| 297 // modify p1 leftover | |
| 298 p1EntryStart = e; | |
| 299 if (p2EntryRange >= p1EntryLen) { | |
| 300 p1EntryLen = 0; | |
| 301 p1Data = new byte[0]; | |
| 302 } else { | |
| 303 p1Data = subarray(p1Data, p1DataPartEnd + p2EntryRange, p1Data.length-1 /*up to the last one*/); | |
| 304 p1EntryLen -= p2EntryRange; | |
| 305 } | |
| 306 // p2 is handled, but there are leftovers of p1 | |
| 307 continue L0; | |
| 308 } else { // p2EntryEnd >= p1EntryAppliedEnd | |
| 309 // SKETCH: II | |
| 310 insideP1entry = false; | 304 insideP1entry = false; |
| 311 p1++; | 305 p1++; |
| 312 if (p1EntryAppliedStart + p1EntryDelta >= p2EntryEnd) { | 306 insideP2entry = true; |
| 313 // here we know next p1 entry would be past p2 entry and thus can put p2 right away | 307 p2EntrySavedStart = p1EntryEnd; // this is how far we've progressed in p1 |
| 314 r.add(p2EntryStart - p1TotalAppliedDelta, p1EntryEnd, p2Data); | 308 // fall-through to get p1TotalAppliedDelta updated with consumed p1 |
| 315 p1TotalAppliedDelta += p1EntryDelta; | |
| 316 continue L0; | |
| 317 } else { | |
| 318 // there are chances there are more p1 entries till p2 ends | |
| 319 insideP2entry = true; | |
| 320 p2EntryStartOffset = p1TotalAppliedDelta; | |
| 321 // p2EntryEnd is past delta, no chances for p1Data leftovers to be in use | |
| 322 // p2 processing is not over, need to fix end, depending on what else fits into p2 range (if nothing, can put p2.end right away) | |
| 323 // fall-through to get p1TotalAppliedDelta incremented; | |
| 324 } | |
| 325 } | 309 } |
| 326 } | 310 } |
| 327 } else { // p1EntryAppliedStart >= p2EntryStart | 311 } else { // p1EntryAppliedStart >= p2EntryStart |
| 328 if (p2EntryEnd < p1EntryAppliedStart) { | 312 if (p2EntryEnd < p1EntryAppliedStart) { |
| 329 // newer patch completely fits between two older patches | 313 // newer patch completely fits between two older patches |
| 330 r.add(p2EntryStart - p1TotalAppliedDelta, p2EntryEnd - p1TotalAppliedDelta, p2Data); | 314 r.add(p2EntryStart - p1TotalAppliedDelta, p2EntryEnd - p1TotalAppliedDelta, p2Data); |
| 331 // SHALL NOT increment p1TotalAppliedDelta as we didn't use any of p1 | 315 // SHALL NOT increment p1TotalAppliedDelta as we didn't use any of p1 |
| 332 continue L0; // next p2 | 316 continue L0; // next p2 |
| 333 } else { // p2EntryEnd >= p1EntryAppliedStart | 317 } else { // p2EntryEnd >= p1EntryAppliedStart |
| 334 // SKETCH: I or IV | 318 // SKETCH: I or IV: |
| 335 // p2EntryEnd is either < p1EntryAppliedEnd or past it | 319 // p2 start is outside of p1 range. |
| 336 if (p2EntryEnd <= p1EntryAppliedEnd) { | 320 // |
| 321 // p2DataPartEnd: this is how many bytes prior to p1EntryStart is replaced by p2Data | |
| 322 int p2DataPartEnd = p1EntryAppliedStart - p2EntryStart; | |
| 323 if (p2EntryEnd < p1EntryAppliedEnd) { | |
| 337 // SKETCH: I: copy p2, strip p1 to start from p2EntryEnd, next i (p2) | 324 // SKETCH: I: copy p2, strip p1 to start from p2EntryEnd, next i (p2) |
| 338 insideP1entry = true; | 325 insideP1entry = true; |
| 339 int e = p2EntryEnd - p1TotalAppliedDelta; | 326 // replace whole p1 (extended to the left by (p2 \ p1) front bytes) |
| 340 if (e > p1EntryEnd) { | 327 r.add(p1EntryStart - p2DataPartEnd, p1EntryEnd, p2Data); |
| 341 e = p1EntryEnd; // added by analogy with above. Is needed? | 328 p1EntryStart = p1EntryEnd; |
| 342 } | 329 // see how much of p1 is left for insertion |
| 343 r.add(p2EntryStart - p1TotalAppliedDelta, e, p2Data); | 330 int p1DataPartEnd = p2EntryEnd - p1EntryAppliedStart; // #1 |
| 344 p1EntryStart = e; | 331 // Similar, although incorrect: p1DataPartEnd == p2Data.length - p2DataPartEnd; // #2 |
| 345 int p1DataShift = p2EntryEnd - p1EntryAppliedStart; | 332 // #1(p2EntryStart + p2DataLen) - p1EntryAppliedStart |
| 346 if (p1DataShift >= p1EntryLen) { | 333 // #2 p2DataLen - (p1EntryAppliedStart - p2EntryStart) |
| 347 p1EntryLen = 0; | 334 // but this works only in assumption that p2EntryEnd-p2EntryStart == p2Data.length |
| 348 p1Data = new byte[0]; | 335 // |
| 349 } else { | 336 // p1EntryAppliedStart <= p2EntryEnd < p1EntryAppliedStart + p1EntryLen |
| 350 p1EntryLen -= p1DataShift; | 337 // -p1EntryAppliedStart (to compare against p1DataPartEnd) ==> 0 <= p1DataPartEnd < p1EntryLen |
| 351 p1Data = subarray(p1Data, p1DataShift, p1Data.length - 1); | 338 assert p1DataPartEnd < p1EntryLen; |
| 352 } | 339 assert p1DataPartEnd >= 0; |
| 353 // p1TotalAppliedDelta would get incremented once this modified p1 is handled | 340 p1EntryLen -= p1DataPartEnd; |
| 341 p1Data = subarray(p1Data, p1DataPartEnd, p1Data.length); | |
| 342 | |
| 343 // p1TotalAppliedDelta XXX | |
| 344 p1TotalAppliedDelta += (p1EntryDelta - p1EntryLen); | |
| 354 continue L0; // next p2; | 345 continue L0; // next p2; |
| 355 } else { | 346 } else { |
| 356 // p2EntryEnd > p1EntryAppliedEnd | 347 // p2EntryEnd >= p1EntryAppliedEnd |
| 357 // SKETCH IV: skip (rest of) p1 completely, continue the same unless found p1 with start or end past p2EntryEnd. | 348 // SKETCH IV: skip (rest of) p1 completely, continue the same unless found p1 with start or end past p2EntryEnd. |
| 358 insideP1entry = false; | 349 insideP1entry = false; |
| 350 // p1 consumed | |
| 359 p1++; | 351 p1++; |
| 360 insideP2entry = true; | 352 insideP2entry = true; |
| 361 p2EntryStartOffset = p1TotalAppliedDelta; | 353 // extend to the left of p1 by p2 \ p1 front bytes |
| 354 p2EntrySavedStart = p1EntryStart - p2DataPartEnd; | |
| 362 // fall-through to get p1TotalAppliedDelta incremented | 355 // fall-through to get p1TotalAppliedDelta incremented |
| 363 } | 356 } |
| 364 } | 357 } |
| 365 } | 358 } |
| 366 p1TotalAppliedDelta += p1EntryDelta; | 359 p1TotalAppliedDelta += p1EntryDelta; |
| 367 } // while (p1 < p1Max) | 360 } // while (p1 < p1Max) |
| 368 { | 361 { |
| 369 // no more p1 entries, shall close p2 (if it's handled, code above jumps directly to L0) | 362 // no more p1 entries, shall close p2 (if it's handled, code above jumps directly to L0) |
| 370 // regardless of whether insideP2 is .t | 363 // regardless of whether insideP2 is .t |
| 371 int s = p2EntryStart; | 364 int s = p2EntrySavedStart != -1 ? p2EntrySavedStart : p2EntryStart - p1TotalAppliedDelta; |
| 372 // p2EntryStartOffset != -1 when we started p2 entry processing, but not completed | 365 // p2EntrySavedStart != -1 when we started p2 entry processing, but not completed |
| 373 // if we handled last p1 entry but didn't start with p2 entry processing, it's -1 and regular p1 delta shall be used | 366 // if we handled last p1 entry but didn't start with p2 entry processing, it's -1 and regular p1 delta shall be used |
| 374 s -= p2EntryStartOffset == -1 ? p1TotalAppliedDelta : p2EntryStartOffset; | |
| 375 r.add(s, p2EntryEnd - p1TotalAppliedDelta, p2Data); | 367 r.add(s, p2EntryEnd - p1TotalAppliedDelta, p2Data); |
| 376 } | 368 } |
| 377 } | 369 } |
| 378 if (p1 < p1Max && insideP1entry) { | 370 if (p1 < p1Max && insideP1entry) { |
| 379 r.add(p1EntryStart, p1EntryEnd, p1Data); | 371 r.add(p1EntryStart, p1EntryEnd, p1Data); |
| 384 p1++; | 376 p1++; |
| 385 }; | 377 }; |
| 386 return r; | 378 return r; |
| 387 } | 379 } |
| 388 | 380 |
| 381 /** | |
| 382 * Combine consecutive regions into one. | |
| 383 * XXX NOW only combines two subsequent regions, seems enough for quick test | |
| 384 * @return <code>this</code> or new instance of consecutive regions found | |
| 385 */ | |
| 386 public Patch normalize() { | |
| 387 Patch rv = null; | |
| 388 for (int i = 1, x = data.size(); i < x; i++) { | |
| 389 if (starts.get(i) == ends.get(i-1)) { | |
| 390 if (rv == null) { | |
| 391 rv = new Patch(); | |
| 392 rv.copyOf(this, 0, i-1); | |
| 393 // } else if (ends.get(i-1) == rv.ends.get(rv.ends.size()-1)) { | |
| 394 // // "JUST IN CASE" code, i++ below prevents us from getting here | |
| 395 // // if the last region is the one already merged, | |
| 396 // // ignore this occurrence (otherwise data(i-1) would get copied again) | |
| 397 // continue; | |
| 398 } | |
| 399 byte[] d1 = data.get(i-1); | |
| 400 byte[] d = data.get(i); | |
| 401 byte[] nd; | |
| 402 if (d1.length > 0 && d.length > 0) { | |
| 403 nd = new byte[d1.length + d.length]; | |
| 404 System.arraycopy(d1, 0, nd, 0, d1.length); | |
| 405 System.arraycopy(d, 0, nd, d1.length, d.length); | |
| 406 } else { | |
| 407 nd = d1.length == 0 ? d : d1 ; | |
| 408 } | |
| 409 rv.add(starts.get(i-1), ends.get(i), nd); | |
| 410 i++; // skip i-th element (effectively means we detect only pairs) | |
| 411 // without this ++, element(i-1) is added to rv once "else" below is hit on the next step | |
| 412 } else { | |
| 413 if (rv != null) { | |
| 414 rv.add(this, i-1); | |
| 415 } | |
| 416 } | |
| 417 } | |
| 418 if (rv == null) { | |
| 419 return this; | |
| 420 } else { | |
| 421 int last = count() - 1; | |
| 422 if (starts.get(last) != ends.get(last-1)) { | |
| 423 rv.add(this, last); | |
| 424 } | |
| 425 } | |
| 426 return rv; | |
| 427 } | |
| 428 | |
| 429 private void copyOf(Patch another, int fromIndex, int upToIndex) { | |
| 430 while(fromIndex < upToIndex) { | |
| 431 add(another, fromIndex++); | |
| 432 } | |
| 433 } | |
| 434 | |
| 389 public class PatchDataSource implements DataSerializer.DataSource { | 435 public class PatchDataSource implements DataSerializer.DataSource { |
| 390 | 436 |
| 391 public void serialize(DataSerializer out) throws IOException { | 437 public void serialize(DataSerializer out) throws IOException { |
| 392 Patch.this.serialize(out); | 438 Patch.this.serialize(out); |
| 393 } | 439 } |
