001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2012-2012 Pentaho and others
008// All Rights Reserved.
009*/
010package mondrian.util;
011
012import mondrian.olap.Util;
013
014import java.lang.ref.*;
015import java.util.*;
016import java.util.concurrent.TimeUnit;
017
018/**
019 * An expiring reference is a subclass of {@link SoftReference}
020 * which pins the reference in memory until a certain timeout
021 * is reached. After that, the reference is free to be garbage
022 * collected if needed.
023 *
024 * <p>The timeout value must be provided as a String representing
025 * both the time value and the time unit. For example, 1 second is
026 * represented as "1s". Valid time units are [d, h, m, s, ms],
027 * representing respectively days, hours, minutes, seconds and
028 * milliseconds.
029 */
030public class ExpiringReference<T> extends SoftReference<T> {
031    T hardRef;
032    long expiry = Long.MIN_VALUE;
033
034    /**
035     * A Timer object to execute what we need to do.
036     */
037    private static final Timer timer =
038        new Timer(
039            "mondrian.util.ExpiringReference$timer",
040            true);
041
042    /**
043     * Creates an expiring reference.
044     * @param ref The referent.
045     * @param timeout The timeout to enforce, in minutes.
046     * If timeout is equal or less than 0, this means a hard reference.
047     */
048    public ExpiringReference(T ref, String timeout) {
049        super(ref);
050        setTimer(ref, timeout);
051    }
052
053    private synchronized void setTimer(T referent, String timeoutString) {
054        Pair<Long, TimeUnit> pair =
055            Util.parseInterval(timeoutString, null);
056        final long timeout = pair.right.toMillis(pair.left);
057
058        if (timeout == Long.MIN_VALUE
059            && expiry != Long.MIN_VALUE)
060        {
061            // Reference was accessed through get().
062            // Don't reset the expiry if it is active.
063            return;
064        }
065
066        if (timeout == 0) {
067            // Permanent ref mode.
068            expiry = Long.MAX_VALUE;
069            // Set the reference
070            this.hardRef = referent;
071            return;
072        }
073
074        if (timeout > 0) {
075            // A timeout must be enforced.
076            long newExpiry =
077                System.currentTimeMillis() + timeout;
078
079            if (newExpiry > expiry) {
080                expiry = newExpiry;
081            }
082
083            // Set the reference
084            this.hardRef = referent;
085
086            // Schedule for cleanup.
087            timer.schedule(
088                getTask(),
089                timeout + 10);
090
091            // Notice the 1001 value above. This to make sure that when
092            // the timer fires, the cleanup takes place. Else, undefined
093            // behavior happens.
094            return;
095        }
096
097        // Timeout is < 0. Act as a regular soft ref.
098        this.hardRef = null;
099    }
100
101    TimerTask getTask() {
102        return new TimerTask() {
103            public void run() {
104                if (expiry <= System.currentTimeMillis()) {
105                    hardRef = null;
106                }
107            }
108        };
109    }
110
111    public synchronized T get() {
112        return get(Long.MIN_VALUE + "ms");
113    }
114
115    public synchronized T get(String timeout) {
116        final T weakRef = super.get();
117
118        if (weakRef != null) {
119            // This object is still alive but was cleaned.
120            // set a new TimerTask.
121            setTimer(weakRef, timeout);
122        }
123
124        return weakRef;
125    }
126}
127// End ExpiringReference.java