You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2017/03/14 14:05:16 UTC

svn commit: r1786904 - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java test/java/org/apache/commons/jexl3/PropertyAccessTest.java

Author: henrib
Date: Tue Mar 14 14:05:16 2017
New Revision: 1786904

URL: http://svn.apache.org/viewvc?rev=1786904&view=rev
Log:
JEXL-222:
Improved IndexedType, allow caching of last set/get, expose container class & name

Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java?rev=1786904&r1=1786903&r2=1786904&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl3/internal/introspection/IndexedType.java Tue Mar 14 14:05:16 2017
@@ -24,18 +24,23 @@ import java.beans.IntrospectionException
 
 /**
  * Abstract an indexed property container.
- * This stores the container name and owning class as well as the list of available getter and setter methods.
+ * <p>This allows getting properties from expressions like <code>var.container.property</code>.
+ * This stores the container name and class as well as the list of available getter and setter methods.
  * It implements JexlPropertyGet since such a container can only be accessed from its owning instance (not set).
  */
 public final class IndexedType implements JexlPropertyGet {
-    /** The container name. */
+     /** The container name. */
     private final String container;
-    /** The owning class. */
+    /** The container class. */
     private final Class<?> clazz;
     /** The array of getter methods. */
     private final Method[] getters;
+    /** Last get method used. */
+    private volatile Method get = null;
     /** The array of setter methods. */
     private final Method[] setters;
+    /** Last set method used. */
+    private volatile Method set = null;
 
     /**
      * Attempts to find an indexed-property getter in an object.
@@ -63,50 +68,66 @@ public final class IndexedType implement
     }
 
     /**
-     * A generic indexed property container, exposes get(key) and set(key, value) and solves method call dynamically
-     * based on arguments.
+     * A generic indexed property container, exposes get(key) and set(key, value)
+     * and solves method call dynamically based on arguments.
      * <p>Must remain public for introspection purpose.</p>
      */
     public static final class IndexedContainer {
-        /** The instance owning the container. */
-        private final Object object;
+        /** The container instance. */
+        private final Object container;
         /** The container type instance. */
         private final IndexedType type;
 
         /**
          * Creates a new duck container.
          * @param theType the container type
-         * @param theObject the instance owning the container
+         * @param theContainer the container instance
          */
-        private IndexedContainer(IndexedType theType, Object theObject) {
+        private IndexedContainer(IndexedType theType, Object theContainer) {
             this.type = theType;
-            this.object = theObject;
+            this.container = theContainer;
+        }
+        
+        /**
+         * Gets the property container name.
+         * @return the container name
+         */
+        public String getContainerName() {
+            return type.container;
         }
 
         /**
-         * Gets a property from a container.
+         * Gets the property container class.
+         * @return the container class
+         */
+        public Class<?> getContainerClass() {
+            return type.clazz;
+        }
+        
+        /**
+         * Gets a property from this indexed container.
          * @param key the property key
          * @return the property value
          * @throws Exception if inner invocation fails
          */
         public Object get(Object key) throws Exception {
-            return type.invokeGet(object, key);
+            return type.invokeGet(container, key);
         }
 
         /**
-         * Sets a property in a container.
+         * Sets a property in this indexed container.
          * @param key the property key
          * @param value the property value
          * @return the invocation result (frequently null)
          * @throws Exception if inner invocation fails
          */
         public Object set(Object key, Object value) throws Exception {
-            return type.invokeSet(object, key, value);
+            return type.invokeSet(container, key, value);
         }
     }
 
     /**
-     * Creates a new indexed type.
+     * Creates a new indexed property container type.
      * @param name the container name
      * @param c the owning class
      * @param gets the array of getter methods
@@ -130,7 +151,9 @@ public final class IndexedType implement
 
     @Override
     public Object tryInvoke(Object obj, Object key) {
-        if (obj != null && key != null && clazz.equals(obj.getClass()) && container.equals(key.toString())) {
+        if (obj != null && key != null
+            && clazz.equals(obj.getClass())
+            && container.equals(key.toString())) {
             return new IndexedContainer(this, obj);
         } else {
             return Uberspect.TRY_FAILED;
@@ -149,49 +172,69 @@ public final class IndexedType implement
 
     /**
      * Gets the value of a property from a container.
-     * @param object the instance owning the container (not null)
+     * @param object the container instance (not null)
      * @param key the property key (not null)
      * @return the property value
-     * @throws Exception if invocation failed; IntrospectionException if a property getter could not be found
+     * @throws Exception if invocation failed;
+     *         IntrospectionException if a property getter could not be found
      */
     private Object invokeGet(Object object, Object key) throws Exception {
-        if (getters != null) {
-            final Object[] args = {key};
-            final Method jm;
-            if (getters.length == 1) {
-                jm = getters[0];
-            } else {
-                jm = new MethodKey(getters[0].getName(), args).getMostSpecificMethod(getters);
+        if (getters != null && getters.length > 0) {
+            Method jm = get;
+            if (jm != null) {
+                final Class<?>[] ptypes = jm.getParameterTypes();
+                if (ptypes[0].isAssignableFrom(key.getClass())) {
+                    return jm.invoke(object, key);
+                }
             }
+            final Object[] args = {key};
+            final String mname = getters[0].getName();
+            final MethodKey km = new MethodKey(mname, args);
+            jm = km.getMostSpecificMethod(getters);
             if (jm != null) {
-                return jm.invoke(object, args);
+                Object invoked = jm.invoke(object, args);
+                get = jm;
+                return invoked;
             }
         }
-        throw new IntrospectionException("property get error: " + object.getClass().toString() + "@" + key.toString());
+        throw new IntrospectionException("property get error: "
+                + object.getClass().toString()
+                + "@" + key.toString());
     }
 
     /**
      * Sets the value of a property in a container.
-     * @param object the instance owning the container (not null)
+     * @param object the container instance (not null)
      * @param key the property key (not null)
      * @param value the property value (not null)
      * @return the result of the method invocation (frequently null)
-     * @throws Exception if invocation failed; IntrospectionException if a property setter could not be found
+     * @throws Exception if invocation failed;
+     *         IntrospectionException if a property setter could not be found
      */
     private Object invokeSet(Object object, Object key, Object value) throws Exception {
-        if (setters != null) {
-            final Object[] args = {key, value};
-            final Method jm;
-            if (setters.length == 1) {
-                jm = setters[0];
-            } else {
-                jm = new MethodKey(setters[0].getName(), args).getMostSpecificMethod(setters);
+        if (setters != null && setters.length > 0) {
+            Method jm = set;
+            if (jm != null) {
+                final Class<?>[] ptypes = jm.getParameterTypes();
+                if (ptypes[0].isAssignableFrom(key.getClass())
+                    && (value == null
+                        || ptypes[1].isAssignableFrom(value.getClass()))) {
+                    return jm.invoke(object, key, value);
+                }
             }
+            final Object[] args = {key, value};
+            final String mname = setters[0].getName();
+            final MethodKey km = new MethodKey(mname, args);
+            jm = km.getMostSpecificMethod(setters);
             if (jm != null) {
-                return jm.invoke(object, args);
+                Object invoked = jm.invoke(object, args);
+                set = jm;
+                return invoked;
             }
         }
-        throw new IntrospectionException("property set error: " + object.getClass().toString() + "@" + key.toString());
+        throw new IntrospectionException("property set error: "
+                + object.getClass().toString()
+                + "@" + key.toString());
     }
 
 }

Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java?rev=1786904&r1=1786903&r2=1786904&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java (original)
+++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl3/PropertyAccessTest.java Tue Mar 14 14:05:16 2017
@@ -20,6 +20,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.commons.jexl3.internal.Debugger;
+import org.apache.commons.jexl3.internal.introspection.IndexedType;
 import org.apache.commons.jexl3.junit.Asserter;
 import org.junit.Assert;
 import org.junit.Before;
@@ -45,7 +46,6 @@ public class PropertyAccessTest extends
         asserter = new Asserter(JEXL);
     }
 
-
     @Test public void testPropertyProperty() throws Exception {
         Integer i42 = Integer.valueOf(42);
         Integer i43 = Integer.valueOf(43);
@@ -70,16 +70,19 @@ public class PropertyAccessTest extends
             asserter.assertExpression("foo.0.'1'", i42);
         }
     }
-
-    public static class Container {
+    
+    /**
+     * A base property container; can only set from string.
+     */
+    public static class PropertyContainer {
         String value0;
         int value1;
-
-        public Container(String name, int number) {
+        
+        public PropertyContainer(String name, int number) {
             value0 = name;
             value1 = number;
         }
-
+        
         public Object getProperty(String name) {
             if ("name".equals(name)) {
                 return value0;
@@ -89,6 +92,91 @@ public class PropertyAccessTest extends
                 return null;
             }
         }
+        
+        public void setProperty(String name, String value) {
+            if ("name".equals(name)) {
+                this.value0 = value.toUpperCase();
+            }
+            if ("number".equals(name)) {
+                this.value1 = Integer.parseInt(value) + 1000;
+            }
+        }
+    }
+
+    
+    /**
+     * Overloads propertySet.
+     */
+    public static class PropertyArithmetic extends JexlArithmetic {
+        int ncalls = 0;
+        
+        public PropertyArithmetic(boolean astrict) {
+            super(astrict);
+        }
+
+        public Object propertySet(IndexedType.IndexedContainer map, String key, Integer value) {
+            if (map.getContainerClass().equals(PropertyContainer.class)
+                && map.getContainerName().equals("property")) {
+                try {
+                    map.set(key, value.toString());
+                    ncalls += 1;
+                } catch (Exception xany) {
+                    throw new JexlException.Operator(null, key + "." + value.toString(), xany);
+                }
+                return null;
+            }
+            return JexlEngine.TRY_FAILED;
+        }
+        
+        public int getCalls() {
+            return ncalls;
+        }
+    }
+
+    @Test public void testInnerViaArithmetic() throws Exception {
+        PropertyArithmetic pa = new PropertyArithmetic(true);
+        JexlEngine jexl = new JexlBuilder().arithmetic(pa).debug(true).strict(true).cache(32).create();
+        PropertyContainer quux = new PropertyContainer("bar", 169);
+        Object result;
+
+        JexlScript getName = jexl.createScript("foo.property.name", "foo");
+        result = getName.execute(null, quux);
+        Assert.assertEquals("bar", result);
+        int calls = pa.getCalls();
+        JexlScript setName = jexl.createScript("foo.property.name = $0", "foo", "$0");
+        setName.execute(null, quux, 123);
+        result = getName.execute(null, quux);
+        Assert.assertEquals("123", result);
+        setName.execute(null, quux, 456);
+        result = getName.execute(null, quux);
+        Assert.assertEquals("456", result);
+        Assert.assertEquals(calls + 2, pa.getCalls());
+        setName.execute(null, quux, "quux");
+        result = getName.execute(null, quux);
+        Assert.assertEquals("QUUX", result);
+        Assert.assertEquals(calls + 2, pa.getCalls());
+        
+        JexlScript getNumber = jexl.createScript("foo.property.number", "foo");
+        result = getNumber.execute(null, quux);
+        Assert.assertEquals(169, result);
+        JexlScript setNumber = jexl.createScript("foo.property.number = $0", "foo", "$0");
+        setNumber.execute(null, quux, 42);
+        result = getNumber.execute(null, quux);
+        Assert.assertEquals(1042, result);
+        setNumber.execute(null, quux, 24);
+        result = getNumber.execute(null, quux);
+        Assert.assertEquals(1024, result);
+        Assert.assertEquals(calls + 4, pa.getCalls());
+        setNumber.execute(null, quux, "42");
+        result = getNumber.execute(null, quux);
+        Assert.assertEquals(1042, result);
+        Assert.assertEquals(calls + 4, pa.getCalls());
+    }
+    
+    public static class Container extends PropertyContainer {
+        public Container(String name, int number) {
+            super(name, number);
+        }
 
         public Object getProperty(int ref) {
             if (0 == ref) {
@@ -126,10 +214,13 @@ public class PropertyAccessTest extends
     }
 
     @Test public void testInnerProperty() throws Exception {
+        PropertyArithmetic pa = new PropertyArithmetic(true);
+        JexlEngine jexl = new JexlBuilder().arithmetic(pa).debug(true).strict(true).cache(32).create();
         Container quux = new Container("quux", 42);
         JexlScript get;
         Object result;
 
+        int calls = pa.getCalls();
         JexlScript getName = JEXL.createScript("foo.property.name", "foo");
         result = getName.execute(null, quux);
         Assert.assertEquals("quux", result);
@@ -173,6 +264,8 @@ public class PropertyAccessTest extends
         Assert.assertEquals(24, result);
         result = get1.execute(null, quux);
         Assert.assertEquals(24, result);
+        
+        Assert.assertEquals(calls, pa.getCalls());
     }