DfsReader.java

  1. /*
  2.  * Copyright (C) 2008-2011, Google Inc.
  3.  * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
  4.  * and other copyright owners as documented in the project's IP log.
  5.  *
  6.  * This program and the accompanying materials are made available
  7.  * under the terms of the Eclipse Distribution License v1.0 which
  8.  * accompanies this distribution, is reproduced below, and is
  9.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  10.  *
  11.  * All rights reserved.
  12.  *
  13.  * Redistribution and use in source and binary forms, with or
  14.  * without modification, are permitted provided that the following
  15.  * conditions are met:
  16.  *
  17.  * - Redistributions of source code must retain the above copyright
  18.  *   notice, this list of conditions and the following disclaimer.
  19.  *
  20.  * - Redistributions in binary form must reproduce the above
  21.  *   copyright notice, this list of conditions and the following
  22.  *   disclaimer in the documentation and/or other materials provided
  23.  *   with the distribution.
  24.  *
  25.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  26.  *   names of its contributors may be used to endorse or promote
  27.  *   products derived from this software without specific prior
  28.  *   written permission.
  29.  *
  30.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43.  */

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

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

  47. import java.io.IOException;
  48. import java.util.ArrayList;
  49. import java.util.Collection;
  50. import java.util.Collections;
  51. import java.util.Comparator;
  52. import java.util.HashSet;
  53. import java.util.Iterator;
  54. import java.util.LinkedList;
  55. import java.util.List;
  56. import java.util.Set;
  57. import java.util.zip.DataFormatException;
  58. import java.util.zip.Inflater;

  59. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  60. import org.eclipse.jgit.errors.MissingObjectException;
  61. import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
  62. import org.eclipse.jgit.internal.JGitText;
  63. import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList;
  64. import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
  65. import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
  66. import org.eclipse.jgit.internal.storage.file.PackIndex;
  67. import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
  68. import org.eclipse.jgit.internal.storage.pack.CachedPack;
  69. import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
  70. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  71. import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
  72. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  73. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  74. import org.eclipse.jgit.lib.AnyObjectId;
  75. import org.eclipse.jgit.lib.AsyncObjectLoaderQueue;
  76. import org.eclipse.jgit.lib.AsyncObjectSizeQueue;
  77. import org.eclipse.jgit.lib.BitmapIndex;
  78. import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
  79. import org.eclipse.jgit.lib.InflaterCache;
  80. import org.eclipse.jgit.lib.ObjectId;
  81. import org.eclipse.jgit.lib.ObjectLoader;
  82. import org.eclipse.jgit.lib.ObjectReader;
  83. import org.eclipse.jgit.lib.ProgressMonitor;
  84. import org.eclipse.jgit.util.BlockList;

  85. /**
  86.  * Reader to access repository content through.
  87.  * <p>
  88.  * See the base {@link org.eclipse.jgit.lib.ObjectReader} documentation for
  89.  * details. Notably, a reader is not thread safe.
  90.  */
  91. public class DfsReader extends ObjectReader implements ObjectReuseAsIs {
  92.     private static final int MAX_RESOLVE_MATCHES = 256;

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

  95.     /** Database this reader loads objects from. */
  96.     final DfsObjDatabase db;

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

  98.     private Inflater inf;
  99.     private DfsBlock block;
  100.     private DeltaBaseCache baseCache;
  101.     private DfsPackFile last;
  102.     private boolean avoidUnreachable;

  103.     /**
  104.      * Initialize a new DfsReader
  105.      *
  106.      * @param db
  107.      *            parent DfsObjDatabase.
  108.      */
  109.     protected DfsReader(DfsObjDatabase db) {
  110.         this.db = db;
  111.         this.streamFileThreshold = db.getReaderOptions().getStreamFileThreshold();
  112.     }

  113.     DfsReaderOptions getOptions() {
  114.         return db.getReaderOptions();
  115.     }

  116.     DeltaBaseCache getDeltaBaseCache() {
  117.         if (baseCache == null)
  118.             baseCache = new DeltaBaseCache(this);
  119.         return baseCache;
  120.     }

  121.     /** {@inheritDoc} */
  122.     @Override
  123.     public ObjectReader newReader() {
  124.         return db.newReader();
  125.     }

  126.     /** {@inheritDoc} */
  127.     @Override
  128.     public void setAvoidUnreachableObjects(boolean avoid) {
  129.         avoidUnreachable = avoid;
  130.     }

  131.     /** {@inheritDoc} */
  132.     @Override
  133.     public BitmapIndex getBitmapIndex() throws IOException {
  134.         for (DfsPackFile pack : db.getPacks()) {
  135.             PackBitmapIndex bitmapIndex = pack.getBitmapIndex(this);
  136.             if (bitmapIndex != null)
  137.                 return new BitmapIndexImpl(bitmapIndex);
  138.         }
  139.         return null;
  140.     }

  141.     /** {@inheritDoc} */
  142.     @Override
  143.     public Collection<CachedPack> getCachedPacksAndUpdate(
  144.         BitmapBuilder needBitmap) throws IOException {
  145.         for (DfsPackFile pack : db.getPacks()) {
  146.             PackBitmapIndex bitmapIndex = pack.getBitmapIndex(this);
  147.             if (needBitmap.removeAllOrNone(bitmapIndex))
  148.                 return Collections.<CachedPack> singletonList(
  149.                         new DfsCachedPack(pack));
  150.         }
  151.         return Collections.emptyList();
  152.     }

  153.     /** {@inheritDoc} */
  154.     @Override
  155.     public Collection<ObjectId> resolve(AbbreviatedObjectId id)
  156.             throws IOException {
  157.         if (id.isComplete())
  158.             return Collections.singleton(id.toObjectId());
  159.         HashSet<ObjectId> matches = new HashSet<>(4);
  160.         PackList packList = db.getPackList();
  161.         resolveImpl(packList, id, matches);
  162.         if (matches.size() < MAX_RESOLVE_MATCHES && packList.dirty()) {
  163.             stats.scanPacks++;
  164.             resolveImpl(db.scanPacks(packList), id, matches);
  165.         }
  166.         return matches;
  167.     }

  168.     private void resolveImpl(PackList packList, AbbreviatedObjectId id,
  169.             HashSet<ObjectId> matches) throws IOException {
  170.         for (DfsPackFile pack : packList.packs) {
  171.             if (skipGarbagePack(pack)) {
  172.                 continue;
  173.             }
  174.             pack.resolve(this, matches, id, MAX_RESOLVE_MATCHES);
  175.             if (matches.size() >= MAX_RESOLVE_MATCHES) {
  176.                 break;
  177.             }
  178.         }
  179.     }

  180.     /** {@inheritDoc} */
  181.     @Override
  182.     public boolean has(AnyObjectId objectId) throws IOException {
  183.         if (last != null
  184.                 && !skipGarbagePack(last)
  185.                 && last.hasObject(this, objectId))
  186.             return true;
  187.         PackList packList = db.getPackList();
  188.         if (hasImpl(packList, objectId)) {
  189.             return true;
  190.         } else if (packList.dirty()) {
  191.             stats.scanPacks++;
  192.             return hasImpl(db.scanPacks(packList), objectId);
  193.         }
  194.         return false;
  195.     }

  196.     private boolean hasImpl(PackList packList, AnyObjectId objectId)
  197.             throws IOException {
  198.         for (DfsPackFile pack : packList.packs) {
  199.             if (pack == last || skipGarbagePack(pack))
  200.                 continue;
  201.             if (pack.hasObject(this, objectId)) {
  202.                 last = pack;
  203.                 return true;
  204.             }
  205.         }
  206.         return false;
  207.     }

  208.     /** {@inheritDoc} */
  209.     @Override
  210.     public ObjectLoader open(AnyObjectId objectId, int typeHint)
  211.             throws MissingObjectException, IncorrectObjectTypeException,
  212.             IOException {
  213.         ObjectLoader ldr;
  214.         if (last != null && !skipGarbagePack(last)) {
  215.             ldr = last.get(this, objectId);
  216.             if (ldr != null) {
  217.                 return checkType(ldr, objectId, typeHint);
  218.             }
  219.         }

  220.         PackList packList = db.getPackList();
  221.         ldr = openImpl(packList, objectId);
  222.         if (ldr != null) {
  223.             return checkType(ldr, objectId, typeHint);
  224.         }
  225.         if (packList.dirty()) {
  226.             stats.scanPacks++;
  227.             ldr = openImpl(db.scanPacks(packList), objectId);
  228.             if (ldr != null) {
  229.                 return checkType(ldr, objectId, typeHint);
  230.             }
  231.         }

  232.         if (typeHint == OBJ_ANY)
  233.             throw new MissingObjectException(objectId.copy(),
  234.                     JGitText.get().unknownObjectType2);
  235.         throw new MissingObjectException(objectId.copy(), typeHint);
  236.     }

  237.     private static ObjectLoader checkType(ObjectLoader ldr, AnyObjectId id,
  238.             int typeHint) throws IncorrectObjectTypeException {
  239.         if (typeHint != OBJ_ANY && ldr.getType() != typeHint) {
  240.             throw new IncorrectObjectTypeException(id.copy(), typeHint);
  241.         }
  242.         return ldr;
  243.     }

  244.     private ObjectLoader openImpl(PackList packList, AnyObjectId objectId)
  245.             throws IOException {
  246.         for (DfsPackFile pack : packList.packs) {
  247.             if (pack == last || skipGarbagePack(pack)) {
  248.                 continue;
  249.             }
  250.             ObjectLoader ldr = pack.get(this, objectId);
  251.             if (ldr != null) {
  252.                 last = pack;
  253.                 return ldr;
  254.             }
  255.         }
  256.         return null;
  257.     }

  258.     /** {@inheritDoc} */
  259.     @Override
  260.     public Set<ObjectId> getShallowCommits() {
  261.         return Collections.emptySet();
  262.     }

  263.     private static final Comparator<FoundObject<?>> FOUND_OBJECT_SORT = (
  264.             FoundObject<?> a, FoundObject<?> b) -> {
  265.         int cmp = a.packIndex - b.packIndex;
  266.         if (cmp == 0)
  267.             cmp = Long.signum(a.offset - b.offset);
  268.         return cmp;
  269.     };

  270.     private static class FoundObject<T extends ObjectId> {
  271.         final T id;
  272.         final DfsPackFile pack;
  273.         final long offset;
  274.         final int packIndex;

  275.         FoundObject(T objectId, int packIdx, DfsPackFile pack, long offset) {
  276.             this.id = objectId;
  277.             this.pack = pack;
  278.             this.offset = offset;
  279.             this.packIndex = packIdx;
  280.         }

  281.         FoundObject(T objectId) {
  282.             this.id = objectId;
  283.             this.pack = null;
  284.             this.offset = 0;
  285.             this.packIndex = 0;
  286.         }
  287.     }

  288.     private <T extends ObjectId> Iterable<FoundObject<T>> findAll(
  289.             Iterable<T> objectIds) throws IOException {
  290.         Collection<T> pending = new LinkedList<>();
  291.         for (T id : objectIds) {
  292.             pending.add(id);
  293.         }

  294.         PackList packList = db.getPackList();
  295.         List<FoundObject<T>> r = new ArrayList<>();
  296.         findAllImpl(packList, pending, r);
  297.         if (!pending.isEmpty() && packList.dirty()) {
  298.             stats.scanPacks++;
  299.             findAllImpl(db.scanPacks(packList), pending, r);
  300.         }
  301.         for (T t : pending) {
  302.             r.add(new FoundObject<>(t));
  303.         }
  304.         Collections.sort(r, FOUND_OBJECT_SORT);
  305.         return r;
  306.     }

  307.     private <T extends ObjectId> void findAllImpl(PackList packList,
  308.             Collection<T> pending, List<FoundObject<T>> r) {
  309.         DfsPackFile[] packs = packList.packs;
  310.         if (packs.length == 0) {
  311.             return;
  312.         }
  313.         int lastIdx = 0;
  314.         DfsPackFile lastPack = packs[lastIdx];

  315.         OBJECT_SCAN: for (Iterator<T> it = pending.iterator(); it.hasNext();) {
  316.             T t = it.next();
  317.             if (!skipGarbagePack(lastPack)) {
  318.                 try {
  319.                     long p = lastPack.findOffset(this, t);
  320.                     if (0 < p) {
  321.                         r.add(new FoundObject<>(t, lastIdx, lastPack, p));
  322.                         it.remove();
  323.                         continue;
  324.                     }
  325.                 } catch (IOException e) {
  326.                     // Fall though and try to examine other packs.
  327.                 }
  328.             }

  329.             for (int i = 0; i < packs.length; i++) {
  330.                 if (i == lastIdx)
  331.                     continue;
  332.                 DfsPackFile pack = packs[i];
  333.                 if (skipGarbagePack(pack))
  334.                     continue;
  335.                 try {
  336.                     long p = pack.findOffset(this, t);
  337.                     if (0 < p) {
  338.                         r.add(new FoundObject<>(t, i, pack, p));
  339.                         it.remove();
  340.                         lastIdx = i;
  341.                         lastPack = pack;
  342.                         continue OBJECT_SCAN;
  343.                     }
  344.                 } catch (IOException e) {
  345.                     // Examine other packs.
  346.                 }
  347.             }
  348.         }

  349.         last = lastPack;
  350.     }

  351.     private boolean skipGarbagePack(DfsPackFile pack) {
  352.         return avoidUnreachable && pack.isGarbage();
  353.     }

  354.     /** {@inheritDoc} */
  355.     @Override
  356.     public <T extends ObjectId> AsyncObjectLoaderQueue<T> open(
  357.             Iterable<T> objectIds, final boolean reportMissing) {
  358.         Iterable<FoundObject<T>> order;
  359.         IOException error = null;
  360.         try {
  361.             order = findAll(objectIds);
  362.         } catch (IOException e) {
  363.             order = Collections.emptyList();
  364.             error = e;
  365.         }

  366.         final Iterator<FoundObject<T>> idItr = order.iterator();
  367.         final IOException findAllError = error;
  368.         return new AsyncObjectLoaderQueue<T>() {
  369.             private FoundObject<T> cur;

  370.             @Override
  371.             public boolean next() throws MissingObjectException, IOException {
  372.                 if (idItr.hasNext()) {
  373.                     cur = idItr.next();
  374.                     return true;
  375.                 } else if (findAllError != null) {
  376.                     throw findAllError;
  377.                 } else {
  378.                     return false;
  379.                 }
  380.             }

  381.             @Override
  382.             public T getCurrent() {
  383.                 return cur.id;
  384.             }

  385.             @Override
  386.             public ObjectId getObjectId() {
  387.                 return cur.id;
  388.             }

  389.             @Override
  390.             public ObjectLoader open() throws IOException {
  391.                 if (cur.pack == null)
  392.                     throw new MissingObjectException(cur.id,
  393.                             JGitText.get().unknownObjectType2);
  394.                 return cur.pack.load(DfsReader.this, cur.offset);
  395.             }

  396.             @Override
  397.             public boolean cancel(boolean mayInterruptIfRunning) {
  398.                 return true;
  399.             }

  400.             @Override
  401.             public void release() {
  402.                 // Nothing to clean up.
  403.             }
  404.         };
  405.     }

  406.     /** {@inheritDoc} */
  407.     @Override
  408.     public <T extends ObjectId> AsyncObjectSizeQueue<T> getObjectSize(
  409.             Iterable<T> objectIds, final boolean reportMissing) {
  410.         Iterable<FoundObject<T>> order;
  411.         IOException error = null;
  412.         try {
  413.             order = findAll(objectIds);
  414.         } catch (IOException e) {
  415.             order = Collections.emptyList();
  416.             error = e;
  417.         }

  418.         final Iterator<FoundObject<T>> idItr = order.iterator();
  419.         final IOException findAllError = error;
  420.         return new AsyncObjectSizeQueue<T>() {
  421.             private FoundObject<T> cur;
  422.             private long sz;

  423.             @Override
  424.             public boolean next() throws MissingObjectException, IOException {
  425.                 if (idItr.hasNext()) {
  426.                     cur = idItr.next();
  427.                     if (cur.pack == null)
  428.                         throw new MissingObjectException(cur.id,
  429.                                 JGitText.get().unknownObjectType2);
  430.                     sz = cur.pack.getObjectSize(DfsReader.this, cur.offset);
  431.                     return true;
  432.                 } else if (findAllError != null) {
  433.                     throw findAllError;
  434.                 } else {
  435.                     return false;
  436.                 }
  437.             }

  438.             @Override
  439.             public T getCurrent() {
  440.                 return cur.id;
  441.             }

  442.             @Override
  443.             public ObjectId getObjectId() {
  444.                 return cur.id;
  445.             }

  446.             @Override
  447.             public long getSize() {
  448.                 return sz;
  449.             }

  450.             @Override
  451.             public boolean cancel(boolean mayInterruptIfRunning) {
  452.                 return true;
  453.             }

  454.             @Override
  455.             public void release() {
  456.                 // Nothing to clean up.
  457.             }
  458.         };
  459.     }

  460.     /** {@inheritDoc} */
  461.     @Override
  462.     public long getObjectSize(AnyObjectId objectId, int typeHint)
  463.             throws MissingObjectException, IncorrectObjectTypeException,
  464.             IOException {
  465.         if (last != null && !skipGarbagePack(last)) {
  466.             long sz = last.getObjectSize(this, objectId);
  467.             if (0 <= sz) {
  468.                 return sz;
  469.             }
  470.         }

  471.         PackList packList = db.getPackList();
  472.         long sz = getObjectSizeImpl(packList, objectId);
  473.         if (0 <= sz) {
  474.             return sz;
  475.         }
  476.         if (packList.dirty()) {
  477.             sz = getObjectSizeImpl(packList, objectId);
  478.             if (0 <= sz) {
  479.                 return sz;
  480.             }
  481.         }

  482.         if (typeHint == OBJ_ANY) {
  483.             throw new MissingObjectException(objectId.copy(),
  484.                     JGitText.get().unknownObjectType2);
  485.         }
  486.         throw new MissingObjectException(objectId.copy(), typeHint);
  487.     }

  488.     private long getObjectSizeImpl(PackList packList, AnyObjectId objectId)
  489.             throws IOException {
  490.         for (DfsPackFile pack : packList.packs) {
  491.             if (pack == last || skipGarbagePack(pack)) {
  492.                 continue;
  493.             }
  494.             long sz = pack.getObjectSize(this, objectId);
  495.             if (0 <= sz) {
  496.                 last = pack;
  497.                 return sz;
  498.             }
  499.         }
  500.         return -1;
  501.     }

  502.     /** {@inheritDoc} */
  503.     @Override
  504.     public DfsObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
  505.         return new DfsObjectToPack(objectId, type);
  506.     }

  507.     private static final Comparator<DfsObjectToPack> OFFSET_SORT = (
  508.             DfsObjectToPack a,
  509.             DfsObjectToPack b) -> Long.signum(a.getOffset() - b.getOffset());

  510.     @Override
  511.     public void selectObjectRepresentation(PackWriter packer,
  512.             ProgressMonitor monitor, Iterable<ObjectToPack> objects)
  513.             throws IOException, MissingObjectException {
  514.         // Don't check dirty bit on PackList; assume ObjectToPacks all came
  515.         // from the current list.
  516.         List<DfsPackFile> packs = sortPacksForSelectRepresentation();
  517.         trySelectRepresentation(packer, monitor, objects, packs, false);

  518.         List<DfsPackFile> garbage = garbagePacksForSelectRepresentation();
  519.         if (!garbage.isEmpty() && checkGarbagePacks(objects)) {
  520.             trySelectRepresentation(packer, monitor, objects, garbage, true);
  521.         }
  522.     }

  523.     private void trySelectRepresentation(PackWriter packer,
  524.             ProgressMonitor monitor, Iterable<ObjectToPack> objects,
  525.             List<DfsPackFile> packs, boolean skipFound) throws IOException {
  526.         for (DfsPackFile pack : packs) {
  527.             List<DfsObjectToPack> tmp = findAllFromPack(pack, objects, skipFound);
  528.             if (tmp.isEmpty())
  529.                 continue;
  530.             Collections.sort(tmp, OFFSET_SORT);
  531.             PackReverseIndex rev = pack.getReverseIdx(this);
  532.             DfsObjectRepresentation rep = new DfsObjectRepresentation(pack);
  533.             for (DfsObjectToPack otp : tmp) {
  534.                 pack.representation(rep, otp.getOffset(), this, rev);
  535.                 otp.setOffset(0);
  536.                 packer.select(otp, rep);
  537.                 if (!otp.isFound()) {
  538.                     otp.setFound();
  539.                     monitor.update(1);
  540.                 }
  541.             }
  542.         }
  543.     }

  544.     private static final Comparator<DfsPackFile> PACK_SORT_FOR_REUSE =
  545.         Comparator.comparing(
  546.                 DfsPackFile::getPackDescription, DfsPackDescription.reuseComparator());

  547.     private List<DfsPackFile> sortPacksForSelectRepresentation()
  548.             throws IOException {
  549.         DfsPackFile[] packs = db.getPacks();
  550.         List<DfsPackFile> sorted = new ArrayList<>(packs.length);
  551.         for (DfsPackFile p : packs) {
  552.             if (p.getPackDescription().getPackSource() != UNREACHABLE_GARBAGE) {
  553.                 sorted.add(p);
  554.             }
  555.         }
  556.         Collections.sort(sorted, PACK_SORT_FOR_REUSE);
  557.         return sorted;
  558.     }

  559.     private List<DfsPackFile> garbagePacksForSelectRepresentation()
  560.             throws IOException {
  561.         DfsPackFile[] packs = db.getPacks();
  562.         List<DfsPackFile> garbage = new ArrayList<>(packs.length);
  563.         for (DfsPackFile p : packs) {
  564.             if (p.getPackDescription().getPackSource() == UNREACHABLE_GARBAGE) {
  565.                 garbage.add(p);
  566.             }
  567.         }
  568.         return garbage;
  569.     }

  570.     private static boolean checkGarbagePacks(Iterable<ObjectToPack> objects) {
  571.         for (ObjectToPack otp : objects) {
  572.             if (!((DfsObjectToPack) otp).isFound()) {
  573.                 return true;
  574.             }
  575.         }
  576.         return false;
  577.     }

  578.     private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
  579.             Iterable<ObjectToPack> objects, boolean skipFound)
  580.                     throws IOException {
  581.         List<DfsObjectToPack> tmp = new BlockList<>();
  582.         PackIndex idx = pack.getPackIndex(this);
  583.         for (ObjectToPack obj : objects) {
  584.             DfsObjectToPack otp = (DfsObjectToPack) obj;
  585.             if (skipFound && otp.isFound()) {
  586.                 continue;
  587.             }
  588.             long p = idx.findOffset(otp);
  589.             if (0 < p && !pack.isCorrupt(p)) {
  590.                 otp.setOffset(p);
  591.                 tmp.add(otp);
  592.             }
  593.         }
  594.         return tmp;
  595.     }

  596.     /** {@inheritDoc} */
  597.     @Override
  598.     public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
  599.             boolean validate) throws IOException,
  600.             StoredObjectRepresentationNotAvailableException {
  601.         DfsObjectToPack src = (DfsObjectToPack) otp;
  602.         src.pack.copyAsIs(out, src, validate, this);
  603.     }

  604.     /** {@inheritDoc} */
  605.     @Override
  606.     public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
  607.             throws IOException {
  608.         for (ObjectToPack otp : list)
  609.             out.writeObject(otp);
  610.     }

  611.     /** {@inheritDoc} */
  612.     @Override
  613.     public void copyPackAsIs(PackOutputStream out, CachedPack pack)
  614.             throws IOException {
  615.         ((DfsCachedPack) pack).copyAsIs(out, this);
  616.     }

  617.     /**
  618.      * Copy bytes from the window to a caller supplied buffer.
  619.      *
  620.      * @param file
  621.      *            the file the desired window is stored within.
  622.      * @param position
  623.      *            position within the file to read from.
  624.      * @param dstbuf
  625.      *            destination buffer to copy into.
  626.      * @param dstoff
  627.      *            offset within <code>dstbuf</code> to start copying into.
  628.      * @param cnt
  629.      *            number of bytes to copy. This value may exceed the number of
  630.      *            bytes remaining in the window starting at offset
  631.      *            <code>pos</code>.
  632.      * @return number of bytes actually copied; this may be less than
  633.      *         <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
  634.      *         available.
  635.      * @throws IOException
  636.      *             this cursor does not match the provider or id and the proper
  637.      *             window could not be acquired through the provider's cache.
  638.      */
  639.     int copy(BlockBasedFile file, long position, byte[] dstbuf, int dstoff,
  640.             int cnt) throws IOException {
  641.         if (cnt == 0)
  642.             return 0;

  643.         long length = file.length;
  644.         if (0 <= length && length <= position)
  645.             return 0;

  646.         int need = cnt;
  647.         do {
  648.             pin(file, position);
  649.             int r = block.copy(position, dstbuf, dstoff, need);
  650.             position += r;
  651.             dstoff += r;
  652.             need -= r;
  653.             if (length < 0)
  654.                 length = file.length;
  655.         } while (0 < need && position < length);
  656.         return cnt - need;
  657.     }

  658.     /**
  659.      * Inflate a region of the pack starting at {@code position}.
  660.      *
  661.      * @param pack
  662.      *            the file the desired window is stored within.
  663.      * @param position
  664.      *            position within the file to read from.
  665.      * @param dstbuf
  666.      *            destination buffer the inflater should output decompressed
  667.      *            data to. Must be large enough to store the entire stream,
  668.      *            unless headerOnly is true.
  669.      * @param headerOnly
  670.      *            if true the caller wants only {@code dstbuf.length} bytes.
  671.      * @return number of bytes inflated into <code>dstbuf</code>.
  672.      * @throws IOException
  673.      *             this cursor does not match the provider or id and the proper
  674.      *             window could not be acquired through the provider's cache.
  675.      * @throws DataFormatException
  676.      *             the inflater encountered an invalid chunk of data. Data
  677.      *             stream corruption is likely.
  678.      */
  679.     int inflate(DfsPackFile pack, long position, byte[] dstbuf,
  680.             boolean headerOnly) throws IOException, DataFormatException {
  681.         long start = System.nanoTime();
  682.         prepareInflater();
  683.         pin(pack, position);
  684.         position += block.setInput(position, inf);
  685.         for (int dstoff = 0;;) {
  686.             int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
  687.             dstoff += n;
  688.             if (inf.finished() || (headerOnly && dstoff == dstbuf.length)) {
  689.                 stats.inflatedBytes += dstoff;
  690.                 stats.inflationMicros += BlockBasedFile.elapsedMicros(start);
  691.                 return dstoff;
  692.             } else if (inf.needsInput()) {
  693.                 pin(pack, position);
  694.                 position += block.setInput(position, inf);
  695.             } else if (n == 0)
  696.                 throw new DataFormatException();
  697.         }
  698.     }

  699.     DfsBlock quickCopy(DfsPackFile p, long pos, long cnt)
  700.             throws IOException {
  701.         pin(p, pos);
  702.         if (block.contains(p.key, pos + (cnt - 1)))
  703.             return block;
  704.         return null;
  705.     }

  706.     Inflater inflater() {
  707.         prepareInflater();
  708.         return inf;
  709.     }

  710.     private void prepareInflater() {
  711.         if (inf == null)
  712.             inf = InflaterCache.get();
  713.         else
  714.             inf.reset();
  715.     }

  716.     void pin(BlockBasedFile file, long position) throws IOException {
  717.         if (block == null || !block.contains(file.key, position)) {
  718.             // If memory is low, we may need what is in our window field to
  719.             // be cleaned up by the GC during the get for the next window.
  720.             // So we always clear it, even though we are just going to set
  721.             // it again.
  722.             block = null;
  723.             block = file.getOrLoadBlock(position, this);
  724.         }
  725.     }

  726.     void unpin() {
  727.         block = null;
  728.     }

  729.     /**
  730.      * Get IO statistics accumulated by this reader.
  731.      *
  732.      * @return IO statistics accumulated by this reader.
  733.      */
  734.     public DfsReaderIoStats getIoStats() {
  735.         return new DfsReaderIoStats(stats);
  736.     }

  737.     /**
  738.      * {@inheritDoc}
  739.      * <p>
  740.      * Release the current window cursor.
  741.      */
  742.     @Override
  743.     public void close() {
  744.         last = null;
  745.         block = null;
  746.         baseCache = null;
  747.         try {
  748.             InflaterCache.release(inf);
  749.         } finally {
  750.             inf = null;
  751.         }
  752.     }
  753. }