/*
* Copyright 2014 - 2018 Rafael Winterhalter
*
* 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 net.bytebuddy.matcher;
import edu.umd.cs.findbugs.annotations.
SuppressFBWarnings;
import net.bytebuddy.build.
HashCodeAndEqualsPlugin;
import java.util.
Iterator;
import java.util.concurrent.
ConcurrentMap;
/**
* A matcher that remembers the results of previously matching an equal target.
*
* @param <T> The actual matched type of this matcher.
*/
@
HashCodeAndEqualsPlugin.
Enhance(permitSubclassEquality = true)
public class
CachingMatcher<T> extends
ElementMatcher.
Junction.
AbstractBase<T> {
/**
* The underlying matcher to apply for non-cached targets.
*/
private final
ElementMatcher<? super T>
matcher;
/**
* A map that serves as a cache for previous matches.
*/
@
HashCodeAndEqualsPlugin.
ValueHandling(
HashCodeAndEqualsPlugin.
ValueHandling.
Sort.
IGNORE)
protected final
ConcurrentMap<? super T,
Boolean>
map;
/**
* Creates a new caching matcher.
*
* @param matcher The underlying matcher to apply for non-cached targets.
* @param map A map that serves as a cache for previous matches. This match is strongly referenced and
* can cause a memory leak if it is not evicted while keeping this matcher alive.
*/
public
CachingMatcher(
ElementMatcher<? super T>
matcher,
ConcurrentMap<? super T,
Boolean>
map) {
this.
matcher =
matcher;
this.
map =
map;
}
/**
* {@inheritDoc}
*/
public boolean
matches(T
target) {
Boolean cached =
map.
get(
target);
if (
cached == null) {
cached =
onCacheMiss(
target);
}
return
cached;
}
/**
* Invoked if the cache is not hit.
*
* @param target The element to be matched.
* @return {@code true} if the element is matched.
*/
protected boolean
onCacheMiss(T
target) {
boolean
cached =
matcher.
matches(
target);
map.
put(
target,
cached);
return
cached;
}
@
Override
public
String toString() {
return "cached(" +
matcher + ")";
}
/**
* A caching matcher with inline cache eviction.
*
* @param <S> The actual matched type of this matcher.
*/
@
SuppressFBWarnings(value = "EQ_DOESNT_OVERRIDE_EQUALS", justification = "Equality does not consider eviction size")
public static class
WithInlineEviction<S> extends
CachingMatcher<S> {
/**
* The maximum amount of entries in this map before removing a random entry from the map.
*/
private final int
evictionSize;
/**
* Creates a new caching matcher with inlined cache eviction.
*
* @param matcher The underlying matcher to apply for non-cached targets.
* @param map A map that serves as a cache for previous matches. This match is strongly referenced and
* can cause a memory leak if it is not evicted while keeping this matcher alive.
* @param evictionSize The maximum amount of entries in this map before removing a random entry from the map.
*/
public
WithInlineEviction(
ElementMatcher<? super S>
matcher,
ConcurrentMap<? super S,
Boolean>
map, int
evictionSize) {
super(
matcher,
map);
this.
evictionSize =
evictionSize;
}
/**
* {@inheritDoc}
*/
protected boolean
onCacheMiss(S
target) {
if (
map.
size() >=
evictionSize) {
Iterator<?>
iterator =
map.
entrySet().
iterator();
iterator.
next();
iterator.
remove();
}
return super.onCacheMiss(
target);
}
}
}