/* * Copyright (c) 2024, Google LLC and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.internal.storage.dfs; import java.io.IOException; import java.text.MessageFormat; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.ReadableChannelSupplier; import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.Ref; import org.eclipse.jgit.internal.storage.dfs.DfsBlockCache.RefLoader; import org.eclipse.jgit.internal.storage.dfs.DfsBlockCacheConfig.DfsBlockCachePackExtConfig; import org.eclipse.jgit.internal.storage.pack.PackExt; /** * A table that holds multiple cache tables accessed by {@link PackExt} types. * *

* Allows the separation of entries from different {@link PackExt} types to * limit churn in cache caused by entries of differing sizes. *

* Separating these tables enables the fine-tuning of cache tables per extension * type. */ class PackExtBlockCacheTable implements DfsBlockCacheTable { /** * Table name. */ private final String name; private final DfsBlockCacheTable defaultBlockCacheTable; // Holds the unique tables backing the extBlockCacheTables values. private final List blockCacheTableList; // Holds the mapping of PackExt to DfsBlockCacheTables. // The relation between the size of extBlockCacheTables entries and // blockCacheTableList entries is: // blockCacheTableList.size() <= extBlockCacheTables.size() private final Map extBlockCacheTables; /** * Builds the PackExtBlockCacheTable from a list of * {@link DfsBlockCachePackExtConfig}s. * * @param cacheConfig * {@link DfsBlockCacheConfig} containing * {@link DfsBlockCachePackExtConfig}s used to configure * PackExtBlockCacheTable. The {@link DfsBlockCacheConfig} holds * the configuration for the default cache table. * @return the cache table built from the given configs. * @throws IllegalArgumentException * when no {@link DfsBlockCachePackExtConfig} exists in the * {@link DfsBlockCacheConfig}. */ static PackExtBlockCacheTable fromBlockCacheConfigs( DfsBlockCacheConfig cacheConfig) { DfsBlockCacheTable defaultTable = new ClockBlockCacheTable(cacheConfig); Map packExtBlockCacheTables = new HashMap<>(); List packExtConfigs = cacheConfig .getPackExtCacheConfigurations(); if (packExtConfigs == null || packExtConfigs.size() == 0) { throw new IllegalArgumentException( JGitText.get().noPackExtConfigurationGiven); } for (DfsBlockCachePackExtConfig packExtCacheConfig : packExtConfigs) { DfsBlockCacheTable table = new ClockBlockCacheTable( packExtCacheConfig.getPackExtCacheConfiguration()); for (PackExt packExt : packExtCacheConfig.getPackExts()) { if (packExtBlockCacheTables.containsKey(packExt)) { throw new IllegalArgumentException(MessageFormat.format( JGitText.get().duplicatePackExtensionsForCacheTables, packExt)); } packExtBlockCacheTables.put(packExt, table); } } return fromCacheTables(defaultTable, packExtBlockCacheTables); } /** * Creates a new PackExtBlockCacheTable from the combination of a default * {@link DfsBlockCacheTable} and a map of {@link PackExt}s to * {@link DfsBlockCacheTable}s. *

* This method allows for the PackExtBlockCacheTable to handle a mapping of * {@link PackExt}s to arbitrarily defined {@link DfsBlockCacheTable} * implementations. This is especially useful for users wishing to implement * custom cache tables. *

* This is currently made visible for testing. * * @param defaultBlockCacheTable * the default table used when a handling a {@link PackExt} type * that does not map to a {@link DfsBlockCacheTable} mapped by * packExtsCacheTablePairs. * @param packExtBlockCacheTables * the mapping of {@link PackExt}s to * {@link DfsBlockCacheTable}s. A single * {@link DfsBlockCacheTable} can be defined for multiple * {@link PackExt}s in a many-to-one relationship. * @return the PackExtBlockCacheTable created from the * defaultBlockCacheTable and packExtsCacheTablePairs mapping. * @throws IllegalArgumentException * when a {@link PackExt} is defined for multiple * {@link DfsBlockCacheTable}s. */ static PackExtBlockCacheTable fromCacheTables( DfsBlockCacheTable defaultBlockCacheTable, Map packExtBlockCacheTables) { Set blockCacheTables = new HashSet<>(); blockCacheTables.add(defaultBlockCacheTable); blockCacheTables.addAll(packExtBlockCacheTables.values()); String name = defaultBlockCacheTable.getName() + "," //$NON-NLS-1$ + packExtBlockCacheTables.values().stream() .map(DfsBlockCacheTable::getName).sorted() .collect(Collectors.joining(",")); //$NON-NLS-1$ return new PackExtBlockCacheTable(name, defaultBlockCacheTable, List.copyOf(blockCacheTables), packExtBlockCacheTables); } private PackExtBlockCacheTable(String name, DfsBlockCacheTable defaultBlockCacheTable, List blockCacheTableList, Map extBlockCacheTables) { this.name = name; this.defaultBlockCacheTable = defaultBlockCacheTable; this.blockCacheTableList = blockCacheTableList; this.extBlockCacheTables = extBlockCacheTables; } @Override public boolean hasBlock0(DfsStreamKey key) { return getTable(key).hasBlock0(key); } @Override public DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader dfsReader, ReadableChannelSupplier fileChannel) throws IOException { return getTable(file.ext).getOrLoad(file, position, dfsReader, fileChannel); } @Override public Ref getOrLoadRef(DfsStreamKey key, long position, RefLoader loader) throws IOException { return getTable(key).getOrLoadRef(key, position, loader); } @Override public void put(DfsBlock v) { getTable(v.stream).put(v); } @Override public Ref put(DfsStreamKey key, long pos, long size, T v) { return getTable(key).put(key, pos, size, v); } @Override public Ref putRef(DfsStreamKey key, long size, T v) { return getTable(key).putRef(key, size, v); } @Override public boolean contains(DfsStreamKey key, long position) { return getTable(key).contains(key, position); } @Override public T get(DfsStreamKey key, long position) { return getTable(key).get(key, position); } @Override public List getBlockCacheStats() { return blockCacheTableList.stream() .flatMap(cacheTable -> cacheTable.getBlockCacheStats().stream()) .collect(Collectors.toList()); } @Override public String getName() { return name; } private DfsBlockCacheTable getTable(PackExt packExt) { return extBlockCacheTables.getOrDefault(packExt, defaultBlockCacheTable); } private DfsBlockCacheTable getTable(DfsStreamKey key) { return extBlockCacheTables.getOrDefault(getPackExt(key), defaultBlockCacheTable); } private static PackExt getPackExt(DfsStreamKey key) { return PackExt.values()[key.packExtPos]; } }