mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-28 17:15:55 +00:00
Test CacheFactory, add JavaDoc
This commit is contained in:
@@ -19,13 +19,13 @@ import java.util.function.BiPredicate;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache containing records that can be invalidated.
|
* A cache containing records that can be invalidated.
|
||||||
*
|
*
|
||||||
* Whenever the paths associated to a given {@link CacheRecord} have been modified,
|
* Whenever the paths associated with the given {@link CacheRecord} have been modified,
|
||||||
* the record will be invalidated using {@link CacheRecord#invalidate()}.
|
* the record will be invalidated using {@link CacheRecord#invalidate()}.
|
||||||
*
|
*
|
||||||
* @param <K>
|
* @param <K> the type of cache keys
|
||||||
* @param <V>
|
* @param <V> the type of cache values
|
||||||
*/
|
*/
|
||||||
public interface Cache<K, V extends CacheRecord> {
|
public interface Cache<K, V extends CacheRecord> {
|
||||||
|
|
||||||
@@ -36,11 +36,17 @@ public interface Cache<K, V extends CacheRecord> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the cached record for the key
|
* Get the cached record for the key
|
||||||
|
*
|
||||||
|
* @param key the key to search for
|
||||||
|
* @return the {@link CacheRecord} associated with the given {@code key}
|
||||||
*/
|
*/
|
||||||
V get(K key);
|
V get(K key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Put a record in the cache
|
* Put the given {@link CacheRecord} into the cache under the given {@code key}
|
||||||
|
*
|
||||||
|
* @param key the key to store the given {@code value} under
|
||||||
|
* @param value the value to store under the given {@code key}
|
||||||
*/
|
*/
|
||||||
void put(K key, V value);
|
void put(K key, V value);
|
||||||
|
|
||||||
@@ -50,12 +56,17 @@ public interface Cache<K, V extends CacheRecord> {
|
|||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove cached records according to the predicate
|
* Remove all records satisfying the given predicate
|
||||||
*/
|
*/
|
||||||
void removeIf(BiPredicate<K, V> predicate);
|
void removeIf(BiPredicate<K, V> predicate);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or compute the cached value if absent and return it.
|
* Get or compute the cached value if absent and return it.
|
||||||
|
*
|
||||||
|
* @param key the key to search for
|
||||||
|
* @param mappingFunction the function to use for the computation of the new {@link CacheRecord} if the key is not
|
||||||
|
* available in this {@link Cache} yet
|
||||||
|
* @return the existing or newly computed {@link CacheRecord}
|
||||||
*/
|
*/
|
||||||
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
|
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);
|
||||||
|
|
||||||
|
@@ -20,6 +20,11 @@ package org.mvndaemon.mvnd.cache.factory;
|
|||||||
*/
|
*/
|
||||||
public interface CacheFactory {
|
public interface CacheFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param <K> the type of {@link Cache} keys
|
||||||
|
* @param <V> the type of {@link Cache} values
|
||||||
|
* @return a new {@link Cache}
|
||||||
|
*/
|
||||||
<K, V extends CacheRecord> Cache<K, V> newCache();
|
<K, V extends CacheRecord> Cache<K, V> newCache();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -19,19 +19,18 @@ import java.nio.file.Path;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data stored in a {@link Cache} which depends on the state
|
* Data stored in a {@link Cache} depending on the state of a collection of files.
|
||||||
* of a few {@link Path}s.
|
|
||||||
*/
|
*/
|
||||||
public interface CacheRecord {
|
public interface CacheRecord {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of Path that will invalidate this record if modified.
|
* @return a {@link Stream} of file (not directory) {@link Path}s whose modification or deletion causes invalidation
|
||||||
|
* of this {@link CacheRecord}.
|
||||||
*/
|
*/
|
||||||
Stream<Path> getDependentPaths();
|
Stream<Path> getDependentPaths();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback called by the cache when this record
|
* Callback called by the cache when this {@link CacheRecord} is removed from the cache.
|
||||||
* is removed from the cache.
|
|
||||||
*/
|
*/
|
||||||
void invalidate();
|
void invalidate();
|
||||||
|
|
||||||
|
@@ -28,15 +28,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import org.codehaus.plexus.logging.AbstractLogEnabled;
|
import org.codehaus.plexus.logging.AbstractLogEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for {@link Cache} objects.
|
* A factory for {@link Cache} objects invalidating its entries based on {@link BasicFileAttributes#lastModifiedTime()}
|
||||||
|
* and {@link java.nio.file.attribute.BasicFileAttributes#fileKey()}.
|
||||||
*/
|
*/
|
||||||
@Named
|
|
||||||
@Singleton
|
|
||||||
public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFactory {
|
public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFactory {
|
||||||
|
|
||||||
public TimestampCacheFactory() {
|
public TimestampCacheFactory() {
|
||||||
@@ -44,15 +41,19 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
||||||
return new DefaultCache<>();
|
return new TimestampCache<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ArtifactTimestamp {
|
/**
|
||||||
|
* A state of a file given by {@link BasicFileAttributes#lastModifiedTime()} and
|
||||||
|
* {@link java.nio.file.attribute.BasicFileAttributes#fileKey()} at the time of {@link FileState} creation.
|
||||||
|
*/
|
||||||
|
static class FileState {
|
||||||
final Path path;
|
final Path path;
|
||||||
final FileTime lastModifiedTime;
|
final FileTime lastModifiedTime;
|
||||||
final Object fileKey;
|
final Object fileKey;
|
||||||
|
|
||||||
ArtifactTimestamp(Path path) {
|
FileState(Path path) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
try {
|
try {
|
||||||
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||||
@@ -69,7 +70,7 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
|||||||
return true;
|
return true;
|
||||||
if (o == null || getClass() != o.getClass())
|
if (o == null || getClass() != o.getClass())
|
||||||
return false;
|
return false;
|
||||||
ArtifactTimestamp that = (ArtifactTimestamp) o;
|
FileState that = (FileState) o;
|
||||||
return path.equals(that.path) &&
|
return path.equals(that.path) &&
|
||||||
Objects.equals(lastModifiedTime, that.lastModifiedTime) &&
|
Objects.equals(lastModifiedTime, that.lastModifiedTime) &&
|
||||||
Objects.equals(fileKey, that.fileKey);
|
Objects.equals(fileKey, that.fileKey);
|
||||||
@@ -79,31 +80,42 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(path, lastModifiedTime, fileKey);
|
return Objects.hash(path, lastModifiedTime, fileKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "FileState [path=" + path + ", lastModifiedTime=" + lastModifiedTime + ", fileKey=" + fileKey + "]";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Record<V extends CacheRecord> {
|
static class Record<V extends CacheRecord> {
|
||||||
final V record;
|
final V record;
|
||||||
final Set<ArtifactTimestamp> timestamp;
|
|
||||||
|
/** {@link Set} of {@link FileState}s at the creation time of this {@link Record} */
|
||||||
|
final Set<FileState> fileStates;
|
||||||
|
|
||||||
public Record(V record) {
|
public Record(V record) {
|
||||||
this.record = record;
|
this.record = record;
|
||||||
this.timestamp = current();
|
this.fileStates = currentFileStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<ArtifactTimestamp> current() {
|
/**
|
||||||
|
* @return {@link Set} of {@link FileState}s at current time
|
||||||
|
*/
|
||||||
|
private Set<FileState> currentFileStates() {
|
||||||
return record.getDependentPaths()
|
return record.getDependentPaths()
|
||||||
.map(ArtifactTimestamp::new)
|
.map(FileState::new)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class DefaultCache<K, V extends CacheRecord> implements Cache<K, V> {
|
static class TimestampCache<K, V extends CacheRecord> implements Cache<K, V> {
|
||||||
|
|
||||||
private final ConcurrentHashMap<K, Record<V>> map = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<K, Record<V>> map = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean contains(K key) {
|
public boolean contains(K key) {
|
||||||
return map.containsKey(key);
|
return this.get(key) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,7 +123,8 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
|||||||
Record<V> record = map.compute(key, (k, v) -> {
|
Record<V> record = map.compute(key, (k, v) -> {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
try {
|
try {
|
||||||
if (Objects.equals(v.timestamp, v.current())) {
|
final Set<FileState> currentFileStates = v.currentFileStates();
|
||||||
|
if (Objects.equals(v.fileStates, currentFileStates)) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
@@ -151,7 +164,7 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
|||||||
return map.compute(key, (k, v) -> {
|
return map.compute(key, (k, v) -> {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
try {
|
try {
|
||||||
if (Objects.equals(v.timestamp, v.current())) {
|
if (Objects.equals(v.fileStates, v.currentFileStates())) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
|
@@ -30,15 +30,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import org.codehaus.plexus.logging.AbstractLogEnabled;
|
import org.codehaus.plexus.logging.AbstractLogEnabled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory for {@link Cache} objects.
|
* A factory for {@link Cache} objects invalidating its entries based on events received from {@link WatchService}.
|
||||||
*/
|
*/
|
||||||
@Named
|
|
||||||
@Singleton
|
|
||||||
public class WatchServiceCacheFactory extends AbstractLogEnabled implements CacheFactory {
|
public class WatchServiceCacheFactory extends AbstractLogEnabled implements CacheFactory {
|
||||||
|
|
||||||
private final WatchService watchService;
|
private final WatchService watchService;
|
||||||
@@ -80,7 +76,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
||||||
return new DefaultCache<>();
|
return new WatchServiceCache<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Registration register(Path key, Registration value) {
|
private Registration register(Path key, Registration value) {
|
||||||
@@ -205,7 +201,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultCache<K, V extends CacheRecord> implements Cache<K, V> {
|
class WatchServiceCache<K, V extends CacheRecord> implements Cache<K, V> {
|
||||||
|
|
||||||
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
102
daemon/src/test/java/org/mvndaemon/mvnd/cache/impl/TimestampCacheFactoryTest.java
vendored
Normal file
102
daemon/src/test/java/org/mvndaemon/mvnd/cache/impl/TimestampCacheFactoryTest.java
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.mvndaemon.mvnd.cache.impl;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import org.mvndaemon.mvnd.cache.factory.Cache;
|
||||||
|
import org.mvndaemon.mvnd.cache.factory.CacheRecord;
|
||||||
|
import org.mvndaemon.mvnd.cache.factory.DefaultCacheFactory;
|
||||||
|
import org.mvndaemon.mvnd.common.Os;
|
||||||
|
|
||||||
|
public class TimestampCacheFactoryTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void putGet(@TempDir Path tempDir) throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
final Path file1 = tempDir.resolve("file1");
|
||||||
|
Files.write(file1, "content1".getBytes(StandardCharsets.UTF_8));
|
||||||
|
final SimpleCacheRecord record1 = new SimpleCacheRecord(file1);
|
||||||
|
|
||||||
|
final Path file2 = tempDir.resolve("file2");
|
||||||
|
Files.write(file2, "content2".getBytes(StandardCharsets.UTF_8));
|
||||||
|
final SimpleCacheRecord record2 = new SimpleCacheRecord(file2);
|
||||||
|
|
||||||
|
final Cache<String, CacheRecord> cache = new DefaultCacheFactory().newCache();
|
||||||
|
|
||||||
|
final String k1 = "k1";
|
||||||
|
cache.put(k1, record1);
|
||||||
|
final String k2 = "k2";
|
||||||
|
cache.put(k2, record2);
|
||||||
|
|
||||||
|
Assertions.assertTrue(cache.contains(k1));
|
||||||
|
Assertions.assertEquals(record1, cache.get(k1));
|
||||||
|
Assertions.assertFalse(record1.invalidated);
|
||||||
|
Assertions.assertTrue(cache.contains(k2));
|
||||||
|
Assertions.assertEquals(record2, cache.get(k2));
|
||||||
|
Assertions.assertFalse(record2.invalidated);
|
||||||
|
|
||||||
|
Files.write(file1, "content1.1".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
if (Os.current() == Os.WINDOWS) {
|
||||||
|
Thread.sleep(3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assertions.assertFalse(cache.contains(k1));
|
||||||
|
Assertions.assertNull(cache.get(k1));
|
||||||
|
Assertions.assertTrue(record1.invalidated);
|
||||||
|
Assertions.assertTrue(cache.contains(k2));
|
||||||
|
Assertions.assertEquals(record2, cache.get(k2));
|
||||||
|
Assertions.assertFalse(record2.invalidated);
|
||||||
|
|
||||||
|
Files.delete(file2);
|
||||||
|
if (Os.current() == Os.WINDOWS) {
|
||||||
|
Thread.sleep(3000);
|
||||||
|
}
|
||||||
|
Assertions.assertFalse(cache.contains(k2));
|
||||||
|
Assertions.assertNull(cache.get(k2));
|
||||||
|
Assertions.assertTrue(record2.invalidated);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SimpleCacheRecord implements org.mvndaemon.mvnd.cache.factory.CacheRecord {
|
||||||
|
|
||||||
|
private final List<Path> paths;
|
||||||
|
private boolean invalidated = false;
|
||||||
|
|
||||||
|
SimpleCacheRecord(Path... paths) {
|
||||||
|
this.paths = Arrays.asList(paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<Path> getDependentPaths() {
|
||||||
|
return paths.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidate() {
|
||||||
|
this.invalidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user