1 /* 2 * Copyright (C) 2008-2009, Google Inc. 3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 4 * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com> 5 * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> 6 * and other copyright owners as documented in the project's IP log. 7 * 8 * This program and the accompanying materials are made available 9 * under the terms of the Eclipse Distribution License v1.0 which 10 * accompanies this distribution, is reproduced below, and is 11 * available at http://www.eclipse.org/org/documents/edl-v10.php 12 * 13 * All rights reserved. 14 * 15 * Redistribution and use in source and binary forms, with or 16 * without modification, are permitted provided that the following 17 * conditions are met: 18 * 19 * - Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 22 * - Redistributions in binary form must reproduce the above 23 * copyright notice, this list of conditions and the following 24 * disclaimer in the documentation and/or other materials provided 25 * with the distribution. 26 * 27 * - Neither the name of the Eclipse Foundation, Inc. nor the 28 * names of its contributors may be used to endorse or promote 29 * products derived from this software without specific prior 30 * written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 33 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 34 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 37 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 39 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 40 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 42 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 43 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 44 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 */ 46 47 package org.eclipse.jgit.dircache; 48 49 import java.io.ByteArrayOutputStream; 50 import java.io.EOFException; 51 import java.io.IOException; 52 import java.io.InputStream; 53 import java.io.OutputStream; 54 import java.nio.ByteBuffer; 55 import java.security.MessageDigest; 56 import java.text.MessageFormat; 57 import java.util.Arrays; 58 59 import org.eclipse.jgit.errors.CorruptObjectException; 60 import org.eclipse.jgit.internal.JGitText; 61 import org.eclipse.jgit.lib.AnyObjectId; 62 import org.eclipse.jgit.lib.Constants; 63 import org.eclipse.jgit.lib.FileMode; 64 import org.eclipse.jgit.lib.ObjectId; 65 import org.eclipse.jgit.util.IO; 66 import org.eclipse.jgit.util.MutableInteger; 67 import org.eclipse.jgit.util.NB; 68 69 /** 70 * A single file (or stage of a file) in a {@link DirCache}. 71 * <p> 72 * An entry represents exactly one stage of a file. If a file path is unmerged 73 * then multiple DirCacheEntry instances may appear for the same path name. 74 */ 75 public class DirCacheEntry { 76 private static final byte[] nullpad = new byte[8]; 77 78 /** The standard (fully merged) stage for an entry. */ 79 public static final int STAGE_0 = 0; 80 81 /** The base tree revision for an entry. */ 82 public static final int STAGE_1 = 1; 83 84 /** The first tree revision (usually called "ours"). */ 85 public static final int STAGE_2 = 2; 86 87 /** The second tree revision (usually called "theirs"). */ 88 public static final int STAGE_3 = 3; 89 90 private static final int P_CTIME = 0; 91 92 // private static final int P_CTIME_NSEC = 4; 93 94 private static final int P_MTIME = 8; 95 96 // private static final int P_MTIME_NSEC = 12; 97 98 // private static final int P_DEV = 16; 99 100 // private static final int P_INO = 20; 101 102 private static final int P_MODE = 24; 103 104 // private static final int P_UID = 28; 105 106 // private static final int P_GID = 32; 107 108 private static final int P_SIZE = 36; 109 110 private static final int P_OBJECTID = 40; 111 112 private static final int P_FLAGS = 60; 113 private static final int P_FLAGS2 = 62; 114 115 /** Mask applied to data in {@link #P_FLAGS} to get the name length. */ 116 private static final int NAME_MASK = 0xfff; 117 118 private static final int INTENT_TO_ADD = 0x20000000; 119 private static final int SKIP_WORKTREE = 0x40000000; 120 private static final int EXTENDED_FLAGS = (INTENT_TO_ADD | SKIP_WORKTREE); 121 122 private static final int INFO_LEN = 62; 123 private static final int INFO_LEN_EXTENDED = 64; 124 125 private static final int EXTENDED = 0x40; 126 private static final int ASSUME_VALID = 0x80; 127 128 /** In-core flag signaling that the entry should be considered as modified. */ 129 private static final int UPDATE_NEEDED = 0x1; 130 131 /** (Possibly shared) header information storage. */ 132 private final byte[] info; 133 134 /** First location within {@link #info} where our header starts. */ 135 private final int infoOffset; 136 137 /** Our encoded path name, from the root of the repository. */ 138 final byte[] path; 139 140 /** Flags which are never stored to disk. */ 141 private byte inCoreFlags; 142 143 DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, 144 final InputStream in, final MessageDigest md, final int smudge_s, 145 final int smudge_ns) throws IOException { 146 info = sharedInfo; 147 infoOffset = infoAt.value; 148 149 IO.readFully(in, info, infoOffset, INFO_LEN); 150 151 final int len; 152 if (isExtended()) { 153 len = INFO_LEN_EXTENDED; 154 IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN); 155 156 if ((getExtendedFlags() & ~EXTENDED_FLAGS) != 0) 157 throw new IOException(MessageFormat.format(JGitText.get() 158 .DIRCUnrecognizedExtendedFlags, String.valueOf(getExtendedFlags()))); 159 } else 160 len = INFO_LEN; 161 162 infoAt.value += len; 163 md.update(info, infoOffset, len); 164 165 int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK; 166 int skipped = 0; 167 if (pathLen < NAME_MASK) { 168 path = new byte[pathLen]; 169 IO.readFully(in, path, 0, pathLen); 170 md.update(path, 0, pathLen); 171 } else { 172 final ByteArrayOutputStream tmp = new ByteArrayOutputStream(); 173 { 174 final byte[] buf = new byte[NAME_MASK]; 175 IO.readFully(in, buf, 0, NAME_MASK); 176 tmp.write(buf); 177 } 178 for (;;) { 179 final int c = in.read(); 180 if (c < 0) 181 throw new EOFException(JGitText.get().shortReadOfBlock); 182 if (c == 0) 183 break; 184 tmp.write(c); 185 } 186 path = tmp.toByteArray(); 187 pathLen = path.length; 188 skipped = 1; // we already skipped 1 '\0' above to break the loop. 189 md.update(path, 0, pathLen); 190 md.update((byte) 0); 191 } 192 193 try { 194 DirCacheCheckout.checkValidPath(toString(path)); 195 } catch (InvalidPathException e) { 196 CorruptObjectException p = 197 new CorruptObjectException(e.getMessage()); 198 if (e.getCause() != null) 199 p.initCause(e.getCause()); 200 throw p; 201 } 202 203 // Index records are padded out to the next 8 byte alignment 204 // for historical reasons related to how C Git read the files. 205 // 206 final int actLen = len + pathLen; 207 final int expLen = (actLen + 8) & ~7; 208 final int padLen = expLen - actLen - skipped; 209 if (padLen > 0) { 210 IO.skipFully(in, padLen); 211 md.update(nullpad, 0, padLen); 212 } 213 214 if (mightBeRacilyClean(smudge_s, smudge_ns)) 215 smudgeRacilyClean(); 216 } 217 218 /** 219 * Create an empty entry at stage 0. 220 * 221 * @param newPath 222 * name of the cache entry. 223 * @throws IllegalArgumentException 224 * If the path starts or ends with "/", or contains "//" either 225 * "\0". These sequences are not permitted in a git tree object 226 * or DirCache file. 227 */ 228 public DirCacheEntry(final String newPath) { 229 this(Constants.encode(newPath), STAGE_0); 230 } 231 232 /** 233 * Create an empty entry at the specified stage. 234 * 235 * @param newPath 236 * name of the cache entry. 237 * @param stage 238 * the stage index of the new entry. 239 * @throws IllegalArgumentException 240 * If the path starts or ends with "/", or contains "//" either 241 * "\0". These sequences are not permitted in a git tree object 242 * or DirCache file. Or if {@code stage} is outside of the 243 * range 0..3, inclusive. 244 */ 245 public DirCacheEntry(final String newPath, final int stage) { 246 this(Constants.encode(newPath), stage); 247 } 248 249 /** 250 * Create an empty entry at stage 0. 251 * 252 * @param newPath 253 * name of the cache entry, in the standard encoding. 254 * @throws IllegalArgumentException 255 * If the path starts or ends with "/", or contains "//" either 256 * "\0". These sequences are not permitted in a git tree object 257 * or DirCache file. 258 */ 259 public DirCacheEntry(final byte[] newPath) { 260 this(newPath, STAGE_0); 261 } 262 263 /** 264 * Create an empty entry at the specified stage. 265 * 266 * @param newPath 267 * name of the cache entry, in the standard encoding. 268 * @param stage 269 * the stage index of the new entry. 270 * @throws IllegalArgumentException 271 * If the path starts or ends with "/", or contains "//" either 272 * "\0". These sequences are not permitted in a git tree object 273 * or DirCache file. Or if {@code stage} is outside of the 274 * range 0..3, inclusive. 275 */ 276 @SuppressWarnings("boxing") 277 public DirCacheEntry(final byte[] newPath, final int stage) { 278 DirCacheCheckout.checkValidPath(toString(newPath)); 279 if (stage < 0 || 3 < stage) 280 throw new IllegalArgumentException(MessageFormat.format( 281 JGitText.get().invalidStageForPath, 282 stage, toString(newPath))); 283 284 info = new byte[INFO_LEN]; 285 infoOffset = 0; 286 path = newPath; 287 288 int flags = ((stage & 0x3) << 12); 289 if (path.length < NAME_MASK) 290 flags |= path.length; 291 else 292 flags |= NAME_MASK; 293 NB.encodeInt16(info, infoOffset + P_FLAGS, flags); 294 } 295 296 void write(final OutputStream os) throws IOException { 297 final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN; 298 final int pathLen = path.length; 299 os.write(info, infoOffset, len); 300 os.write(path, 0, pathLen); 301 302 // Index records are padded out to the next 8 byte alignment 303 // for historical reasons related to how C Git read the files. 304 // 305 final int actLen = len + pathLen; 306 final int expLen = (actLen + 8) & ~7; 307 if (actLen != expLen) 308 os.write(nullpad, 0, expLen - actLen); 309 } 310 311 /** 312 * Is it possible for this entry to be accidentally assumed clean? 313 * <p> 314 * The "racy git" problem happens when a work file can be updated faster 315 * than the filesystem records file modification timestamps. It is possible 316 * for an application to edit a work file, update the index, then edit it 317 * again before the filesystem will give the work file a new modification 318 * timestamp. This method tests to see if file was written out at the same 319 * time as the index. 320 * 321 * @param smudge_s 322 * seconds component of the index's last modified time. 323 * @param smudge_ns 324 * nanoseconds component of the index's last modified time. 325 * @return true if extra careful checks should be used. 326 */ 327 public final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) { 328 // If the index has a modification time then it came from disk 329 // and was not generated from scratch in memory. In such cases 330 // the entry is 'racily clean' if the entry's cached modification 331 // time is equal to or later than the index modification time. In 332 // such cases the work file is too close to the index to tell if 333 // it is clean or not based on the modification time alone. 334 // 335 final int base = infoOffset + P_MTIME; 336 final int mtime = NB.decodeInt32(info, base); 337 if (smudge_s == mtime) 338 return smudge_ns <= NB.decodeInt32(info, base + 4); 339 return false; 340 } 341 342 /** 343 * Force this entry to no longer match its working tree file. 344 * <p> 345 * This avoids the "racy git" problem by making this index entry no longer 346 * match the file in the working directory. Later git will be forced to 347 * compare the file content to ensure the file matches the working tree. 348 */ 349 public final void smudgeRacilyClean() { 350 // To mark an entry racily clean we set its length to 0 (like native git 351 // does). Entries which are not racily clean and have zero length can be 352 // distinguished from racily clean entries by checking P_OBJECTID 353 // against the SHA1 of empty content. When length is 0 and P_OBJECTID is 354 // different from SHA1 of empty content we know the entry is marked 355 // racily clean 356 final int base = infoOffset + P_SIZE; 357 Arrays.fill(info, base, base + 4, (byte) 0); 358 } 359 360 /** 361 * Check whether this entry has been smudged or not 362 * <p> 363 * If a blob has length 0 we know his id see {@link Constants#EMPTY_BLOB_ID}. If an entry 364 * has length 0 and an ID different from the one for empty blob we know this 365 * entry was smudged. 366 * 367 * @return <code>true</code> if the entry is smudged, <code>false</code> 368 * otherwise 369 */ 370 public final boolean isSmudged() { 371 final int base = infoOffset + P_OBJECTID; 372 return (getLength() == 0) && (Constants.EMPTY_BLOB_ID.compareTo(info, base) != 0); 373 } 374 375 final byte[] idBuffer() { 376 return info; 377 } 378 379 final int idOffset() { 380 return infoOffset + P_OBJECTID; 381 } 382 383 /** 384 * Is this entry always thought to be unmodified? 385 * <p> 386 * Most entries in the index do not have this flag set. Users may however 387 * set them on if the file system stat() costs are too high on this working 388 * directory, such as on NFS or SMB volumes. 389 * 390 * @return true if we must assume the entry is unmodified. 391 */ 392 public boolean isAssumeValid() { 393 return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0; 394 } 395 396 /** 397 * Set the assume valid flag for this entry, 398 * 399 * @param assume 400 * true to ignore apparent modifications; false to look at last 401 * modified to detect file modifications. 402 */ 403 public void setAssumeValid(final boolean assume) { 404 if (assume) 405 info[infoOffset + P_FLAGS] |= ASSUME_VALID; 406 else 407 info[infoOffset + P_FLAGS] &= ~ASSUME_VALID; 408 } 409 410 /** 411 * @return true if this entry should be checked for changes 412 */ 413 public boolean isUpdateNeeded() { 414 return (inCoreFlags & UPDATE_NEEDED) != 0; 415 } 416 417 /** 418 * Set whether this entry must be checked for changes 419 * 420 * @param updateNeeded 421 */ 422 public void setUpdateNeeded(boolean updateNeeded) { 423 if (updateNeeded) 424 inCoreFlags |= UPDATE_NEEDED; 425 else 426 inCoreFlags &= ~UPDATE_NEEDED; 427 } 428 429 /** 430 * Get the stage of this entry. 431 * <p> 432 * Entries have one of 4 possible stages: 0-3. 433 * 434 * @return the stage of this entry. 435 */ 436 public int getStage() { 437 return (info[infoOffset + P_FLAGS] >>> 4) & 0x3; 438 } 439 440 /** 441 * Returns whether this entry should be skipped from the working tree. 442 * 443 * @return true if this entry should be skipepd. 444 */ 445 public boolean isSkipWorkTree() { 446 return (getExtendedFlags() & SKIP_WORKTREE) != 0; 447 } 448 449 /** 450 * Returns whether this entry is intent to be added to the Index. 451 * 452 * @return true if this entry is intent to add. 453 */ 454 public boolean isIntentToAdd() { 455 return (getExtendedFlags() & INTENT_TO_ADD) != 0; 456 } 457 458 /** 459 * Returns whether this entry is in the fully-merged stage (0). 460 * 461 * @return true if this entry is merged 462 * @since 2.2 463 */ 464 public boolean isMerged() { 465 return getStage() == STAGE_0; 466 } 467 468 /** 469 * Obtain the raw {@link FileMode} bits for this entry. 470 * 471 * @return mode bits for the entry. 472 * @see FileMode#fromBits(int) 473 */ 474 public int getRawMode() { 475 return NB.decodeInt32(info, infoOffset + P_MODE); 476 } 477 478 /** 479 * Obtain the {@link FileMode} for this entry. 480 * 481 * @return the file mode singleton for this entry. 482 */ 483 public FileMode getFileMode() { 484 return FileMode.fromBits(getRawMode()); 485 } 486 487 /** 488 * Set the file mode for this entry. 489 * 490 * @param mode 491 * the new mode constant. 492 * @throws IllegalArgumentException 493 * If {@code mode} is {@link FileMode#MISSING}, 494 * {@link FileMode#TREE}, or any other type code not permitted 495 * in a tree object. 496 */ 497 public void setFileMode(final FileMode mode) { 498 switch (mode.getBits() & FileMode.TYPE_MASK) { 499 case FileMode.TYPE_MISSING: 500 case FileMode.TYPE_TREE: 501 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidModeForPath 502 , mode, getPathString())); 503 } 504 NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits()); 505 } 506 507 /** 508 * Get the cached creation time of this file, in milliseconds. 509 * 510 * @return cached creation time of this file, in milliseconds since the 511 * Java epoch (midnight Jan 1, 1970 UTC). 512 */ 513 public long getCreationTime() { 514 return decodeTS(P_CTIME); 515 } 516 517 /** 518 * Set the cached creation time of this file, using milliseconds. 519 * 520 * @param when 521 * new cached creation time of the file, in milliseconds. 522 */ 523 public void setCreationTime(final long when) { 524 encodeTS(P_CTIME, when); 525 } 526 527 /** 528 * Get the cached last modification date of this file, in milliseconds. 529 * <p> 530 * One of the indicators that the file has been modified by an application 531 * changing the working tree is if the last modification time for the file 532 * differs from the time stored in this entry. 533 * 534 * @return last modification time of this file, in milliseconds since the 535 * Java epoch (midnight Jan 1, 1970 UTC). 536 */ 537 public long getLastModified() { 538 return decodeTS(P_MTIME); 539 } 540 541 /** 542 * Set the cached last modification date of this file, using milliseconds. 543 * 544 * @param when 545 * new cached modification date of the file, in milliseconds. 546 */ 547 public void setLastModified(final long when) { 548 encodeTS(P_MTIME, when); 549 } 550 551 /** 552 * Get the cached size (mod 4 GB) (in bytes) of this file. 553 * <p> 554 * One of the indicators that the file has been modified by an application 555 * changing the working tree is if the size of the file (in bytes) differs 556 * from the size stored in this entry. 557 * <p> 558 * Note that this is the length of the file in the working directory, which 559 * may differ from the size of the decompressed blob if work tree filters 560 * are being used, such as LF<->CRLF conversion. 561 * <p> 562 * Note also that for very large files, this is the size of the on-disk file 563 * truncated to 32 bits, i.e. modulo 4294967296. If that value is larger 564 * than 2GB, it will appear negative. 565 * 566 * @return cached size of the working directory file, in bytes. 567 */ 568 public int getLength() { 569 return NB.decodeInt32(info, infoOffset + P_SIZE); 570 } 571 572 /** 573 * Set the cached size (in bytes) of this file. 574 * 575 * @param sz 576 * new cached size of the file, as bytes. If the file is larger 577 * than 2G, cast it to (int) before calling this method. 578 */ 579 public void setLength(final int sz) { 580 NB.encodeInt32(info, infoOffset + P_SIZE, sz); 581 } 582 583 /** 584 * Set the cached size (in bytes) of this file. 585 * 586 * @param sz 587 * new cached size of the file, as bytes. 588 */ 589 public void setLength(final long sz) { 590 setLength((int) sz); 591 } 592 593 /** 594 * Obtain the ObjectId for the entry. 595 * <p> 596 * Using this method to compare ObjectId values between entries is 597 * inefficient as it causes memory allocation. 598 * 599 * @return object identifier for the entry. 600 */ 601 public ObjectId getObjectId() { 602 return ObjectId.fromRaw(idBuffer(), idOffset()); 603 } 604 605 /** 606 * Set the ObjectId for the entry. 607 * 608 * @param id 609 * new object identifier for the entry. May be 610 * {@link ObjectId#zeroId()} to remove the current identifier. 611 */ 612 public void setObjectId(final AnyObjectId id) { 613 id.copyRawTo(idBuffer(), idOffset()); 614 } 615 616 /** 617 * Set the ObjectId for the entry from the raw binary representation. 618 * 619 * @param bs 620 * the raw byte buffer to read from. At least 20 bytes after p 621 * must be available within this byte array. 622 * @param p 623 * position to read the first byte of data from. 624 */ 625 public void setObjectIdFromRaw(final byte[] bs, final int p) { 626 final int n = Constants.OBJECT_ID_LENGTH; 627 System.arraycopy(bs, p, idBuffer(), idOffset(), n); 628 } 629 630 /** 631 * Get the entry's complete path. 632 * <p> 633 * This method is not very efficient and is primarily meant for debugging 634 * and final output generation. Applications should try to avoid calling it, 635 * and if invoked do so only once per interesting entry, where the name is 636 * absolutely required for correct function. 637 * 638 * @return complete path of the entry, from the root of the repository. If 639 * the entry is in a subtree there will be at least one '/' in the 640 * returned string. 641 */ 642 public String getPathString() { 643 return toString(path); 644 } 645 646 /** 647 * Get a copy of the entry's raw path bytes. 648 * 649 * @return raw path bytes. 650 * @since 3.4 651 */ 652 public byte[] getRawPath() { 653 return path.clone(); 654 } 655 656 /** 657 * Use for debugging only ! 658 */ 659 @SuppressWarnings("nls") 660 @Override 661 public String toString() { 662 return getFileMode() + " " + getLength() + " " + getLastModified() 663 + " " + getObjectId() + " " + getStage() + " " 664 + getPathString() + "\n"; 665 } 666 667 /** 668 * Copy the ObjectId and other meta fields from an existing entry. 669 * <p> 670 * This method copies everything except the path from one entry to another, 671 * supporting renaming. 672 * 673 * @param src 674 * the entry to copy ObjectId and meta fields from. 675 */ 676 public void copyMetaData(final DirCacheEntry src) { 677 copyMetaData(src, false); 678 } 679 680 /** 681 * Copy the ObjectId and other meta fields from an existing entry. 682 * <p> 683 * This method copies everything except the path and possibly stage from one 684 * entry to another, supporting renaming. 685 * 686 * @param src 687 * the entry to copy ObjectId and meta fields from. 688 * @param keepStage 689 * if true, the stage attribute will not be copied 690 */ 691 void copyMetaData(final DirCacheEntry src, boolean keepStage) { 692 int origflags = NB.decodeUInt16(info, infoOffset + P_FLAGS); 693 int newflags = NB.decodeUInt16(src.info, src.infoOffset + P_FLAGS); 694 System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN); 695 final int pLen = origflags & NAME_MASK; 696 final int SHIFTED_STAGE_MASK = 0x3 << 12; 697 final int pStageShifted; 698 if (keepStage) 699 pStageShifted = origflags & SHIFTED_STAGE_MASK; 700 else 701 pStageShifted = newflags & SHIFTED_STAGE_MASK; 702 NB.encodeInt16(info, infoOffset + P_FLAGS, pStageShifted | pLen 703 | (newflags & ~NAME_MASK & ~SHIFTED_STAGE_MASK)); 704 } 705 706 /** 707 * @return true if the entry contains extended flags. 708 */ 709 boolean isExtended() { 710 return (info[infoOffset + P_FLAGS] & EXTENDED) != 0; 711 } 712 713 private long decodeTS(final int pIdx) { 714 final int base = infoOffset + pIdx; 715 final int sec = NB.decodeInt32(info, base); 716 final int ms = NB.decodeInt32(info, base + 4) / 1000000; 717 return 1000L * sec + ms; 718 } 719 720 private void encodeTS(final int pIdx, final long when) { 721 final int base = infoOffset + pIdx; 722 NB.encodeInt32(info, base, (int) (when / 1000)); 723 NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); 724 } 725 726 private int getExtendedFlags() { 727 if (isExtended()) 728 return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; 729 else 730 return 0; 731 } 732 733 private static String toString(final byte[] path) { 734 return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); 735 } 736 737 static int getMaximumInfoLength(boolean extended) { 738 return extended ? INFO_LEN_EXTENDED : INFO_LEN; 739 } 740 }