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    *      https://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.Collection;
21  import java.util.Collections;
22  import java.util.Map;
23  
24  import org.apache.commons.jexl3.JexlCache;
25  
26  /**
27   * A soft referenced cache.
28   * <p>
29   *   The actual cache is held through a soft reference, allowing it to be GCed
30   *   under memory pressure.
31   * </p>
32   * <p>
33   *   Note that the underlying map is a synchronized LinkedHashMap.
34   *   The reason is that a get() will reorder elements (the LRU queue) and thus
35   *   needs synchronization to ensure thread-safety.
36   * </p>
37   * <p>
38   *   When caching JEXL scripts or expressions, one should expect the execution cost of those
39   *   to be several fold the cost of the cache handling; after some (synthetic) tests, measures indicate
40   *   cache handling is a marginal latency factor.
41   * </p>
42   *
43   * @param <K> the cache key entry type
44   * @param <V> the cache key value type
45   */
46  public class SoftCache<K, V> implements JexlCache<K, V> {
47      /**
48       * The default cache load factor.
49       */
50      protected static final float LOAD_FACTOR = 0.75f;
51      /**
52       * Creates a synchronized LinkedHashMap.
53       * @param capacity the map capacity
54       * @return the map instance
55       * @param <K> key type
56       * @param <V> value type
57       */
58      public static <K, V> Map<K, V> createSynchronizedLinkedHashMap(final int capacity) {
59          return Collections.synchronizedMap(new java.util.LinkedHashMap<K, V>(capacity, LOAD_FACTOR, true) {
60              /**
61               * Serial version UID.
62               */
63              private static final long serialVersionUID = 1L;
64  
65              @Override
66              protected boolean removeEldestEntry(final Map.Entry<K, V> eldest) {
67                  return super.size() > capacity;
68              }
69          });
70      }
71      /**
72       * The cache capacity.
73       */
74      protected final int capacity;
75  
76      /**
77       * The soft reference to the cache map.
78       */
79      protected volatile SoftReference<Map<K, V>> reference;
80  
81      /**
82       * Creates a new instance of a soft cache.
83       *
84       * @param theSize the cache size
85       */
86      public SoftCache(final int theSize) {
87          capacity = theSize;
88      }
89  
90      /**
91       * {@inheritDoc}
92       */
93      @Override
94      public int capacity() {
95          return capacity;
96      }
97  
98      /**
99       * {@inheritDoc}
100      */
101     @Override
102     public void clear() {
103         final SoftReference<Map<K, V>> ref = reference;
104         if (ref != null) {
105             reference = null;
106             final Map<K, V> map = ref.get();
107             if (map != null) {
108                 map.clear();
109             }
110         }
111     }
112 
113     /**
114      * Creates a cache store.
115      *
116      * @param <KT> the key type
117      * @param <VT> the value type
118      * @param cacheSize the cache size, must be &gt; 0
119      * @return a Map usable as a cache bounded to the given size
120      */
121     protected <KT, VT> Map<KT, VT> createMap(final int cacheSize) {
122         return createSynchronizedLinkedHashMap(cacheSize);
123     }
124 
125     /**
126      * {@inheritDoc}
127      */
128     @Override
129     public Collection<Map.Entry<K, V>> entries() {
130         final SoftReference<Map<K, V>> ref = reference;
131         final Map<K, V> map = ref != null ? ref.get() : null;
132         return map == null? Collections.emptyList() : map.entrySet();
133     }
134 
135     /**
136      * {@inheritDoc}
137      */
138     @Override
139     public V get(final K key) {
140         final SoftReference<Map<K, V>> ref = reference;
141         final Map<K, V> map = ref != null ? ref.get() : null;
142         return map != null ? map.get(key) : null;
143     }
144 
145     /**
146      * {@inheritDoc}
147      */
148     @Override
149     public V put(final K key, final V script) {
150         SoftReference<Map<K, V>> ref = reference;
151         Map<K, V> map = ref != null ? ref.get() : null;
152         if (map == null) {
153             synchronized (this) {
154                 ref = reference;
155                 map = ref != null ? ref.get() : null;
156                 if (map == null) {
157                     map = createMap(capacity);
158                     reference = new SoftReference<>(map);
159                 }
160             }
161         }
162         return map.put(key, script);
163     }
164 
165     /**
166      * {@inheritDoc}
167      */
168     @Override
169     public int size() {
170         final SoftReference<Map<K, V>> ref = reference;
171         final Map<K, V> map = ref != null ? ref.get() : null;
172         return map != null ? map.size() : 0;
173     }
174 }
175