Author: henrib
Date: Fri Mar 10 14:39:20 2017
New Revision: 1786352

URL: http://svn.apache.org/viewvc?rev=1786352&view=rev
Log:
JEXL-216:
Use r/w lock on cache, busy-flag logic on parser (parser is not reentrant, 
create a temp one if busy)

Added:
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
   (with props)
Modified:
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
    
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java?rev=1786352&r1=1786351&r2=1786352&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/Engine.java
 Fri Mar 10 14:39:20 2017
@@ -43,11 +43,10 @@ import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 
-import java.lang.ref.SoftReference;
 import java.nio.charset.Charset;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -86,7 +85,12 @@ public class Engine extends JexlEngine {
      */
     protected final Log logger;
     /**
-     * The {@link Parser}; when parsing expressions, this engine synchronizes 
on the parser.
+     * The atomic parsing flag; true whilst parsing.
+     */
+    protected final AtomicBoolean parsing = new AtomicBoolean(false);
+    /**
+     * The {@link Parser}; when parsing expressions, this engine uses the 
parser if it
+     * is not already in use otherwise it will create a new temporary one.
      */
     protected final Parser parser = new Parser(new StringReader(";")); 
//$NON-NLS-1$
     /**
@@ -128,10 +132,6 @@ public class Engine extends JexlEngine {
      * The default jxlt engine.
      */
     protected volatile TemplateEngine jxlt = null;
-    /**
-     * The default cache load factor.
-     */
-    private static final float LOAD_FACTOR = 0.75f;
 
     /**
      * Creates an engine with default arguments.
@@ -214,6 +214,11 @@ public class Engine extends JexlEngine {
         return strict;
     }
 
+    //@Override
+    public boolean isCancellable() {
+        return this.cancellable;
+    }
+
     @Override
     public void setClassLoader(ClassLoader loader) {
         uberspect.setClassLoader(loader);
@@ -229,116 +234,10 @@ public class Engine extends JexlEngine {
         return new TemplateEngine(this, noScript, cacheSize, immediate, 
deferred);
     }
 
-    /**
-     * Swaps the current thread local context.
-     * @param tls the context or null
-     * @return the previous thread local context
-     */
-    protected JexlContext.ThreadLocal putThreadLocal(JexlContext.ThreadLocal 
tls) {
-        JexlContext.ThreadLocal local = CONTEXT.get();
-        CONTEXT.set(tls);
-        return local;
-    }
-
-    /**
-     * A soft referenced cache.
-     * <p>The actual cache is held through a soft reference, allowing it to be 
GCed under
-     * memory pressure.</p>
-     * @param <K> the cache key entry type
-     * @param <V> the cache key value type
-     */
-    protected class SoftCache<K, V> {
-        /**
-         * The cache size.
-         */
-        private final int size;
-        /**
-         * The soft reference to the cache map.
-         */
-        private SoftReference<Map<K, V>> ref = null;
-
-        /**
-         * Creates a new instance of a soft cache.
-         * @param theSize the cache size
-         */
-        SoftCache(int theSize) {
-            size = theSize;
-        }
-
-        /**
-         * Returns the cache size.
-         * @return the cache size
-         */
-        int size() {
-            return size;
-        }
-
-        /**
-         * Clears the cache.
-         */
-        void clear() {
-            ref = null;
-        }
-
-        /**
-         * Produces the cache entry set.
-         * @return the cache entry set
-         */
-        Set<Entry<K, V>> entrySet() {
-            Map<K, V> map = ref != null ? ref.get() : null;
-            return map != null ? map.entrySet() : Collections.<Entry<K, 
V>>emptySet();
-        }
-
-        /**
-         * Gets a value from cache.
-         * @param key the cache entry key
-         * @return the cache entry value
-         */
-        V get(K key) {
-            final Map<K, V> map = ref != null ? ref.get() : null;
-            return map != null ? map.get(key) : null;
-        }
-
-        /**
-         * Puts a value in cache.
-         * @param key    the cache entry key
-         * @param script the cache entry value
-         */
-        void put(K key, V script) {
-            Map<K, V> map = ref != null ? ref.get() : null;
-            if (map == null) {
-                map = createCache(size);
-                ref = new SoftReference<Map<K, V>>(map);
-            }
-            map.put(key, script);
-        }
-    }
-
-    /**
-     * Creates a cache.
-     * @param <K>       the key type
-     * @param <V>       the value type
-     * @param cacheSize the cache size, must be &gt; 0
-     * @return a Map usable as a cache bounded to the given size
-     */
-    protected <K, V> Map<K, V> createCache(final int cacheSize) {
-        return new java.util.LinkedHashMap<K, V>(cacheSize, LOAD_FACTOR, true) 
{
-            /** Serial version UID. */
-            private static final long serialVersionUID = 1L;
-
-            @Override
-            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
-                return size() > cacheSize;
-            }
-        };
-    }
-
     @Override
     public void clearCache() {
-        synchronized (parser) {
-            if (cache != null) {
-                cache.clear();
-            }
+        if (cache != null) {
+            cache.clear();
         }
     }
 
@@ -509,6 +408,17 @@ public class Engine extends JexlEngine {
     }
 
     /**
+     * Swaps the current thread local context.
+     * @param tls the context or null
+     * @return the previous thread local context
+     */
+    protected JexlContext.ThreadLocal putThreadLocal(JexlContext.ThreadLocal 
tls) {
+        JexlContext.ThreadLocal local = CONTEXT.get();
+        CONTEXT.set(tls);
+        return local;
+    }
+
+    /**
      * Gets the list of variables accessed by a script.
      * <p>This method will visit all nodes of a script and extract all 
variables whether they
      * are written in 'dot' or 'bracketed' notation. (a.b is equivalent to 
a['b']).</p>
@@ -666,22 +576,33 @@ public class Engine extends JexlEngine {
      */
     protected ASTJexlScript parse(JexlInfo info, String src, Scope scope, 
boolean registers, boolean expression) {
         final boolean cached = src.length() < cacheThreshold && cache != null;
-        ASTJexlScript script;
-        synchronized (parser) {
-            if (cached) {
-                script = cache.get(src);
-                if (script != null) {
-                    Scope f = script.getScope();
-                    if ((f == null && scope == null) || (f != null && 
f.equals(scope))) {
-                        return script;
-                    }
+        ASTJexlScript script = null;
+        if (cached) {
+            script = cache.get(src);
+            if (script != null) {
+                Scope f = script.getScope();
+                if ((f == null && scope == null) || (f != null && 
f.equals(scope))) {
+                    return script;
                 }
             }
-            final JexlInfo ninfo = info == null && debug? createInfo() : info;
-            script = parser.parse(ninfo, src, scope, registers, expression);
-            if (cached) {
-                cache.put(src, script);
+        }
+        final JexlInfo ninfo = info == null && debug ? createInfo() : info;
+        // if parser not in use...
+        if (parsing.compareAndSet(false, true)) {
+            try {
+                // lets parse
+                script = parser.parse(ninfo, src, scope, registers, 
expression);
+            } finally {
+                // no longer in use
+                parsing.set(false);
             }
+        } else {
+            // ...otherwise parser was in use, create a new temporary one
+            Parser lparser = new Parser(new StringReader(";"));
+            script = lparser.parse(ninfo, src, scope, registers, expression);
+        }
+        if (cached) {
+            cache.put(src, script);
         }
         return script;
     }

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java?rev=1786352&r1=1786351&r2=1786352&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java
 Fri Mar 10 14:39:20 2017
@@ -154,7 +154,7 @@ public abstract class InterpreterBase ex
                 return ocancellable.booleanValue();
             }
         }
-        return jexl.cancellable;
+        return jexl.isCancellable();
     }
 
     /**

Added: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java?rev=1786352&view=auto
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
 (added)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
 Fri Mar 10 14:39:20 2017
@@ -0,0 +1,211 @@
+/*
+ * 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.commons.jexl3.internal;
+
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * A soft referenced cache.
+ * <p>
+ * The actual cache is held through a soft reference, allowing it to be GCed
+ * under memory pressure.</p>
+ *
+ * @param <K> the cache key entry type
+ * @param <V> the cache key value type
+ */
+public class SoftCache<K, V> {
+    /**
+     * The default cache load factor.
+     */
+    private static final float LOAD_FACTOR = 0.75f;
+    /**
+     * The cache size.
+     */
+    private final int size;
+    /**
+     * The soft reference to the cache map.
+     */
+    private SoftReference<Map<K, V>> ref = null;
+    /**
+     * The cache r/w lock.
+     */
+    private final ReadWriteLock lock;
+
+    /**
+     * Creates a new instance of a soft cache.
+     *
+     * @param theSize the cache size
+     */
+    SoftCache(int theSize) {
+        size = theSize;
+        lock = new ReentrantReadWriteLock();
+    }
+
+    /**
+     * Returns the cache size.
+     *
+     * @return the cache size
+     */
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Clears the cache.
+     */
+    public void clear() {
+        lock.writeLock().lock();
+        try {
+            ref = null;
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Gets a value from cache.
+     *
+     * @param key the cache entry key
+     * @return the cache entry value
+     */
+    public V get(K key) {
+        lock.readLock().lock();
+        try {
+            final Map<K, V> map = ref != null ? ref.get() : null;
+            return map != null ? map.get(key) : null;
+        } finally {
+            lock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Puts a value in cache.
+     *
+     * @param key the cache entry key
+     * @param script the cache entry value
+     */
+    public void put(K key, V script) {
+        lock.writeLock().lock();
+        try {
+            Map<K, V> map = ref != null ? ref.get() : null;
+            if (map == null) {
+                map = createCache(size);
+                ref = new SoftReference<Map<K, V>>(map);
+            }
+            map.put(key, script);
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Produces the cache entry set.
+     * <p>
+     * For testing only, perform deep copy of cache entries
+     *
+     * @return the cache entry list
+     */
+    public List<Map.Entry<K, V>> entries() {
+        lock.readLock().lock();
+        try {
+            Map<K, V> map = ref != null ? ref.get() : null;
+            if (map == null) {
+                return Collections.emptyList();
+            }
+            final Set<Map.Entry<K, V>> set = map.entrySet();
+            final List<Map.Entry<K, V>> entries = new ArrayList<Map.Entry<K, 
V>>(set.size());
+            for (Map.Entry<K, V> e : set) {
+                entries.add(new SoftCacheEntry(e));
+            }
+            return entries;
+        } finally {
+            lock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Creates the cache store.
+     *
+     * @param <K> the key type
+     * @param <V> the value type
+     * @param cacheSize the cache size, must be &gt; 0
+     * @return a Map usable as a cache bounded to the given size
+     */
+    public <K, V> Map<K, V> createCache(final int cacheSize) {
+        return new java.util.LinkedHashMap<K, V>(cacheSize, LOAD_FACTOR, true) 
{
+            /**
+             * Serial version UID.
+             */
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+                return size() > cacheSize;
+            }
+        };
+    }
+}
+
+/**
+ * A soft cache entry.
+ *
+ * @param <K> key type
+ * @param <V> value type
+ */
+class SoftCacheEntry<K, V> implements Map.Entry<K, V> {
+    /**
+     * The entry key.
+     */
+    private final K key;
+    /**
+     * The entry value.
+     */
+    private final V value;
+
+    /**
+     * Creates an entry clone.
+     *
+     * @param e the entry to clone
+     */
+    SoftCacheEntry(Map.Entry<K, V> e) {
+        key = e.getKey();
+        value = e.getValue();
+    }
+
+    @Override
+    public K getKey() {
+        return key;
+    }
+
+    @Override
+    public V getValue() {
+        return value;
+    }
+
+    @Override
+    public V setValue(V value) {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+}
+

Propchange: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/SoftCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
URL: 
http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java?rev=1786352&r1=1786351&r2=1786352&view=diff
==============================================================================
--- 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
 (original)
+++ 
commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
 Fri Mar 10 14:39:20 2017
@@ -41,7 +41,7 @@ import java.util.Set;
  */
 public final class TemplateEngine extends JxltEngine {
     /** The TemplateExpression cache. */
-    private final Engine.SoftCache<String, TemplateExpression> cache;
+    private final SoftCache<String, TemplateExpression> cache;
     /** The JEXL engine instance. */
     private final Engine jexl;
     /** The first character for immediate expressions. */
@@ -61,7 +61,7 @@ public final class TemplateEngine extend
      */
     public TemplateEngine(Engine aJexl, boolean noScript, int cacheSize, char 
immediate, char deferred) {
         this.jexl = aJexl;
-        this.cache = aJexl.new SoftCache<String, 
TemplateExpression>(cacheSize);
+        this.cache = new SoftCache<String, TemplateExpression>(cacheSize);
         immediateChar = immediate;
         deferredChar = deferred;
         noscript = noScript;
@@ -662,12 +662,10 @@ public final class TemplateEngine extend
             if (cache == null) {
                 stmt = parseExpression(info, expression, null);
             } else {
-                synchronized (cache) {
-                    stmt = cache.get(expression);
-                    if (stmt == null) {
-                        stmt = parseExpression(info, expression, null);
-                        cache.put(expression, stmt);
-                    }
+                stmt = cache.get(expression);
+                if (stmt == null) {
+                    stmt = parseExpression(info, expression, null);
+                    cache.put(expression, stmt);
                 }
             }
         } catch (JexlException xjexl) {


Reply via email to