| /* |
| * Copyright (C) 2025, Google Inc. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| package org.eclipse.jgit.junit; |
| |
| import static java.util.function.Function.identity; |
| import static java.util.stream.Collectors.toMap; |
| import static java.util.stream.Collectors.toUnmodifiableList; |
| |
| import java.io.IOException; |
| import java.util.Comparator; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.jgit.errors.CorruptObjectException; |
| import org.eclipse.jgit.errors.MissingObjectException; |
| import org.eclipse.jgit.internal.storage.file.PackIndex; |
| import org.eclipse.jgit.internal.storage.file.PackIndex.EntriesIterator; |
| import org.eclipse.jgit.internal.storage.file.PackReverseIndex; |
| import org.eclipse.jgit.lib.AbbreviatedObjectId; |
| import org.eclipse.jgit.lib.AnyObjectId; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| |
| /** |
| * Create indexes with predefined data |
| * |
| * @since 7.2 |
| */ |
| public class FakeIndexFactory { |
| |
| /** |
| * An object for the fake index |
| * |
| * @param name |
| * a sha1 |
| * @param offset |
| * the (fake) position of the object in the pack |
| */ |
| public record IndexObject(String name, long offset) { |
| /** |
| * Name (sha1) as an objectId |
| * |
| * @return name (a sha1) as an objectId. |
| */ |
| public ObjectId getObjectId() { |
| return ObjectId.fromString(name); |
| } |
| } |
| |
| /** |
| * Return an index populated with these objects |
| * |
| * @param objs |
| * objects to be indexed |
| * @return a PackIndex implementation |
| */ |
| public static PackIndex indexOf(List<IndexObject> objs) { |
| return new FakePackIndex(objs); |
| } |
| |
| /** |
| * Return a reverse pack index with these objects |
| * |
| * @param objs |
| * objects to be indexed |
| * @return a PackReverseIndex implementation |
| */ |
| public static PackReverseIndex reverseIndexOf(List<IndexObject> objs) { |
| return new FakeReverseIndex(objs); |
| } |
| |
| private FakeIndexFactory() { |
| } |
| |
| private static class FakePackIndex implements PackIndex { |
| private static final Comparator<IndexObject> SHA1_COMPARATOR = (o1, |
| o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.name(), |
| o2.name()); |
| |
| private final Map<String, IndexObject> idx; |
| |
| private final List<IndexObject> sha1Ordered; |
| |
| private final long offset64count; |
| |
| FakePackIndex(List<IndexObject> objs) { |
| sha1Ordered = objs.stream().sorted(SHA1_COMPARATOR) |
| .collect(toUnmodifiableList()); |
| idx = objs.stream().collect(toMap(IndexObject::name, identity())); |
| offset64count = objs.stream() |
| .filter(o -> o.offset > Integer.MAX_VALUE).count(); |
| } |
| |
| @Override |
| public Iterator<MutableEntry> iterator() { |
| return new FakeEntriesIterator(sha1Ordered); |
| } |
| |
| @Override |
| public long getObjectCount() { |
| return sha1Ordered.size(); |
| } |
| |
| @Override |
| public long getOffset64Count() { |
| return offset64count; |
| } |
| |
| @Override |
| public ObjectId getObjectId(long nthPosition) { |
| return ObjectId |
| .fromString(sha1Ordered.get((int) nthPosition).name()); |
| } |
| |
| @Override |
| public long getOffset(long nthPosition) { |
| return sha1Ordered.get((int) nthPosition).offset(); |
| } |
| |
| @Override |
| public long findOffset(AnyObjectId objId) { |
| IndexObject o = idx.get(objId.name()); |
| if (o == null) { |
| return -1; |
| } |
| return o.offset(); |
| } |
| |
| @Override |
| public int findPosition(AnyObjectId objId) { |
| IndexObject o = idx.get(objId.name()); |
| if (o == null) { |
| return -1; |
| } |
| return sha1Ordered.indexOf(o); |
| } |
| |
| @Override |
| public long findCRC32(AnyObjectId objId) throws MissingObjectException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public boolean hasCRC32Support() { |
| return false; |
| } |
| |
| @Override |
| public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, |
| int matchLimit) throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public byte[] getChecksum() { |
| return new byte[0]; |
| } |
| } |
| |
| private static class FakeReverseIndex implements PackReverseIndex { |
| private static final Comparator<IndexObject> OFFSET_COMPARATOR = Comparator |
| .comparingLong(IndexObject::offset); |
| |
| private final List<IndexObject> byOffset; |
| |
| private final Map<Long, IndexObject> ridx; |
| |
| FakeReverseIndex(List<IndexObject> objs) { |
| byOffset = objs.stream().sorted(OFFSET_COMPARATOR) |
| .collect(toUnmodifiableList()); |
| ridx = byOffset.stream() |
| .collect(toMap(IndexObject::offset, identity())); |
| } |
| |
| @Override |
| public void verifyPackChecksum(String packFilePath) { |
| // Do nothing |
| } |
| |
| @Override |
| public ObjectId findObject(long offset) { |
| IndexObject indexObject = ridx.get(Long.valueOf(offset)); |
| if (indexObject == null) { |
| return null; |
| } |
| return ObjectId.fromString(indexObject.name()); |
| } |
| |
| @Override |
| public long findNextOffset(long offset, long maxOffset) |
| throws CorruptObjectException { |
| IndexObject o = ridx.get(Long.valueOf(offset)); |
| if (o == null) { |
| throw new CorruptObjectException("Invalid offset"); //$NON-NLS-1$ |
| } |
| int pos = byOffset.indexOf(o); |
| if (pos == byOffset.size() - 1) { |
| return maxOffset; |
| } |
| return byOffset.get(pos + 1).offset(); |
| } |
| |
| @Override |
| public int findPosition(long offset) { |
| IndexObject indexObject = ridx.get(Long.valueOf(offset)); |
| return byOffset.indexOf(indexObject); |
| } |
| |
| @Override |
| public ObjectId findObjectByPosition(int nthPosition) { |
| return byOffset.get(nthPosition).getObjectId(); |
| } |
| } |
| |
| private static class FakeEntriesIterator extends EntriesIterator { |
| |
| private static final byte[] buffer = new byte[Constants.OBJECT_ID_LENGTH]; |
| |
| private final Iterator<IndexObject> it; |
| |
| FakeEntriesIterator(List<IndexObject> objs) { |
| super(objs.size()); |
| it = objs.iterator(); |
| } |
| |
| @Override |
| protected void readNext() { |
| IndexObject next = it.next(); |
| next.getObjectId().copyRawTo(buffer, 0); |
| setIdBuffer(buffer, 0); |
| setOffset(next.offset()); |
| } |
| } |
| } |