mirror of
https://github.com/apache/maven-mvnd.git
synced 2025-09-28 08:47:29 +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;
|
||||
|
||||
/**
|
||||
* 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()}.
|
||||
*
|
||||
* @param <K>
|
||||
* @param <V>
|
||||
* @param <K> the type of cache keys
|
||||
* @param <V> the type of cache values
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param key the key to search for
|
||||
* @return the {@link CacheRecord} associated with the given {@code 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);
|
||||
|
||||
@@ -50,12 +56,17 @@ public interface Cache<K, V extends CacheRecord> {
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Remove cached records according to the predicate
|
||||
* Remove all records satisfying the given predicate
|
||||
*/
|
||||
void removeIf(BiPredicate<K, V> predicate);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
|
@@ -20,6 +20,11 @@ package org.mvndaemon.mvnd.cache.factory;
|
||||
*/
|
||||
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();
|
||||
|
||||
}
|
||||
|
@@ -19,19 +19,18 @@ import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Data stored in a {@link Cache} which depends on the state
|
||||
* of a few {@link Path}s.
|
||||
* Data stored in a {@link Cache} depending on the state of a collection of files.
|
||||
*/
|
||||
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();
|
||||
|
||||
/**
|
||||
* Callback called by the cache when this record
|
||||
* is removed from the cache.
|
||||
* Callback called by the cache when this {@link CacheRecord} is removed from the cache.
|
||||
*/
|
||||
void invalidate();
|
||||
|
||||
|
@@ -28,15 +28,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
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 TimestampCacheFactory() {
|
||||
@@ -44,15 +41,19 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
||||
|
||||
@Override
|
||||
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 FileTime lastModifiedTime;
|
||||
final Object fileKey;
|
||||
|
||||
ArtifactTimestamp(Path path) {
|
||||
FileState(Path path) {
|
||||
this.path = path;
|
||||
try {
|
||||
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
@@ -69,7 +70,7 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
ArtifactTimestamp that = (ArtifactTimestamp) o;
|
||||
FileState that = (FileState) o;
|
||||
return path.equals(that.path) &&
|
||||
Objects.equals(lastModifiedTime, that.lastModifiedTime) &&
|
||||
Objects.equals(fileKey, that.fileKey);
|
||||
@@ -79,31 +80,42 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
||||
public int hashCode() {
|
||||
return Objects.hash(path, lastModifiedTime, fileKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FileState [path=" + path + ", lastModifiedTime=" + lastModifiedTime + ", fileKey=" + fileKey + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Record<V extends CacheRecord> {
|
||||
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) {
|
||||
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()
|
||||
.map(ArtifactTimestamp::new)
|
||||
.map(FileState::new)
|
||||
.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<>();
|
||||
|
||||
@Override
|
||||
public boolean contains(K key) {
|
||||
return map.containsKey(key);
|
||||
return this.get(key) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,7 +123,8 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
||||
Record<V> record = map.compute(key, (k, v) -> {
|
||||
if (v != null) {
|
||||
try {
|
||||
if (Objects.equals(v.timestamp, v.current())) {
|
||||
final Set<FileState> currentFileStates = v.currentFileStates();
|
||||
if (Objects.equals(v.fileStates, currentFileStates)) {
|
||||
return v;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
@@ -151,7 +164,7 @@ public class TimestampCacheFactory extends AbstractLogEnabled implements CacheFa
|
||||
return map.compute(key, (k, v) -> {
|
||||
if (v != null) {
|
||||
try {
|
||||
if (Objects.equals(v.timestamp, v.current())) {
|
||||
if (Objects.equals(v.fileStates, v.currentFileStates())) {
|
||||
return v;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
|
@@ -30,15 +30,11 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
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 {
|
||||
|
||||
private final WatchService watchService;
|
||||
@@ -80,7 +76,7 @@ public class WatchServiceCacheFactory extends AbstractLogEnabled implements Cach
|
||||
|
||||
@Override
|
||||
public <K, V extends CacheRecord> Cache<K, V> newCache() {
|
||||
return new DefaultCache<>();
|
||||
return new WatchServiceCache<>();
|
||||
}
|
||||
|
||||
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<>();
|
||||
|
||||
|
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