PooledIconDeclaration.java
package swingtree.api;
import com.google.errorprone.annotations.Immutable;
import org.jspecify.annotations.Nullable;
import swingtree.layout.Size;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
/**
* A basic implementation of the {@link IconDeclaration} interface
* with object pooling built in. So repeated calls to the {@link #of(Size, SourceFormat, String)}
* factory method with the same values, will always return the same instance.
* This is because the {@link IconDeclaration} is used as key in weak hash map based caches.
*/
@Immutable
@SuppressWarnings("Immutable")
final class PooledIconDeclaration implements IconDeclaration
{
/**
* Implementations of {@link IconDeclaration} are used as weak cache keys
* in the style engine as well as the image cache... So we pool instances
* of this in order to ensure that cache entries are not cleared too early.<br>
* This optimizes both cache hits as well as memory consumption!
*/
private static final Map<PooledIconDeclaration, WeakReference<PooledIconDeclaration>> POOL = Collections.synchronizedMap(new WeakHashMap<>());
private static PooledIconDeclaration intern(PooledIconDeclaration value) {
synchronized (POOL) {
WeakReference<PooledIconDeclaration> ref = POOL.get(value);
if (ref != null) {
PooledIconDeclaration canonical = ref.get();
if (canonical != null) {
return canonical;
}
}
POOL.put(value, new WeakReference<>(value));
return value;
}
}
private final @Nullable Size size;
private final SourceFormat sourceFormat;
private final String source;
private final AtomicReference<@Nullable Integer> _hashCache = new AtomicReference<>();
public static PooledIconDeclaration of(@Nullable Size size, SourceFormat sourceFormat, String source) {
return intern(new PooledIconDeclaration( size, sourceFormat, source ));
}
private PooledIconDeclaration(@Nullable Size size, SourceFormat sourceFormat, String source) {
this.size = size;
this.sourceFormat = sourceFormat;
this.source = source;
}
@Override
public Optional<Size> size() {
return Optional.ofNullable(size);
}
@Override
public String source() {
return source;
}
@Override
public SourceFormat sourceFormat() {
return sourceFormat;
}
@Override
public String toString() {
return this.getClass().getSimpleName()+"["+
"size=" + ( size().map(it->it.equals(Size.unknown()) ? "?" : String.valueOf(it) ).orElse("?") ) + ", " +
"sourceFormat=" + sourceFormat + ", " +
"source='" + source + "'" +
"]";
}
@Override public int hashCode() {
return Objects.requireNonNull(
_hashCache.updateAndGet(h -> h != null ? h : Objects.hash(source, sourceFormat, size))
);
}
@Override public boolean equals( Object other ) {
if ( other == this ) return true;
if ( other == null ) return false;
if ( other.getClass() != this.getClass() ) return false;
IconDeclaration that = (IconDeclaration) other;
return Objects.equals(this.source(), that.source())
&& Objects.equals(this.sourceFormat(), that.sourceFormat())
&& Objects.equals(this.size(), that.size());
}
}