DfsReader.java

  1. /*
  2.  * Copyright (C) 2008-2011, Google Inc.
  3.  * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.internal.storage.dfs;

  12. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
  13. import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;

  14. import java.io.IOException;
  15. import java.util.ArrayList;
  16. import java.util.Collection;
  17. import java.util.Collections;
  18. import java.util.Comparator;
  19. import java.util.HashSet;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;
  23. import java.util.Set;
  24. import java.util.zip.DataFormatException;
  25. import java.util.zip.Inflater;

  26. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  27. import org.eclipse.jgit.errors.MissingObjectException;
  28. import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
  29. import org.eclipse.jgit.internal.JGitText;
  30. import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList;
  31. import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
  32. import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
  33. import org.eclipse.jgit.internal.storage.file.PackIndex;
  34. import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
  35. import org.eclipse.jgit.internal.storage.pack.CachedPack;
  36. import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
  37. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  38. import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
  39. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  40. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  41. import org.eclipse.jgit.lib.AnyObjectId;
  42. import org.eclipse.jgit.lib.AsyncObjectLoaderQueue;
  43. import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
  44. import org.eclipse.jgit.lib.BitmapIndex;
  45. import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
  46. import org.eclipse.jgit.lib.InflaterCache;
  47. import org.eclipse.jgit.lib.ObjectId;
  48. import org.eclipse.jgit.lib.ObjectLoader;
  49. import org.eclipse.jgit.lib.ObjectReader;
  50. import org.eclipse.jgit.lib.ProgressMonitor;
  51. import org.eclipse.jgit.util.BlockList;

  52. /**
  53.  * Reader to access repository content through.
  54.  * <p>
  55.  * See the base {@link org.eclipse.jgit.lib.ObjectReader} documentation for
  56.  * details. Notably, a reader is not thread safe.
  57.  */
  58. public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
  59.     private static final int MAX_RESOLVE_MATCHES = 256;

  60.     /** Temporary buffer large enough for at least one raw object id. */
  61.     final byte[] tempId = new byte[OBJECT_ID_LENGTH];

  62.     /** Database this reader loads objects from. */
  63.     final DfsObjDatabase db;

  64.     final DfsReaderIoStats.Accumulator stats = new DfsReaderIoStats.Accumulator();

  65.     private Inflater inf;
  66.     private DfsBlock block;
  67.     private DeltaBaseCache baseCache;
  68.     private DfsPackFile last;
  69.     private boolean avoidUnreachable;

  70.     /**
  71.      * Initialize a new DfsReader
  72.      *
  73.      * @param db
  74.      *            parent DfsObjDatabase.
  75.      */
  76.     protected DfsReader(DfsObjDatabase db) {
  77.         this.db = db;
  78.         this.streamFileThreshold = db.getReaderOptions().getStreamFileThreshold();
  79.     }

  80.     DfsReaderOptions getOptions() {
  81.         return db.getReaderOptions();
  82.     }

  83.     DeltaBaseCache getDeltaBaseCache() {
  84.         if (baseCache == null)
  85.             baseCache = new DeltaBaseCache(this);
  86.         return baseCache;
  87.     }

  88.     /** {@inheritDoc} */
  89.     @Override
  90.     public ObjectReader newReader() {
  91.         return db.newReader();
  92.     }

  93.     /** {@inheritDoc} */
  94.     @Override
  95.     public void setAvoidUnreachableObjects(boolean avoid) {
  96.         avoidUnreachable = avoid;
  97.     }

  98.     /** {@inheritDoc} */
  99.     @Override
  100.     public BitmapIndex getBitmapIndex() throws IOException {
  101.         for (DfsPackFile pack : db.getPacks()) {
  102.             PackBitmapIndex bitmapIndex = pack.getBitmapIndex(this);
  103.             if (bitmapIndex != null)
  104.                 return new BitmapIndexImpl(bitmapIndex);
  105.         }
  106.         return null;
  107.     }

  108.     /** {@inheritDoc} */
  109.     @Override
  110.     public Collection<CachedPack> getCachedPacksAndUpdate(
  111.         BitmapBuilder needBitmap) throws IOException {
  112.         for (DfsPackFile pack : db.getPacks()) {
  113.             PackBitmapIndex bitmapIndex = pack.getBitmapIndex(this);
  114.             if (needBitmap.removeAllOrNone(bitmapIndex))
  115.                 return Collections.<CachedPack> singletonList(
  116.                         new DfsCachedPack(pack));
  117.         }
  118.         return Collections.emptyList();
  119.     }

  120.     /** {@inheritDoc} */
  121.     @Override
  122.     public Collection<ObjectId> resolve(AbbreviatedObjectId id)
  123.             throws IOException {
  124.         if (id.isComplete())
  125.             return Collections.singleton(id.toObjectId());
  126.         HashSet<ObjectId> matches = new HashSet<>(4);
  127.         PackList packList = db.getPackList();
  128.         resolveImpl(packList, id, matches);
  129.         if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) {
  130.             stats.scanPacks++;
  131.             resolveImpl(db.scanPacks(packList), id, matches);
  132.         }
  133.         return matches;
  134.     }

  135.     private void resolveImpl(PackList packList, AbbreviatedObjectId id,
  136.             HashSet<ObjectId> matches) throws IOException {
  137.         for (DfsPackFile pack : packList.packs) {
  138.             if (skipGarbagePack(pack)) {
  139.                 continue;
  140.             }
  141.             pack.resolve(this, matches, id, MAX_RESOLVE_MATCHES);
  142.             if (matches.size() >= MAX_RESOLVE_MATCHES) {
  143.                 break;
  144.             }
  145.         }
  146.     }

  147.     /** {@inheritDoc} */
  148.     @Override
  149.     public boolean has(AnyObjectId objectId) throws IOException {
  150.         if (last != null
  151.                 && !skipGarbagePack(last)
  152.                 && last.hasObject(this, objectId))
  153.             return true;
  154.         PackList packList = db.getPackList();
  155.         if (hasImpl(packList, objectId)) {
  156.             return true;
  157.         } else if (packList.dirty()) {
  158.             stats.scanPacks++;
  159.             return hasImpl(db.scanPacks(packList), objectId);
  160.         }
  161.         return false;
  162.     }

  163.     private boolean hasImpl(PackList packList, AnyObjectId objectId)
  164.             throws IOException {
  165.         for (DfsPackFile pack : packList.packs) {
  166.             if (pack == last || skipGarbagePack(pack))
  167.                 continue;
  168.             if (pack.hasObject(this, objectId)) {
  169.                 last = pack;
  170.                 return true;
  171.             }
  172.         }
  173.         return false;
  174.     }

  175.     /** {@inheritDoc} */
  176.     @Override
  177.     public ObjectLoader open(AnyObjectId objectId, int typeHint)
  178.             throws MissingObjectException, IncorrectObjectTypeException,
  179.             IOException {
  180.         ObjectLoader ldr;
  181.         if (last != null && !skipGarbagePack(last)) {
  182.             ldr = last.get(this, objectId);
  183.             if (ldr != null) {
  184.                 return checkType(ldr, objectId, typeHint);
  185.             }
  186.         }

  187.         PackList packList = db.getPackList();
  188.         ldr = openImpl(packList, objectId);
  189.         if (ldr != null) {
  190.             return checkType(ldr, objectId, typeHint);
  191.         }
  192.         if (packList.dirty()) {
  193.             stats.scanPacks++;
  194.             ldr = openImpl(db.scanPacks(packList), objectId);
  195.             if (ldr != null) {
  196.                 return checkType(ldr, objectId, typeHint);
  197.             }
  198.         }

  199.         if (typeHint == OBJ_ANY)
  200.             throw new MissingObjectException(objectId.copy(),
  201.                     JGitText.get().unknownObjectType2);
  202.         throw new MissingObjectException(objectId.copy(), typeHint);
  203.     }

  204.     private static ObjectLoader checkType(ObjectLoader ldr, AnyObjectId id,
  205.             int typeHint) throws IncorrectObjectTypeException {
  206.         if (typeHint != OBJ_ANY && ldr.getType() != typeHint) {
  207.             throw new IncorrectObjectTypeException(id.copy(), typeHint);
  208.         }
  209.         return ldr;
  210.     }

  211.     private ObjectLoader openImpl(PackList packList, AnyObjectId objectId)
  212.             throws IOException {
  213.         for (DfsPackFile pack : packList.packs) {
  214.             if (pack == last || skipGarbagePack(pack)) {
  215.                 continue;
  216.             }
  217.             ObjectLoader ldr = pack.get(this, objectId);
  218.             if (ldr != null) {
  219.                 last = pack;
  220.                 return ldr;
  221.             }
  222.         }
  223.         return null;
  224.     }

  225.     /** {@inheritDoc} */
  226.     @Override
  227.     public Set<ObjectId> getShallowCommits() {
  228.         return Collections.emptySet();
  229.     }

  230.     private static final Comparator<FoundObject<?>> FOUND_OBJECT_SORT = (
  231.             FoundObject<?> a, FoundObject<?> b) -> {
  232.         int cmp = a.packIndex - b.packIndex;
  233.         if (cmp == 0)
  234.             cmp = Long.signum(a.offset - b.offset);
  235.         return cmp;
  236.     };

  237.     private static class FoundObject<T extends ObjectId> {
  238.         final T id;
  239.         final DfsPackFile pack;
  240.         final long offset;
  241.         final int packIndex;

  242.         FoundObject(T objectId, int packIdx, DfsPackFile pack, long offset) {
  243.             this.id = objectId;
  244.             this.pack = pack;
  245.             this.offset = offset;
  246.             this.packIndex = packIdx;
  247.         }

  248.         FoundObject(T objectId) {
  249.             this.id = objectId;
  250.             this.pack = null;
  251.             this.offset = 0;
  252.             this.packIndex = 0;
  253.         }
  254.     }

  255.     private <T extends ObjectId> Iterable<FoundObject<T>> findAll(
  256.             Iterable<T> objectIds) throws IOException {
  257.         Collection<T> pending = new LinkedList<>();
  258.         for (T id : objectIds) {
  259.             pending.add(id);
  260.         }

  261.         PackList packList = db.getPackList();
  262.         List<FoundObject<T>> r = new ArrayList<>();
  263.         findAllImpl(packList, pending, r);
  264.         if (!pending.isEmpty() && packList.dirty()) {
  265.             stats.scanPacks++;
  266.             findAllImpl(db.scanPacks(packList), pending, r);
  267.         }
  268.         for (T t : pending) {
  269.             r.add(new FoundObject<>(t));
  270.         }
  271.         Collections.sort(r, FOUND_OBJECT_SORT);
  272.         return r;
  273.     }

  274.     private <T extends ObjectId> void findAllImpl(PackList packList,
  275.             Collection<T> pending, List<FoundObject<T>> r) {
  276.         DfsPackFile[] packs = packList.packs;
  277.         if (packs.length == 0) {
  278.             return;
  279.         }
  280.         int lastIdx = 0;
  281.         DfsPackFile lastPack = packs[lastIdx];

  282.         OBJECT_SCAN: for (Iterator<T> it = pending.iterator(); it.hasNext();) {
  283.             T t = it.next();
  284.             if (!skipGarbagePack(lastPack)) {
  285.                 try {
  286.                     long p = lastPack.findOffset(this, t);
  287.                     if (0 < p) {
  288.                         r.add(new FoundObject<>(t, lastIdx, lastPack, p));
  289.                         it.remove();
  290.                         continue;
  291.                     }
  292.                 } catch (IOException e) {
  293.                     // Fall though and try to examine other packs.
  294.                 }
  295.             }

  296.             for (int i = 0; i < packs.length; i++) {
  297.                 if (i == lastIdx)
  298.                     continue;
  299.                 DfsPackFile pack = packs[i];
  300.                 if (skipGarbagePack(pack))
  301.                     continue;
  302.                 try {
  303.                     long p = pack.findOffset(this, t);
  304.                     if (0 < p) {
  305.                         r.add(new FoundObject<>(t, i, pack, p));
  306.                         it.remove();
  307.                         lastIdx = i;
  308.                         lastPack = pack;
  309.                         continue OBJECT_SCAN;
  310.                     }
  311.                 } catch (IOException e) {
  312.                     // Examine other packs.
  313.                 }
  314.             }
  315.         }

  316.         last = lastPack;
  317.     }

  318.     private boolean skipGarbagePack(DfsPackFile pack) {
  319.         return avoidUnreachable && pack.isGarbage();
  320.     }

  321.     /** {@inheritDoc} */
  322.     @Override
  323.     public <T extends ObjectId> AsyncObjectLoaderQueue<T> open(
  324.             Iterable<T> objectIds, final boolean reportMissing) {
  325.         Iterable<FoundObject<T>> order;
  326.         IOException error = null;
  327.         try {
  328.             order = findAll(objectIds);
  329.         } catch (IOException e) {
  330.             order = Collections.emptyList();
  331.             error = e;
  332.         }

  333.         final Iterator<FoundObject<T>> idItr = order.iterator();
  334.         final IOException findAllError = error;
  335.         return new AsyncObjectLoaderQueue<T>() {
  336.             private FoundObject<T> cur;

  337.             @Override
  338.             public boolean next() throws MissingObjectException, IOException {
  339.                 if (idItr.hasNext()) {
  340.                     cur = idItr.next();
  341.                     return true;
  342.                 } else if (findAllError != null) {
  343.                     throw findAllError;
  344.                 } else {
  345.                     return false;
  346.                 }
  347.             }

  348.             @Override
  349.             public T getCurrent() {
  350.                 return cur.id;
  351.             }

  352.             @Override
  353.             public ObjectId getObjectId() {
  354.                 return cur.id;
  355.             }

  356.             @Override
  357.             public ObjectLoader open() throws IOException {
  358.                 if (cur.pack == null)
  359.                     throw new MissingObjectException(cur.id,
  360.                             JGitText.get().unknownObjectType2);
  361.                 return cur.pack.load(DfsReader.this, cur.offset);
  362.             }

  363.             @Override
  364.             public boolean cancel(boolean mayInterruptIfRunning) {
  365.                 return true;
  366.             }

  367.             @Override
  368.             public void release() {
  369.                 // Nothing to clean up.
  370.             }
  371.         };
  372.     }

  373.     /** {@inheritDoc} */
  374.     @Override
  375.     public <T extends ObjectId> AsyncObjectSizeQueue<T> getObjectSize(
  376.             Iterable<T> objectIds, final boolean reportMissing) {
  377.         Iterable<FoundObject<T>> order;
  378.         IOException error = null;
  379.         try {
  380.             order = findAll(objectIds);
  381.         } catch (IOException e) {
  382.             order = Collections.emptyList();
  383.             error = e;
  384.         }

  385.         final Iterator<FoundObject<T>> idItr = order.iterator();
  386.         final IOException findAllError = error;
  387.         return new AsyncObjectSizeQueue<T>() {
  388.             private FoundObject<T> cur;
  389.             private long sz;

  390.             @Override
  391.             public boolean next() throws MissingObjectException, IOException {
  392.                 if (idItr.hasNext()) {
  393.                     cur = idItr.next();
  394.                     if (cur.pack == null)
  395.                         throw new MissingObjectException(cur.id,
  396.                                 JGitText.get().unknownObjectType2);
  397.                     sz = cur.pack.getObjectSize(DfsReader.this, cur.offset);
  398.                     return true;
  399.                 } else if (findAllError != null) {
  400.                     throw findAllError;
  401.                 } else {
  402.                     return false;
  403.                 }
  404.             }

  405.             @Override
  406.             public T getCurrent() {
  407.                 return cur.id;
  408.             }

  409.             @Override
  410.             public ObjectId getObjectId() {
  411.                 return cur.id;
  412.             }

  413.             @Override
  414.             public long getSize() {
  415.                 return sz;
  416.             }

  417.             @Override
  418.             public boolean cancel(boolean mayInterruptIfRunning) {
  419.                 return true;
  420.             }

  421.             @Override
  422.             public void release() {
  423.                 // Nothing to clean up.
  424.             }
  425.         };
  426.     }

  427.     /** {@inheritDoc} */
  428.     @Override
  429.     public long getObjectSize(AnyObjectId objectId, int typeHint)
  430.             throws MissingObjectException, IncorrectObjectTypeException,
  431.             IOException {
  432.         if (last != null && !skipGarbagePack(last)) {
  433.             long sz = last.getObjectSize(this, objectId);
  434.             if (0 <= sz) {
  435.                 return sz;
  436.             }
  437.         }

  438.         PackList packList = db.getPackList();
  439.         long sz = getObjectSizeImpl(packList, objectId);
  440.         if (0 <= sz) {
  441.             return sz;
  442.         }
  443.         if (packList.dirty()) {
  444.             sz = getObjectSizeImpl(packList, objectId);
  445.             if (0 <= sz) {
  446.                 return sz;
  447.             }
  448.         }

  449.         if (typeHint == OBJ_ANY) {
  450.             throw new MissingObjectException(objectId.copy(),
  451.                     JGitText.get().unknownObjectType2);
  452.         }
  453.         throw new MissingObjectException(objectId.copy(), typeHint);
  454.     }

  455.     private long getObjectSizeImpl(PackList packList, AnyObjectId objectId)
  456.             throws IOException {
  457.         for (DfsPackFile pack : packList.packs) {
  458.             if (pack == last || skipGarbagePack(pack)) {
  459.                 continue;
  460.             }
  461.             long sz = pack.getObjectSize(this, objectId);
  462.             if (0 <= sz) {
  463.                 last = pack;
  464.                 return sz;
  465.             }
  466.         }
  467.         return -1;
  468.     }

  469.     /** {@inheritDoc} */
  470.     @Override
  471.     public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
  472.         return new DfsObjectToPack(objectId, type);
  473.     }

  474.     private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
  475.             DfsObjectToPack a,
  476.             DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());

  477.     @Override
  478.     public void selectObjectRepresentation(PackWriter packer,
  479.             ProgressMonitor monitor, Iterable<ObjectToPack> objects)
  480.             throws IOException, MissingObjectException {
  481.         // Don't check dirty bit on PackList; assume ObjectToPacks all came
  482.         // from the current list.
  483.         List<DfsPackFile> packs = sortPacksForSelectRepresentation();
  484.         trySelectRepresentation(packer, monitor, objects, packs, false);

  485.         List<DfsPackFile> garbage = garbagePacksForSelectRepresentation();
  486.         if (!garbage.isEmpty() && checkGarbagePacks(objects)) {
  487.             trySelectRepresentation(packer, monitor, objects, garbage, true);
  488.         }
  489.     }

  490.     private void trySelectRepresentation(PackWriter packer,
  491.             ProgressMonitor monitor, Iterable<ObjectToPack> objects,
  492.             List<DfsPackFile> packs, boolean skipFound) throws IOException {
  493.         for (DfsPackFile pack : packs) {
  494.             List<DfsObjectToPack> tmp = findAllFromPack(pack, objects, skipFound);
  495.             if (tmp.isEmpty())
  496.                 continue;
  497.             Collections.sort(tmp, OFFSET_SORT);
  498.             PackReverseIndex rev = pack.getReverseIdx(this);
  499.             DfsObjectRepresentation rep = new DfsObjectRepresentation(pack);
  500.             for (DfsObjectToPack otp : tmp) {
  501.                 pack.representation(rep, otp.getOffset(), this, rev);
  502.                 otp.setOffset(0);
  503.                 packer.select(otp, rep);
  504.                 if (!otp.isFound()) {
  505.                     otp.setFound();
  506.                     monitor.update(1);
  507.                 }
  508.             }
  509.         }
  510.     }

  511.     private static final Comparator<DfsPackFile> PACK_SORT_FOR_REUSE =
  512.         Comparator.comparing(
  513.                 DfsPackFile::getPackDescription, DfsPackDescription.reuseComparator());

  514.     private List<DfsPackFile> sortPacksForSelectRepresentation()
  515.             throws IOException {
  516.         DfsPackFile[] packs = db.getPacks();
  517.         List<DfsPackFile> sorted = new ArrayList<>(packs.length);
  518.         for (DfsPackFile p : packs) {
  519.             if (p.getPackDescription().getPackSource() != UNREACHABLE_GARBAGE) {
  520.                 sorted.add(p);
  521.             }
  522.         }
  523.         Collections.sort(sorted, PACK_SORT_FOR_REUSE);
  524.         return sorted;
  525.     }

  526.     private List<DfsPackFile> garbagePacksForSelectRepresentation()
  527.             throws IOException {
  528.         DfsPackFile[] packs = db.getPacks();
  529.         List<DfsPackFile> garbage = new ArrayList<>(packs.length);
  530.         for (DfsPackFile p : packs) {
  531.             if (p.getPackDescription().getPackSource() == UNREACHABLE_GARBAGE) {
  532.                 garbage.add(p);
  533.             }
  534.         }
  535.         return garbage;
  536.     }

  537.     private static boolean checkGarbagePacks(Iterable<ObjectToPack> objects) {
  538.         for (ObjectToPack otp : objects) {
  539.             if (!((DfsObjectToPack) otp).isFound()) {
  540.                 return true;
  541.             }
  542.         }
  543.         return false;
  544.     }

  545.     private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
  546.             Iterable<ObjectToPack> objects, boolean skipFound)
  547.                     throws IOException {
  548.         List<DfsObjectToPack> tmp = new BlockList<>();
  549.         PackIndex idx = pack.getPackIndex(this);
  550.         for (ObjectToPack obj : objects) {
  551.             DfsObjectToPack otp = (DfsObjectToPack) obj;
  552.             if (skipFound && otp.isFound()) {
  553.                 continue;
  554.             }
  555.             long p = idx.findOffset(otp);
  556.             if (0 < p && !pack.isCorrupt(p)) {
  557.                 otp.setOffset(p);
  558.                 tmp.add(otp);
  559.             }
  560.         }
  561.         return tmp;
  562.     }

  563.     /** {@inheritDoc} */
  564.     @Override
  565.     public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
  566.             boolean validate) throws IOException,
  567.             StoredObjectRepresentationNotAvailableException {
  568.         DfsObjectToPack src = (DfsObjectToPack) otp;
  569.         src.pack.copyAsIs(out, src, validate, this);
  570.     }

  571.     /** {@inheritDoc} */
  572.     @Override
  573.     public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
  574.             throws IOException {
  575.         for (ObjectToPack otp : list)
  576.             out.writeObject(otp);
  577.     }

  578.     /** {@inheritDoc} */
  579.     @Override
  580.     public void copyPackAsIs(PackOutputStream out, CachedPack pack)
  581.             throws IOException {
  582.         ((DfsCachedPack) pack).copyAsIs(out, this);
  583.     }

  584.     /**
  585.      * Copy bytes from the window to a caller supplied buffer.
  586.      *
  587.      * @param file
  588.      *            the file the desired window is stored within.
  589.      * @param position
  590.      *            position within the file to read from.
  591.      * @param dstbuf
  592.      *            destination buffer to copy into.
  593.      * @param dstoff
  594.      *            offset within <code>dstbuf</code> to start copying into.
  595.      * @param cnt
  596.      *            number of bytes to copy. This value may exceed the number of
  597.      *            bytes remaining in the window starting at offset
  598.      *            <code>pos</code>.
  599.      * @return number of bytes actually copied; this may be less than
  600.      *         <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
  601.      *         available.
  602.      * @throws IOException
  603.      *             this cursor does not match the provider or id and the proper
  604.      *             window could not be acquired through the provider's cache.
  605.      */
  606.     int copy(BlockBasedFile file, long position, byte[] dstbuf, int dstoff,
  607.             int cnt) throws IOException {
  608.         if (cnt == 0)
  609.             return 0;

  610.         long length = file.length;
  611.         if (0 <= length && length <= position)
  612.             return 0;

  613.         int need = cnt;
  614.         do {
  615.             pin(file, position);
  616.             int r = block.copy(position, dstbuf, dstoff, need);
  617.             position += r;
  618.             dstoff += r;
  619.             need -= r;
  620.             if (length < 0)
  621.                 length = file.length;
  622.         } while (0 < need && position < length);
  623.         return cnt - need;
  624.     }

  625.     /**
  626.      * Inflate a region of the pack starting at {@code position}.
  627.      *
  628.      * @param pack
  629.      *            the file the desired window is stored within.
  630.      * @param position
  631.      *            position within the file to read from.
  632.      * @param dstbuf
  633.      *            destination buffer the inflater should output decompressed
  634.      *            data to. Must be large enough to store the entire stream,
  635.      *            unless headerOnly is true.
  636.      * @param headerOnly
  637.      *            if true the caller wants only {@code dstbuf.length} bytes.
  638.      * @return number of bytes inflated into <code>dstbuf</code>.
  639.      * @throws IOException
  640.      *             this cursor does not match the provider or id and the proper
  641.      *             window could not be acquired through the provider's cache.
  642.      * @throws DataFormatException
  643.      *             the inflater encountered an invalid chunk of data. Data
  644.      *             stream corruption is likely.
  645.      */
  646.     int inflate(DfsPackFile pack, long position, byte[] dstbuf,
  647.             boolean headerOnly) throws IOException, DataFormatException {
  648.         long start = System.nanoTime();
  649.         prepareInflater();
  650.         pin(pack, position);
  651.         position += block.setInput(position, inf);
  652.         for (int dstoff = 0;;) {
  653.             int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
  654.             dstoff += n;
  655.             if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) {
  656.                 stats.inflatedBytes += dstoff;
  657.                 stats.inflationMicros += BlockBasedFile.elapsedMicros(start);
  658.                 return dstoff;
  659.             } else if (inf.needsInput()) {
  660.                 pin(pack, position);
  661.                 position += block.setInput(position, inf);
  662.             } else if (n == 0)
  663.                 throw new DataFormatException();
  664.         }
  665.     }

  666.     DfsBlock quickCopy(DfsPackFile p, long pos, long cnt)
  667.             throws IOException {
  668.         pin(p, pos);
  669.         if (block.contains(p.key, pos + (cnt - 1)))
  670.             return block;
  671.         return null;
  672.     }

  673.     Inflater inflater() {
  674.         prepareInflater();
  675.         return inf;
  676.     }

  677.     private void prepareInflater() {
  678.         if (inf == null)
  679.             inf = InflaterCache.get();
  680.         else
  681.             inf.reset();
  682.     }

  683.     void pin(BlockBasedFile file, long position) throws IOException {
  684.         if (block == null || !block.contains(file.key, position)) {
  685.             // If memory is low, we may need what is in our window field to
  686.             // be cleaned up by the GC during the get for the next window.
  687.             // So we always clear it, even though we are just going to set
  688.             // it again.
  689.             block = null;
  690.             block = file.getOrLoadBlock(position, this);
  691.         }
  692.     }

  693.     void unpin() {
  694.         block = null;
  695.     }

  696.     /**
  697.      * Get IO statistics accumulated by this reader.
  698.      *
  699.      * @return IO statistics accumulated by this reader.
  700.      */
  701.     public DfsReaderIoStats getIoStats() {
  702.         return new DfsReaderIoStats(stats);
  703.     }

  704.     /**
  705.      * {@inheritDoc}
  706.      * <p>
  707.      * Release the current window cursor.
  708.      */
  709.     @Override
  710.     public void close() {
  711.         last = null;
  712.         block = null;
  713.         baseCache = null;
  714.         try {
  715.             InflaterCache.release(inf);
  716.         } finally {
  717.             inf = null;
  718.         }
  719.     }
  720. }