Mercurial > hg4j
comparison src/org/tmatesoft/hg/repo/HgDataFile.java @ 425:48f993aa2f41
FIXMEs: exceptions, javadoc
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 28 Mar 2012 18:39:29 +0200 |
| parents | 6437d261048a |
| children | 063b0663495a |
comparison
equal
deleted
inserted
replaced
| 424:6437d261048a | 425:48f993aa2f41 |
|---|---|
| 27 import java.nio.channels.FileChannel; | 27 import java.nio.channels.FileChannel; |
| 28 import java.util.ArrayList; | 28 import java.util.ArrayList; |
| 29 import java.util.Arrays; | 29 import java.util.Arrays; |
| 30 import java.util.Collection; | 30 import java.util.Collection; |
| 31 | 31 |
| 32 import org.tmatesoft.hg.core.HgException; | 32 import org.tmatesoft.hg.core.HgChangesetFileSneaker; |
| 33 import org.tmatesoft.hg.core.Nodeid; | 33 import org.tmatesoft.hg.core.Nodeid; |
| 34 import org.tmatesoft.hg.internal.DataAccess; | 34 import org.tmatesoft.hg.internal.DataAccess; |
| 35 import org.tmatesoft.hg.internal.FilterByteChannel; | 35 import org.tmatesoft.hg.internal.FilterByteChannel; |
| 36 import org.tmatesoft.hg.internal.FilterDataAccess; | 36 import org.tmatesoft.hg.internal.FilterDataAccess; |
| 37 import org.tmatesoft.hg.internal.IntMap; | 37 import org.tmatesoft.hg.internal.IntMap; |
| 46 import org.tmatesoft.hg.util.ProgressSupport; | 46 import org.tmatesoft.hg.util.ProgressSupport; |
| 47 | 47 |
| 48 | 48 |
| 49 | 49 |
| 50 /** | 50 /** |
| 51 * ? name:HgFileNode? | 51 * Regular user data file stored in the repository. |
| 52 * | |
| 53 * <p> Note, most methods accept index in the file's revision history, not that of changelog. Easy way to obtain | |
| 54 * changeset revision index from file's is to use {@link #getChangesetRevisionIndex(int)}. To obtain file's revision | |
| 55 * index for a given changeset, {@link HgManifest#getFileRevision(int, Path)} or {@link HgChangesetFileSneaker} may | |
| 56 * come handy. | |
| 52 * | 57 * |
| 53 * @author Artem Tikhomirov | 58 * @author Artem Tikhomirov |
| 54 * @author TMate Software Ltd. | 59 * @author TMate Software Ltd. |
| 55 */ | 60 */ |
| 56 public class HgDataFile extends Revlog { | 61 public class HgDataFile extends Revlog { |
| 86 * Handy shorthand for {@link #getLength(int) length(getRevisionIndex(nodeid))} | 91 * Handy shorthand for {@link #getLength(int) length(getRevisionIndex(nodeid))} |
| 87 * | 92 * |
| 88 * @param nodeid revision of the file | 93 * @param nodeid revision of the file |
| 89 * | 94 * |
| 90 * @return size of the file content at the given revision | 95 * @return size of the file content at the given revision |
| 91 * @throws HgInvalidRevisionException if supplied nodeid doesn't identify any revision from this revlog (<em>runtime exception</em>) | 96 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 92 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 97 */ |
| 93 */ | 98 public int getLength(Nodeid nodeid) throws HgRuntimeException { |
| 94 public int getLength(Nodeid nodeid) throws HgInvalidControlFileException, HgInvalidRevisionException { | |
| 95 try { | 99 try { |
| 96 return getLength(getRevisionIndex(nodeid)); | 100 return getLength(getRevisionIndex(nodeid)); |
| 97 } catch (HgInvalidControlFileException ex) { | 101 } catch (HgInvalidControlFileException ex) { |
| 98 throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid); | 102 throw ex.isRevisionSet() ? ex : ex.setRevision(nodeid); |
| 99 } catch (HgInvalidRevisionException ex) { | 103 } catch (HgInvalidRevisionException ex) { |
| 102 } | 106 } |
| 103 | 107 |
| 104 /** | 108 /** |
| 105 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. | 109 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, only {@link HgRepository#TIP} makes sense. |
| 106 * @return size of the file content at the revision identified by local revision number. | 110 * @return size of the file content at the revision identified by local revision number. |
| 107 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>) | 111 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 108 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 112 */ |
| 109 */ | 113 public int getLength(int fileRevisionIndex) throws HgRuntimeException { |
| 110 public int getLength(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { | |
| 111 if (wrongRevisionIndex(fileRevisionIndex) || fileRevisionIndex == BAD_REVISION) { | 114 if (wrongRevisionIndex(fileRevisionIndex) || fileRevisionIndex == BAD_REVISION) { |
| 112 throw new HgInvalidRevisionException(fileRevisionIndex); | 115 throw new HgInvalidRevisionException(fileRevisionIndex); |
| 113 } | 116 } |
| 114 if (fileRevisionIndex == TIP) { | 117 if (fileRevisionIndex == TIP) { |
| 115 fileRevisionIndex = getLastRevision(); | 118 fileRevisionIndex = getLastRevision(); |
| 143 * | 146 * |
| 144 * NOTE, if file is missing from the working directory and is not part of the dirstate (but otherwise legal repository file, | 147 * NOTE, if file is missing from the working directory and is not part of the dirstate (but otherwise legal repository file, |
| 145 * e.g. from another branch), no content would be supplied. | 148 * e.g. from another branch), no content would be supplied. |
| 146 * | 149 * |
| 147 * @param sink content consumer | 150 * @param sink content consumer |
| 148 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | |
| 149 * @throws HgInvalidFileException if access to file in working directory failed | |
| 150 * @throws CancelledException if execution of the operation was cancelled | 151 * @throws CancelledException if execution of the operation was cancelled |
| 151 */ | 152 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 152 public void workingCopy(ByteChannel sink) throws HgException, CancelledException { | 153 */ |
| 154 public void workingCopy(ByteChannel sink) throws CancelledException, HgRuntimeException { | |
| 153 File f = getRepo().getFile(this); | 155 File f = getRepo().getFile(this); |
| 154 if (f.exists()) { | 156 if (f.exists()) { |
| 155 final CancelSupport cs = CancelSupport.Factory.get(sink); | 157 final CancelSupport cs = CancelSupport.Factory.get(sink); |
| 156 final ProgressSupport progress = ProgressSupport.Factory.get(sink); | 158 final ProgressSupport progress = ProgressSupport.Factory.get(sink); |
| 157 final long flength = f.length(); | 159 final long flength = f.length(); |
| 232 * XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves? | 234 * XXX not sure distinct method contentWithFilters() is the best way to do, perhaps, callers shall add filters themselves? |
| 233 * | 235 * |
| 234 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. | 236 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. |
| 235 * @param sink content consumer | 237 * @param sink content consumer |
| 236 * | 238 * |
| 237 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | |
| 238 * @throws HgInvalidFileException if access to file in working directory failed | |
| 239 * @throws CancelledException if execution of the operation was cancelled | 239 * @throws CancelledException if execution of the operation was cancelled |
| 240 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>) | 240 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 241 */ | 241 */ |
| 242 public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { | 242 public void contentWithFilters(int fileRevisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException { |
| 243 if (fileRevisionIndex == WORKING_COPY) { | 243 if (fileRevisionIndex == WORKING_COPY) { |
| 244 workingCopy(sink); // pass un-mangled sink | 244 workingCopy(sink); // pass un-mangled sink |
| 245 } else { | 245 } else { |
| 246 content(fileRevisionIndex, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath()))); | 246 content(fileRevisionIndex, new FilterByteChannel(sink, getRepo().getFiltersFromRepoToWorkingDir(getPath()))); |
| 247 } | 247 } |
| 252 * For filtered content, use {@link #contentWithFilters(int, ByteChannel)}. | 252 * For filtered content, use {@link #contentWithFilters(int, ByteChannel)}. |
| 253 * | 253 * |
| 254 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. | 254 * @param fileRevisionIndex - revision local index, non-negative. From predefined constants, {@link HgRepository#TIP} and {@link HgRepository#WORKING_COPY} make sense. |
| 255 * @param sink content consumer | 255 * @param sink content consumer |
| 256 * | 256 * |
| 257 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | |
| 258 * @throws HgInvalidFileException if access to file in working directory failed | |
| 259 * @throws CancelledException if execution of the operation was cancelled | 257 * @throws CancelledException if execution of the operation was cancelled |
| 260 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog (<em>runtime exception</em>) | 258 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 261 */ | 259 */ |
| 262 public void content(int fileRevisionIndex, ByteChannel sink) throws HgException, CancelledException, HgInvalidRevisionException { | 260 public void content(int fileRevisionIndex, ByteChannel sink) throws CancelledException, HgRuntimeException { |
| 263 // for data files need to check heading of the file content for possible metadata | 261 // for data files need to check heading of the file content for possible metadata |
| 264 // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- | 262 // @see http://mercurial.selenic.com/wiki/FileFormats#data.2BAC8- |
| 265 if (fileRevisionIndex == TIP) { | 263 if (fileRevisionIndex == TIP) { |
| 266 fileRevisionIndex = getLastRevision(); | 264 fileRevisionIndex = getLastRevision(); |
| 267 } | 265 } |
| 291 insp = new MetadataInspector(metadata, lf, new ContentPipe(sink, 0, lf)); | 289 insp = new MetadataInspector(metadata, lf, new ContentPipe(sink, 0, lf)); |
| 292 } | 290 } |
| 293 insp.checkCancelled(); | 291 insp.checkCancelled(); |
| 294 super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); | 292 super.content.iterate(fileRevisionIndex, fileRevisionIndex, true, insp); |
| 295 try { | 293 try { |
| 296 insp.checkFailed(); // XXX is there real need to throw IOException from ContentPipe? | 294 insp.checkFailed(); |
| 297 } catch (HgInvalidControlFileException ex) { | 295 } catch (HgInvalidControlFileException ex) { |
| 298 ex = ex.setFileName(getPath()); | 296 ex = ex.setFileName(getPath()); |
| 299 throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex); | 297 throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(fileRevisionIndex); |
| 300 } catch (IOException ex) { | 298 } catch (IOException ex) { |
| 301 HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); | 299 HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); |
| 302 throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); | 300 throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); |
| 303 } catch (HgException ex) { | 301 } |
| 304 // shall not happen, unless we changed ContentPipe or its subclass | 302 } |
| 305 HgInvalidControlFileException e = new HgInvalidControlFileException("Revision content access failed", ex, null); | 303 |
| 306 throw content.initWithIndexFile(e).setFileName(getPath()).setRevisionIndex(fileRevisionIndex); | 304 /** |
| 307 } | 305 * Walk complete change history of the file. |
| 308 } | 306 * @param inspector callback to visit changesets |
| 309 | 307 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 310 // FIXME is that still needed? | 308 */ |
| 311 public void history(HgChangelog.Inspector inspector) throws HgInvalidControlFileException { | 309 public void history(HgChangelog.Inspector inspector) throws HgRuntimeException { |
| 312 history(0, getLastRevision(), inspector); | 310 history(0, getLastRevision(), inspector); |
| 313 } | 311 } |
| 314 | 312 |
| 315 /** | 313 /** |
| 316 * | 314 * Walk subset of the file's change history. |
| 317 * @param start local revision index | 315 * @param start revision local index, inclusive; non-negative or {@link HgRepository#TIP} |
| 318 * @param end local revision index | 316 * @param end revision local index, inclusive; non-negative or {@link HgRepository#TIP} |
| 319 * @param inspector | 317 * @param inspector callback to visit changesets |
| 320 * FIXME EXCEPTIONS | 318 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 321 * @throws HgInvalidRevisionException | 319 */ |
| 322 * @throws HgInvalidControlFileException | 320 public void history(int start, int end, HgChangelog.Inspector inspector) throws HgRuntimeException { |
| 323 */ | |
| 324 public void history(int start, int end, HgChangelog.Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { | |
| 325 if (!exists()) { | 321 if (!exists()) { |
| 326 throw new IllegalStateException("Can't get history of invalid repository file node"); | 322 throw new IllegalStateException("Can't get history of invalid repository file node"); |
| 327 } | 323 } |
| 328 final int last = getLastRevision(); | 324 final int last = getLastRevision(); |
| 329 if (end == TIP) { | 325 if (end == TIP) { |
| 359 | 355 |
| 360 /** | 356 /** |
| 361 * For a given revision of the file (identified with revision index), find out index of the corresponding changeset. | 357 * For a given revision of the file (identified with revision index), find out index of the corresponding changeset. |
| 362 * | 358 * |
| 363 * @return changeset revision index | 359 * @return changeset revision index |
| 364 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog | 360 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 365 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 361 */ |
| 366 */ | 362 public int getChangesetRevisionIndex(int revision) throws HgRuntimeException { |
| 367 public int getChangesetRevisionIndex(int revision) throws HgInvalidControlFileException, HgInvalidRevisionException { | |
| 368 return content.linkRevision(revision); | 363 return content.linkRevision(revision); |
| 369 } | 364 } |
| 370 | 365 |
| 371 /** | 366 /** |
| 372 * Complements {@link #getChangesetRevisionIndex(int)} to get changeset revision that corresponds to supplied file revision | 367 * Complements {@link #getChangesetRevisionIndex(int)} to get changeset revision that corresponds to supplied file revision |
| 373 * | 368 * |
| 374 * @param nid revision of the file | 369 * @param nid revision of the file |
| 375 * @return changeset revision | 370 * @return changeset revision |
| 376 * @throws HgInvalidRevisionException if supplied argument doesn't represent revision index in this revlog | 371 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 377 * @throws HgInvalidControlFileException if access to revlog index/data entry failed | 372 */ |
| 378 */ | 373 public Nodeid getChangesetRevision(Nodeid nid) throws HgRuntimeException { |
| 379 public Nodeid getChangesetRevision(Nodeid nid) throws HgInvalidControlFileException, HgInvalidRevisionException { | |
| 380 int changelogRevision = getChangesetRevisionIndex(getRevisionIndex(nid)); | 374 int changelogRevision = getChangesetRevisionIndex(getRevisionIndex(nid)); |
| 381 return getRepo().getChangelog().getRevision(changelogRevision); | 375 return getRepo().getChangelog().getRevision(changelogRevision); |
| 382 } | 376 } |
| 383 | 377 |
| 384 /** | 378 /** |
| 385 * Tells whether this file originates from another repository file | 379 * Tells whether this file originates from another repository file |
| 386 * @return <code>true</code> if this file is a copy of another from the repository | 380 * @return <code>true</code> if this file is a copy of another from the repository |
| 387 * @throws HgInvalidControlFileException if access to revlog or file metadata failed | 381 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 388 */ | 382 */ |
| 389 public boolean isCopy() throws HgInvalidControlFileException { | 383 public boolean isCopy() throws HgRuntimeException { |
| 390 if (metadata == null || !metadata.checked(0)) { | 384 if (metadata == null || !metadata.checked(0)) { |
| 391 checkAndRecordMetadata(0); | 385 checkAndRecordMetadata(0); |
| 392 } | 386 } |
| 393 if (!metadata.known(0)) { | 387 if (!metadata.known(0)) { |
| 394 return false; | 388 return false; |
| 398 | 392 |
| 399 /** | 393 /** |
| 400 * Get name of the file this one was copied from. | 394 * Get name of the file this one was copied from. |
| 401 * | 395 * |
| 402 * @return name of the file origin | 396 * @return name of the file origin |
| 403 * @throws HgInvalidControlFileException if access to revlog or file metadata failed | 397 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 404 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) | 398 */ |
| 405 */ | 399 public Path getCopySourceName() throws HgRuntimeException { |
| 406 public Path getCopySourceName() throws HgInvalidControlFileException { | |
| 407 if (isCopy()) { | 400 if (isCopy()) { |
| 408 return Path.create(metadata.find(0, "copy")); | 401 return Path.create(metadata.find(0, "copy")); |
| 409 } | 402 } |
| 410 throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) | 403 throw new UnsupportedOperationException(); // XXX REVISIT, think over if Exception is good (clients would check isCopy() anyway, perhaps null is sufficient?) |
| 411 } | 404 } |
| 412 | 405 |
| 413 /** | 406 /** |
| 414 * | 407 * |
| 415 * @return revision this file was copied from | 408 * @return revision this file was copied from |
| 416 * @throws HgInvalidControlFileException if access to revlog or file metadata failed | 409 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 417 * @throws UnsupportedOperationException if this file doesn't represent a copy ({@link #isCopy()} was false) | 410 */ |
| 418 */ | 411 public Nodeid getCopySourceRevision() throws HgRuntimeException { |
| 419 public Nodeid getCopySourceRevision() throws HgInvalidControlFileException { | |
| 420 if (isCopy()) { | 412 if (isCopy()) { |
| 421 return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid | 413 return Nodeid.fromAscii(metadata.find(0, "copyrev")); // XXX reuse/cache Nodeid |
| 422 } | 414 } |
| 423 throw new UnsupportedOperationException(); | 415 throw new UnsupportedOperationException(); |
| 424 } | 416 } |
| 425 | 417 |
| 426 /** | 418 /** |
| 427 * Get file flags recorded in the manifest | 419 * Get file flags recorded in the manifest |
| 428 * @param fileRevisionIndex - revision local index, non-negative, or {@link HgRepository#TIP}. | 420 * @param fileRevisionIndex - revision local index, non-negative, or {@link HgRepository#TIP}. |
| 429 * @see HgManifest#getFileFlags(int, Path) | 421 * @see HgManifest#getFileFlags(int, Path) |
| 430 * FIXME EXCEPTIONS | 422 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em> |
| 431 * @throws HgInvalidControlFileException | 423 */ |
| 432 * @throws HgInvalidRevisionException | 424 public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgRuntimeException { |
| 433 */ | |
| 434 public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { | |
| 435 int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex); | 425 int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex); |
| 436 return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath()); | 426 return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath()); |
| 437 } | 427 } |
| 438 | 428 |
| 439 @Override | 429 @Override |
| 460 }); | 450 }); |
| 461 } catch (CancelledException ex) { | 451 } catch (CancelledException ex) { |
| 462 // it's ok, we did that | 452 // it's ok, we did that |
| 463 } catch (HgInvalidControlFileException ex) { | 453 } catch (HgInvalidControlFileException ex) { |
| 464 throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(localRev); | 454 throw ex.isRevisionIndexSet() ? ex : ex.setRevisionIndex(localRev); |
| 465 } catch (HgException ex) { | |
| 466 // metadata comes from the content, hence initWithDataFile | |
| 467 throw content.initWithDataFile(new HgInvalidControlFileException(null, ex, null)); | |
| 468 } | 455 } |
| 469 } | 456 } |
| 470 | 457 |
| 471 private static final class MetadataEntry { | 458 private static final class MetadataEntry { |
| 472 private final String entry; | 459 private final String entry; |
| 652 } | 639 } |
| 653 return lastEntryStart; | 640 return lastEntryStart; |
| 654 } | 641 } |
| 655 | 642 |
| 656 @Override | 643 @Override |
| 657 public void checkFailed() throws HgException, IOException, CancelledException { | 644 public void checkFailed() throws HgRuntimeException, IOException, CancelledException { |
| 658 super.checkFailed(); | 645 super.checkFailed(); |
| 659 if (delegate instanceof ErrorHandlingInspector) { | 646 if (delegate instanceof ErrorHandlingInspector) { |
| 660 // XXX need to add ErrorDestination and pass it around (much like CancelSupport get passed) | 647 // TODO need to add ErrorDestination (ErrorTarget/Acceptor?) and pass it around (much like CancelSupport get passed) |
| 661 // so that delegate would be able report its failures directly to caller without this hack | 648 // so that delegate would be able report its failures directly to caller without this hack |
| 662 ((ErrorHandlingInspector) delegate).checkFailed(); | 649 ((ErrorHandlingInspector) delegate).checkFailed(); |
| 663 } | 650 } |
| 664 } | 651 } |
| 665 } | 652 } |
