    001// Copyright 2013 The Apache Software Foundation
    002//
    003// Licensed under the Apache License, Version 2.0 (the "License");
    004// you may not use this file except in compliance with the License.
    005// You may obtain a copy of the License at
    006//
    007//     http:#www.apache.org/licenses/LICENSE-2.0
    008//
    009// Unless required by applicable law or agreed to in writing, software
    010// distributed under the License is distributed on an "AS IS" BASIS,
    011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012// See the License for the specific language governing permissions and
    013// limitations under the License.
    014
    015package org.apache.tapestry5.internal.util;
    016
    017import org.apache.tapestry5.ioc.Invokable;
    018import org.apache.tapestry5.services.InvalidationEventHub;
    019
    020/**
    021 * A utility class for managing a cacheable result that can be recomputed when needed.
    022 */
    023public class RecomputableSupport
    024{
    025    private volatile int masterVersion = 1;
    026
    027    /**
    028     * Invalidates any existing {@link #create(org.apache.tapestry5.ioc.Invokable)} wrappers} such that they will
    029     * re-perform the computation when next invoked.
    030     */
    031    public void invalidate()
    032    {
    033        masterVersion++;
    034    }
    035
    036    /**
    037     * Forces {@link #invalidate()} to be invoked when the hub emits an {@linkplain InvalidationEventHub#addInvalidationCallback(Runnable) invalidation callback}.
    038     *
    039     * @param hub
    040     */
    041    public void initialize(InvalidationEventHub hub)
    042    {
    043        hub.addInvalidationCallback(new Runnable()
    044        {
    045            public void run()
    046            {
    047                invalidate();
    048            }
    049        });
    050    }
    051
    052    /**
    053     * Wraps a computation with caching logic; once computed, the Invokable will return the same value, until
    054     * {@link #invalidate()} is invoked.
    055     *
    056     * @param invokable
    057     *         a computation to perform, whose results are cacheable until invalidated
    058     * @param <T>
    059     *         type of result
    060     * @return caching-enabled wrapper around invokable
    061     */
    062    public <T> Invokable<T> create(final Invokable<T> invokable)
    063    {
    064        return new Invokable<T>()
    065        {
    066            private volatile int localVersion = masterVersion;
    067
    068            private volatile T cachedResult;
    069
    070            public T invoke()
    071            {
    072                // Has the master version changed since the computation was last executed?
    073                if (localVersion != masterVersion)
    074                {
    075                    cachedResult = null;
    076                    localVersion = masterVersion;
    077                }
    078
    079                if (cachedResult == null)
    080                {
    081                    cachedResult = invokable.invoke();
    082                }
    083
    084
    085                return cachedResult;
    086            }
    087        };
    088    }
    089}