View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import java.lang.ref.SoftReference;
20  import java.util.ArrayList;
21  import java.util.Collections;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  import java.util.concurrent.locks.ReadWriteLock;
26  import java.util.concurrent.locks.ReentrantReadWriteLock;
27  
28  /**
29   * A soft referenced cache.
30   * <p>
31   * The actual cache is held through a soft reference, allowing it to be GCed
32   * under memory pressure.</p>
33   *
34   * @param <K> the cache key entry type
35   * @param <V> the cache key value type
36   */
37  public class SoftCache<K, V> {
38      /**
39       * The default cache load factor.
40       */
41      private static final float LOAD_FACTOR = 0.75f;
42      /**
43       * The cache size.
44       */
45      private final int size;
46      /**
47       * The soft reference to the cache map.
48       */
49      private SoftReference<Map<K, V>> ref = null;
50      /**
51       * The cache r/w lock.
52       */
53      private final ReadWriteLock lock;
54  
55      /**
56       * Creates a new instance of a soft cache.
57       *
58       * @param theSize the cache size
59       */
60      SoftCache(final int theSize) {
61          size = theSize;
62          lock = new ReentrantReadWriteLock();
63      }
64  
65      /**
66       * Returns the cache size.
67       *
68       * @return the cache size
69       */
70      public int size() {
71          return size;
72      }
73  
74      /**
75       * Clears the cache.
76       */
77      public void clear() {
78          lock.writeLock().lock();
79          try {
80              ref = null;
81          } finally {
82              lock.writeLock().unlock();
83          }
84      }
85  
86      /**
87       * Gets a value from cache.
88       *
89       * @param key the cache entry key
90       * @return the cache entry value
91       */
92      public V get(final K key) {
93          lock.readLock().lock();
94          try {
95              final Map<K, V> map = ref != null ? ref.get() : null;
96              return map != null ? map.get(key) : null;
97          } finally {
98              lock.readLock().unlock();
99          }
100     }
101 
102     /**
103      * Puts a value in cache.
104      *
105      * @param key the cache entry key
106      * @param script the cache entry value
107      */
108     public void put(final K key, final V script) {
109         lock.writeLock().lock();
110         try {
111             Map<K, V> map = ref != null ? ref.get() : null;
112             if (map == null) {
113                 map = createCache(size);
114                 ref = new SoftReference<>(map);
115             }
116             map.put(key, script);
117         } finally {
118             lock.writeLock().unlock();
119         }
120     }
121 
122     /**
123      * Produces the cache entry set.
124      * <p>
125      * For testing only, perform deep copy of cache entries
126      *
127      * @return the cache entry list
128      */
129     public List<Map.Entry<K, V>> entries() {
130         lock.readLock().lock();
131         try {
132             final Map<K, V> map = ref != null ? ref.get() : null;
133             if (map == null) {
134                 return Collections.emptyList();
135             }
136             final Set<Map.Entry<K, V>> set = map.entrySet();
137             final List<Map.Entry<K, V>> entries = new ArrayList<>(set.size());
138             for (final Map.Entry<K, V> e : set) {
139                 entries.add(new SoftCacheEntry<>(e));
140             }
141             return entries;
142         } finally {
143             lock.readLock().unlock();
144         }
145     }
146 
147     /**
148      * Creates the cache store.
149      *
150      * @param <K> the key type
151      * @param <V> the value type
152      * @param cacheSize the cache size, must be &gt; 0
153      * @return a Map usable as a cache bounded to the given size
154      */
155     public <K, V> Map<K, V> createCache(final int cacheSize) {
156         return new java.util.LinkedHashMap<K, V>(cacheSize, LOAD_FACTOR, true) {
157             /**
158              * Serial version UID.
159              */
160             private static final long serialVersionUID = 1L;
161 
162             @Override
163             protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
164                 return super.size() > cacheSize;
165             }
166         };
167     }
168 }
169 
170 /**
171  * A soft cache entry.
172  *
173  * @param <K> key type
174  * @param <V> value type
175  */
176 class SoftCacheEntry<K, V> implements Map.Entry<K, V> {
177     /**
178      * The entry key.
179      */
180     private final K key;
181     /**
182      * The entry value.
183      */
184     private final V value;
185 
186     /**
187      * Creates an entry clone.
188      *
189      * @param e the entry to clone
190      */
191     SoftCacheEntry(final Map.Entry<K, V> e) {
192         key = e.getKey();
193         value = e.getValue();
194     }
195 
196     @Override
197     public K getKey() {
198         return key;
199     }
200 
201     @Override
202     public V getValue() {
203         return value;
204     }
205 
206     @Override
207     public V setValue(final V v) {
208         throw new UnsupportedOperationException("Not supported.");
209     }
210 }
211