/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.jackrabbit.core.state;

import org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.core.id.ItemId;
import org.apache.jackrabbit.core.util.Dumpable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintStream;
import java.util.Map;

/**
 * <code>ItemStateReferenceCache</code> internally consists of 2 components:
 * <ul>
 * <li>an <code>ItemStateReferenceMap<code> serving as the primary (or main)
 * cache; it holds weak references to <code>ItemState</code> instances. This
 * <code>ItemStateCache</code> implementation directly represents the
 * contents of the primary cache, i.e. {@link #isCached(ItemId)},
 * {@link #retrieve(ItemId)}}, {@link #isEmpty()} etc. only refer to the contents
 * of the primary cache.</li>
 * <li>an <code>ItemStateCache</code> implementing a custom eviction policy and
 * serving as the secondary (or auxiliary) cache; entries that are automatically
 * flushed from this secondary cache through its eviction policy (LRU, etc.)
 * will be indirectly flushed from the primary (reference) cache by the garbage
 * collector if they are thus rendered weakly reachable.
 * </li>
 * </ul>
 * This implementation of ItemStateCache is thread-safe.
 */
public class ItemStateReferenceCache implements ItemStateCache, Dumpable {

    /** Logger instance */
    private static Logger log = LoggerFactory.getLogger(ItemStateReferenceCache.class);

    /**
     * primary cache storing weak references to <code>ItemState</code>
     * instances.
     */
    @SuppressWarnings("unchecked")
    private final Map<ItemId, ItemState> refs =
        new ReferenceMap(ReferenceMap.HARD, ReferenceMap.SOFT);

    /**
     * secondary cache that automatically flushes entries based on some
     * eviction policy; entries flushed from the secondary cache will be
     * indirectly flushed from the primary (reference) cache by the garbage
     * collector if they thus are rendered weakly reachable.
     */
    private final ItemStateCache cache;

    /**
     * Creates a new <code>ItemStateReferenceCache</code> that uses a
     * <code>MLRUItemStateCache</code> instance as internal secondary
     * cache.
     */
    public ItemStateReferenceCache(ItemStateCacheFactory cacheFactory) {
        this(cacheFactory.newItemStateCache());
    }

    /**
     * Creates a new <code>ItemStateReferenceCache</code> that uses the
     * specified <code>ItemStateCache</code> instance as internal secondary
     * cache.
     *
     * @param cache secondary cache implementing a custom eviction policy
     */
    public ItemStateReferenceCache(ItemStateCache cache) {
        this.cache = cache;
    }

    //-------------------------------------------------------< ItemStateCache >
    /**
     * {@inheritDoc}
     */
    public synchronized boolean isCached(ItemId id) {
        // check primary cache
        return refs.containsKey(id);
    }

    /**
     * {@inheritDoc}
     */
    public synchronized ItemState retrieve(ItemId id) {
        // fake call to update stats of secondary cache
        cache.retrieve(id);

        // retrieve from primary cache
        return refs.get(id);
    }

    /**
     * {@inheritDoc}
     */
    public synchronized ItemState[] retrieveAll() {
        // values of primary cache
        return (ItemState[]) refs.values().toArray(new ItemState[refs.size()]);
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void cache(ItemState state) {
        ItemId id = state.getId();
        if (refs.containsKey(id)) {
            log.warn("overwriting cached entry " + id);
        }
        // fake call to update stats of secondary cache
        cache.cache(state);
        // store weak reference in primary cache
        refs.put(id, state);

    }

    /**
     * {@inheritDoc}
     */
    public synchronized void evict(ItemId id) {
        // fake call to update stats of secondary cache
        cache.evict(id);
        // remove from primary cache
        refs.remove(id);
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void dispose() {
        cache.dispose();
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void evictAll() {
        // fake call to update stats of secondary cache
        cache.evictAll();
        // remove all weak references from primary cache
        refs.clear();
    }

    /**
     * {@inheritDoc}
     */
    public synchronized void update(ItemId id) {
        // delegate
        cache.update(id);
    }

    /**
     * {@inheritDoc}
     */
    public synchronized boolean isEmpty() {
        // check primary cache
        return refs.isEmpty();
    }

    //-------------------------------------------------------------< Dumpable >
    /**
     * {@inheritDoc}
     */
    public synchronized void dump(PrintStream ps) {
        ps.println("ItemStateReferenceCache (" + this + ")");
        ps.println("  refs: " + refs.keySet());
        ps.println();
    }
}

@@ -2423,16 +2423,382 @@
  refs =%0A
+        // I tried using soft instead of weak references here, but that%0A        // seems to have some unexpected performance consequences (notable%0A        // increase in the JCR TCK run time). So even though soft references%0A        // are generally recommended over weak references for caching%0A        // purposes, it seems that using weak references is safer here.%0A
         
@@ -2850,12 +2850,12 @@
 Map.
-SOFT
+WEAK
 );%0A%0A

Improve read/write concurrency