CacheValue.java

// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
 *******************************************************************************
 * Copyright (C) 2016, International Business Machines Corporation and
 * others. All Rights Reserved.
 *******************************************************************************
 */
package com.ibm.icu.impl;

import com.ibm.icu.util.ICUException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;

/**
 * Value type for cache items: Holds a value either via a direct reference or via a {@link
 * Reference}, depending on the current "strength" when {@code getInstance()} was called.
 *
 * <p>The value is <i>conceptually<i> immutable. If it is held via a direct reference, then it is
 * actually immutable.
 *
 * <p>A {@code Reference} may be cleared (garbage-collected), after which {@code get()} returns
 * null. It can then be reset via {@code resetIfAbsent()}. The new value should be the same as, or
 * equivalent to, the old value.
 *
 * <p>Null values are supported. They can be distinguished from cleared values via {@code isNull()}.
 *
 * @param <V> Cache instance value type
 */
public abstract class CacheValue<V> {
    /**
     * "Strength" of holding a value in CacheValue instances. The default strength is {@code SOFT}.
     */
    public enum Strength {
        /**
         * Subsequent {@code getInstance()}-created objects will hold direct references to their
         * values.
         */
        STRONG,
        /**
         * Subsequent {@code getInstance()}-created objects will hold {@link SoftReference}s to
         * their values.
         */
        SOFT
    };

    private static volatile Strength strength = Strength.SOFT;

    @SuppressWarnings("rawtypes")
    private static final CacheValue NULL_VALUE = new NullValue();

    /** Changes the "strength" of value references for subsequent {@code getInstance()} calls. */
    public static void setStrength(Strength strength) {
        CacheValue.strength = strength;
    }

    /** Returns true if the "strength" is set to {@code STRONG}. */
    public static boolean futureInstancesWillBeStrong() {
        return strength == Strength.STRONG;
    }

    /**
     * Returns a CacheValue instance that holds the value. It holds it directly if the value is null
     * or if the current "strength" is {@code STRONG}. Otherwise, it holds it via a {@link
     * Reference}.
     */
    @SuppressWarnings("unchecked")
    public static <V> CacheValue<V> getInstance(V value) {
        if (value == null) {
            return NULL_VALUE;
        }
        return strength == Strength.STRONG ? new StrongValue<V>(value) : new SoftValue<V>(value);
    }

    /**
     * Distinguishes a null value from a Reference value that has been cleared.
     *
     * @return true if this object represents a null value.
     */
    public boolean isNull() {
        return false;
    }

    /**
     * Returns the value (which can be null), or null if it was held in a Reference and has been
     * cleared.
     */
    public abstract V get();

    /**
     * If the value was held via a {@link Reference} which has been cleared, then it is replaced
     * with a new {@link Reference} to the new value, and the new value is returned. The old and new
     * values should be the same or equivalent.
     *
     * <p>Otherwise the old value is returned.
     *
     * @param value Replacement value, for when the current {@link Reference} has been cleared.
     * @return The old or new value.
     */
    public abstract V resetIfCleared(V value);

    private static final class NullValue<V> extends CacheValue<V> {
        @Override
        public boolean isNull() {
            return true;
        }

        @Override
        public V get() {
            return null;
        }

        @Override
        public V resetIfCleared(V value) {
            if (value != null) {
                throw new ICUException("resetting a null value to a non-null value");
            }
            return null;
        }
    }

    private static final class StrongValue<V> extends CacheValue<V> {
        private V value;

        StrongValue(V value) {
            this.value = value;
        }

        @Override
        public V get() {
            return value;
        }

        @Override
        public V resetIfCleared(V value) {
            // value and this.value should be equivalent, but
            // we do not require equals() to be implemented appropriately.
            return this.value;
        }
    }

    private static final class SoftValue<V> extends CacheValue<V> {
        private volatile Reference<V> ref; // volatile for unsynchronized get()

        SoftValue(V value) {
            ref = new SoftReference<V>(value);
        }

        @Override
        public V get() {
            return ref.get();
        }

        @Override
        public synchronized V resetIfCleared(V value) {
            V oldValue = ref.get();
            if (oldValue == null) {
                ref = new SoftReference<V>(value);
                return value;
            } else {
                // value and oldValue should be equivalent, but
                // we do not require equals() to be implemented appropriately.
                return oldValue;
            }
        }
    }
}