WindowCursor.java

  1. /*
  2.  * Copyright (C) 2008-2009, 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.file;

  12. import java.io.IOException;
  13. import java.util.Collection;
  14. import java.util.Collections;
  15. import java.util.HashSet;
  16. import java.util.List;
  17. import java.util.Set;
  18. import java.util.zip.DataFormatException;
  19. import java.util.zip.Inflater;

  20. import org.eclipse.jgit.annotations.Nullable;
  21. import org.eclipse.jgit.errors.IncorrectObjectTypeException;
  22. import org.eclipse.jgit.errors.MissingObjectException;
  23. import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
  24. import org.eclipse.jgit.internal.JGitText;
  25. import org.eclipse.jgit.internal.storage.pack.CachedPack;
  26. import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
  27. import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
  28. import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
  29. import org.eclipse.jgit.internal.storage.pack.PackWriter;
  30. import org.eclipse.jgit.lib.AbbreviatedObjectId;
  31. import org.eclipse.jgit.lib.AnyObjectId;
  32. import org.eclipse.jgit.lib.BitmapIndex;
  33. import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
  34. import org.eclipse.jgit.lib.Constants;
  35. import org.eclipse.jgit.lib.InflaterCache;
  36. import org.eclipse.jgit.lib.ObjectId;
  37. import org.eclipse.jgit.lib.ObjectInserter;
  38. import org.eclipse.jgit.lib.ObjectLoader;
  39. import org.eclipse.jgit.lib.ObjectReader;
  40. import org.eclipse.jgit.lib.ProgressMonitor;

  41. /** Active handle to a ByteWindow. */
  42. final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
  43.     /** Temporary buffer large enough for at least one raw object id. */
  44.     final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH];

  45.     private Inflater inf;

  46.     private ByteWindow window;

  47.     private DeltaBaseCache baseCache;

  48.     @Nullable
  49.     private final ObjectInserter createdFromInserter;

  50.     final FileObjectDatabase db;

  51.     WindowCursor(FileObjectDatabase db) {
  52.         this.db = db;
  53.         this.createdFromInserter = null;
  54.         this.streamFileThreshold = WindowCache.getStreamFileThreshold();
  55.     }

  56.     WindowCursor(FileObjectDatabase db,
  57.             @Nullable ObjectDirectoryInserter createdFromInserter) {
  58.         this.db = db;
  59.         this.createdFromInserter = createdFromInserter;
  60.         this.streamFileThreshold = WindowCache.getStreamFileThreshold();
  61.     }

  62.     DeltaBaseCache getDeltaBaseCache() {
  63.         if (baseCache == null)
  64.             baseCache = new DeltaBaseCache();
  65.         return baseCache;
  66.     }

  67.     /** {@inheritDoc} */
  68.     @Override
  69.     public ObjectReader newReader() {
  70.         return new WindowCursor(db);
  71.     }

  72.     /** {@inheritDoc} */
  73.     @Override
  74.     public BitmapIndex getBitmapIndex() throws IOException {
  75.         for (Pack pack : db.getPacks()) {
  76.             PackBitmapIndex index = pack.getBitmapIndex();
  77.             if (index != null)
  78.                 return new BitmapIndexImpl(index);
  79.         }
  80.         return null;
  81.     }

  82.     /** {@inheritDoc} */
  83.     @Override
  84.     public Collection<CachedPack> getCachedPacksAndUpdate(
  85.             BitmapBuilder needBitmap) throws IOException {
  86.         for (Pack pack : db.getPacks()) {
  87.             PackBitmapIndex index = pack.getBitmapIndex();
  88.             if (needBitmap.removeAllOrNone(index))
  89.                 return Collections.<CachedPack> singletonList(
  90.                         new LocalCachedPack(Collections.singletonList(pack)));
  91.         }
  92.         return Collections.emptyList();
  93.     }

  94.     /** {@inheritDoc} */
  95.     @Override
  96.     public Collection<ObjectId> resolve(AbbreviatedObjectId id)
  97.             throws IOException {
  98.         if (id.isComplete())
  99.             return Collections.singleton(id.toObjectId());
  100.         HashSet<ObjectId> matches = new HashSet<>(4);
  101.         db.resolve(matches, id);
  102.         return matches;
  103.     }

  104.     /** {@inheritDoc} */
  105.     @Override
  106.     public boolean has(AnyObjectId objectId) throws IOException {
  107.         return db.has(objectId);
  108.     }

  109.     /** {@inheritDoc} */
  110.     @Override
  111.     public ObjectLoader open(AnyObjectId objectId, int typeHint)
  112.             throws MissingObjectException, IncorrectObjectTypeException,
  113.             IOException {
  114.         final ObjectLoader ldr = db.openObject(this, objectId);
  115.         if (ldr == null) {
  116.             if (typeHint == OBJ_ANY)
  117.                 throw new MissingObjectException(objectId.copy(),
  118.                         JGitText.get().unknownObjectType2);
  119.             throw new MissingObjectException(objectId.copy(), typeHint);
  120.         }
  121.         if (typeHint != OBJ_ANY && ldr.getType() != typeHint)
  122.             throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
  123.         return ldr;
  124.     }

  125.     /** {@inheritDoc} */
  126.     @Override
  127.     public Set<ObjectId> getShallowCommits() throws IOException {
  128.         return db.getShallowCommits();
  129.     }

  130.     /** {@inheritDoc} */
  131.     @Override
  132.     public long getObjectSize(AnyObjectId objectId, int typeHint)
  133.             throws MissingObjectException, IncorrectObjectTypeException,
  134.             IOException {
  135.         long sz = db.getObjectSize(this, objectId);
  136.         if (sz < 0) {
  137.             if (typeHint == OBJ_ANY)
  138.                 throw new MissingObjectException(objectId.copy(),
  139.                         JGitText.get().unknownObjectType2);
  140.             throw new MissingObjectException(objectId.copy(), typeHint);
  141.         }
  142.         return sz;
  143.     }

  144.     /** {@inheritDoc} */
  145.     @Override
  146.     public LocalObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
  147.         return new LocalObjectToPack(objectId, type);
  148.     }

  149.     /** {@inheritDoc} */
  150.     @Override
  151.     public void selectObjectRepresentation(PackWriter packer,
  152.             ProgressMonitor monitor, Iterable<ObjectToPack> objects)
  153.             throws IOException, MissingObjectException {
  154.         for (ObjectToPack otp : objects) {
  155.             db.selectObjectRepresentation(packer, otp, this);
  156.             monitor.update(1);
  157.         }
  158.     }

  159.     /** {@inheritDoc} */
  160.     @Override
  161.     public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
  162.             boolean validate) throws IOException,
  163.             StoredObjectRepresentationNotAvailableException {
  164.         LocalObjectToPack src = (LocalObjectToPack) otp;
  165.         src.pack.copyAsIs(out, src, validate, this);
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
  170.             throws IOException {
  171.         for (ObjectToPack otp : list)
  172.             out.writeObject(otp);
  173.     }

  174.     /**
  175.      * Copy bytes from the window to a caller supplied buffer.
  176.      *
  177.      * @param pack
  178.      *            the file the desired window is stored within.
  179.      * @param position
  180.      *            position within the file to read from.
  181.      * @param dstbuf
  182.      *            destination buffer to copy into.
  183.      * @param dstoff
  184.      *            offset within <code>dstbuf</code> to start copying into.
  185.      * @param cnt
  186.      *            number of bytes to copy. This value may exceed the number of
  187.      *            bytes remaining in the window starting at offset
  188.      *            <code>pos</code>.
  189.      * @return number of bytes actually copied; this may be less than
  190.      *         <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
  191.      *         available.
  192.      * @throws IOException
  193.      *             this cursor does not match the provider or id and the proper
  194.      *             window could not be acquired through the provider's cache.
  195.      */
  196.     int copy(final Pack pack, long position, final byte[] dstbuf,
  197.             int dstoff, final int cnt) throws IOException {
  198.         final long length = pack.length;
  199.         int need = cnt;
  200.         while (need > 0 && position < length) {
  201.             pin(pack, position);
  202.             final int r = window.copy(position, dstbuf, dstoff, need);
  203.             position += r;
  204.             dstoff += r;
  205.             need -= r;
  206.         }
  207.         return cnt - need;
  208.     }

  209.     /** {@inheritDoc} */
  210.     @Override
  211.     public void copyPackAsIs(PackOutputStream out, CachedPack pack)
  212.             throws IOException {
  213.         ((LocalCachedPack) pack).copyAsIs(out, this);
  214.     }

  215.     void copyPackAsIs(final Pack pack, final long length,
  216.             final PackOutputStream out) throws IOException {
  217.         long position = 12;
  218.         long remaining = length - (12 + 20);
  219.         while (0 < remaining) {
  220.             pin(pack, position);

  221.             int ptr = (int) (position - window.start);
  222.             int n = (int) Math.min(window.size() - ptr, remaining);
  223.             window.write(out, position, n);
  224.             position += n;
  225.             remaining -= n;
  226.         }
  227.     }

  228.     /**
  229.      * Inflate a region of the pack starting at {@code position}.
  230.      *
  231.      * @param pack
  232.      *            the file the desired window is stored within.
  233.      * @param position
  234.      *            position within the file to read from.
  235.      * @param dstbuf
  236.      *            destination buffer the inflater should output decompressed
  237.      *            data to. Must be large enough to store the entire stream,
  238.      *            unless headerOnly is true.
  239.      * @param headerOnly
  240.      *            if true the caller wants only {@code dstbuf.length} bytes.
  241.      * @return number of bytes inflated into <code>dstbuf</code>.
  242.      * @throws IOException
  243.      *             this cursor does not match the provider or id and the proper
  244.      *             window could not be acquired through the provider's cache.
  245.      * @throws DataFormatException
  246.      *             the inflater encountered an invalid chunk of data. Data
  247.      *             stream corruption is likely.
  248.      */
  249.     int inflate(final Pack pack, long position, final byte[] dstbuf,
  250.             boolean headerOnly) throws IOException, DataFormatException {
  251.         prepareInflater();
  252.         pin(pack, position);
  253.         position += window.setInput(position, inf);
  254.         for (int dstoff = 0;;) {
  255.             int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
  256.             dstoff += n;
  257.             if (inf.finished() || (headerOnly && dstoff == dstbuf.length))
  258.                 return dstoff;
  259.             if (inf.needsInput()) {
  260.                 pin(pack, position);
  261.                 position += window.setInput(position, inf);
  262.             } else if (n == 0)
  263.                 throw new DataFormatException();
  264.         }
  265.     }

  266.     ByteArrayWindow quickCopy(Pack p, long pos, long cnt)
  267.             throws IOException {
  268.         pin(p, pos);
  269.         if (window instanceof ByteArrayWindow
  270.                 && window.contains(p, pos + (cnt - 1)))
  271.             return (ByteArrayWindow) window;
  272.         return null;
  273.     }

  274.     Inflater inflater() {
  275.         prepareInflater();
  276.         return inf;
  277.     }

  278.     private void prepareInflater() {
  279.         if (inf == null)
  280.             inf = InflaterCache.get();
  281.         else
  282.             inf.reset();
  283.     }

  284.     void pin(Pack pack, long position)
  285.             throws IOException {
  286.         final ByteWindow w = window;
  287.         if (w == null || !w.contains(pack, position)) {
  288.             // If memory is low, we may need what is in our window field to
  289.             // be cleaned up by the GC during the get for the next window.
  290.             // So we always clear it, even though we are just going to set
  291.             // it again.
  292.             //
  293.             window = null;
  294.             window = WindowCache.get(pack, position);
  295.         }
  296.     }

  297.     /** {@inheritDoc} */
  298.     @Override
  299.     @Nullable
  300.     public ObjectInserter getCreatedFromInserter() {
  301.         return createdFromInserter;
  302.     }

  303.     /**
  304.      * {@inheritDoc}
  305.      * <p>
  306.      * Release the current window cursor.
  307.      */
  308.     @Override
  309.     public void close() {
  310.         window = null;
  311.         baseCache = null;
  312.         try {
  313.             InflaterCache.release(inf);
  314.         } finally {
  315.             inf = null;
  316.         }
  317.     }
  318. }