You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2018/01/07 12:07:05 UTC

incubator-freemarker git commit: Added new property to BeansWrapper.MethodAppearanceDecision: replaceExistingProperty. This is useful when a method like size() is exposed as a JavaBean property via MethodAppearanceDecision.exposeAsProperty, but there's a

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3-gae 2f24b4a3a -> b349362cf


Added new property to BeansWrapper.MethodAppearanceDecision: replaceExistingProperty. This is useful when a method like size() is exposed as a JavaBean property via MethodAppearanceDecision.exposeAsProperty, but there's also a real JavaBean property (like getSize()) with identical name. By default the real property isn't replaced, but now with replaceExistingProperty it can be.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b349362c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b349362c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b349362c

Branch: refs/heads/2.3-gae
Commit: b349362cf07d4521e7d3a6ac665036a387ed8ad8
Parents: 2f24b4a
Author: ddekany <dd...@apache.org>
Authored: Sun Jan 7 13:06:45 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Jan 7 13:06:45 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/ext/beans/BeansWrapper.java | 49 +++++++++++-
 .../freemarker/ext/beans/ClassIntrospector.java |  4 +-
 .../ext/beans/MethodAppearanceFineTuner.java    | 13 +++-
 src/manual/en_US/book.xml                       | 40 ++++++++++
 .../ext/beans/FineTuneMethodAppearanceTest.java | 78 +++++++++++++++++++-
 5 files changed, 177 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b349362c/src/main/java/freemarker/ext/beans/BeansWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index 3dbb3e1..4b1ec6c 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -1766,35 +1766,82 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      */
     static public final class MethodAppearanceDecision {
         private PropertyDescriptor exposeAsProperty;
+        private boolean replaceExistingProperty;
         private String exposeMethodAs;
         private boolean methodShadowsProperty;
         
         void setDefaults(Method m) {
             exposeAsProperty = null;
+            replaceExistingProperty = false;
             exposeMethodAs = m.getName();
             methodShadowsProperty = true;
         }
         
+        /**
+         * See in the documentation of {@link MethodAppearanceFineTuner#process}. 
+         */
         public PropertyDescriptor getExposeAsProperty() {
             return exposeAsProperty;
         }
         
+        /**
+         * See in the documentation of {@link MethodAppearanceFineTuner#process}.
+         * Note that you may also want to call
+         * {@link #setMethodShadowsProperty(boolean) setMethodShadowsProperty(false)} when you call this. 
+         */
         public void setExposeAsProperty(PropertyDescriptor exposeAsProperty) {
             this.exposeAsProperty = exposeAsProperty;
         }
-        
+
+        /**
+         * Getter pair of {@link #setReplaceExistingProperty(boolean)}.
+         * 
+         * @since 2.3.28
+         */
+        public boolean getReplaceExistingProperty() {
+            return replaceExistingProperty;
+        }
+
+        /**
+         * If {@link #getExposeAsProperty()} is non-{@code null}, and a {@link PropertyDescriptor} with the same
+         * property name was already added to the class introspection data, this decides if that will be replaced
+         * with the {@link PropertyDescriptor} returned by {@link #getExposeAsProperty()}. The default is {@code false},
+         * that is, the old {@link PropertyDescriptor} is kept, and the new one is ignored.
+         * JavaBean properties discovered with the standard (non-{@link MethodAppearanceFineTuner}) mechanism
+         * are added before those created by the {@link MethodAppearanceFineTuner}, so with this you can decide if a
+         * real JavaBeans property can be replaced by the "fake" one created with
+         * {@link #setExposeAsProperty(PropertyDescriptor)}.
+         * 
+         * @since 2.3.28
+         */
+        public void setReplaceExistingProperty(boolean overrideExistingProperty) {
+            this.replaceExistingProperty = overrideExistingProperty;
+        }
+
+        /**
+         * See in the documentation of {@link MethodAppearanceFineTuner#process}. 
+         */
         public String getExposeMethodAs() {
             return exposeMethodAs;
         }
         
+        /**
+         * See in the documentation of {@link MethodAppearanceFineTuner#process}. 
+         */
         public void setExposeMethodAs(String exposeAsMethod) {
             this.exposeMethodAs = exposeAsMethod;
         }
         
+        /**
+         * See in the documentation of {@link MethodAppearanceFineTuner#process}. 
+         */
         public boolean getMethodShadowsProperty() {
             return methodShadowsProperty;
         }
         
+        /**
+         * See in the documentation of {@link MethodAppearanceFineTuner#process}. 
+         */
         public void setMethodShadowsProperty(boolean shadowEarlierProperty) {
             this.methodShadowsProperty = shadowEarlierProperty;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b349362c/src/main/java/freemarker/ext/beans/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
index ce8bbd3..3b8f6ce 100644
--- a/src/main/java/freemarker/ext/beans/ClassIntrospector.java
+++ b/src/main/java/freemarker/ext/beans/ClassIntrospector.java
@@ -349,7 +349,9 @@ class ClassIntrospector {
                     }
 
                     PropertyDescriptor propDesc = decision.getExposeAsProperty();
-                    if (propDesc != null && !(introspData.get(propDesc.getName()) instanceof FastPropertyDescriptor)) {
+                    if (propDesc != null &&
+                            (decision.getReplaceExistingProperty()
+                                    || !(introspData.get(propDesc.getName()) instanceof FastPropertyDescriptor))) {
                         addPropertyDescriptorToClassIntrospectionData(
                                 introspData, propDesc, clazz, accessibleMethods);
                     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b349362c/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java b/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
index 6b80893..8dd134a 100644
--- a/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
+++ b/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java
@@ -55,7 +55,11 @@ public interface MethodAppearanceFineTuner {
      *     {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}.
      *     For example, if you have <tt>int size()</tt> in a class, but you
      *     want it to be accessed from the templates as <tt>obj.size</tt>,
-     *     rather than as <tt>obj.size()</tt>, you can do that with this.
+     *     rather than as <tt>obj.size()</tt>, you can do that with this
+     *     (but remember calling
+     *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)
+     *     setMethodShadowsProperty(false)} as well, if the method name is exactly
+     *     the same as the property name).
      *     The default is {@code null}, which means that no fake property is
      *     created for the method. You need not and shouldn't set this
      *     to non-<tt>null</tt> for the getter methods of real JavaBean
@@ -65,9 +69,10 @@ public interface MethodAppearanceFineTuner {
      *     is given as the <tt>clazz</tt> parameter or it must be inherited from
      *     that class, or else whatever errors can occur later.
      *     {@link IndexedPropertyDescriptor}-s are supported.
-     *     If a real JavaBean property of the same name exists, it won't be
-     *     replaced by the fake one. Also if a fake property of the same name
-     *     was assigned earlier, it won't be replaced.
+     *     If a real JavaBean property of the same name exists, or a fake property
+     *     of the same name was already assigned earlier, it won't be
+     *     replaced by the new one by default, however this can be changed with
+     *     {@link MethodAppearanceDecision#setReplaceExistingProperty(boolean)}.
      *   <li>Prevent the method to hide a JavaBean property (fake or real) of
      *     the same name by calling
      *     {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b349362c/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 769b7b1..fd6956c 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -27070,6 +27070,46 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
     <appendix xml:id="app_versions">
       <title>Version history</title>
 
+      <section xml:id="versions_2_3_28">
+        <title>2.3.28 (incubating at Apache)</title>
+
+        <para>Release date: [FIXME]</para>
+
+        <para><emphasis role="bold">This is a stable, final
+        release.</emphasis> The <quote>incubating</quote> suffix is required
+        by the Apache Software Foundation until the project becomes a fully
+        accepted (graduated) Apache project.</para>
+
+        <section>
+          <title>Changes on the FTL side</title>
+
+          <itemizedlist>
+            <listitem>
+              <para>[FIXME]</para>
+            </listitem>
+          </itemizedlist>
+        </section>
+
+        <section>
+          <title>Changes on the Java side</title>
+
+          <itemizedlist>
+            <listitem>
+              <para>Added new property to
+              <literal>BeansWrapper.MethodAppearanceDecision</literal>:
+              <literal>replaceExistingProperty</literal>. This is useful when
+              a method like <literal>size()</literal> is exposed as a JavaBean
+              property via
+              <literal>MethodAppearanceDecision.exposeAsProperty</literal>,
+              but there's also a <quote>real</quote> JavaBean property (like
+              <literal>getSize()</literal>) with identical name. By default
+              the real property isn't replaced, but now with
+              <literal>replaceExistingProperty</literal> it can be.</para>
+            </listitem>
+          </itemizedlist>
+        </section>
+      </section>
+
       <section xml:id="versions_2_3_27">
         <title>2.3.27 (incubating at Apache)</title>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b349362c/src/test/java/freemarker/ext/beans/FineTuneMethodAppearanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/ext/beans/FineTuneMethodAppearanceTest.java b/src/test/java/freemarker/ext/beans/FineTuneMethodAppearanceTest.java
index fa663a4..29eaa23 100644
--- a/src/test/java/freemarker/ext/beans/FineTuneMethodAppearanceTest.java
+++ b/src/test/java/freemarker/ext/beans/FineTuneMethodAppearanceTest.java
@@ -21,12 +21,17 @@ package freemarker.ext.beans;
 
 import static org.junit.Assert.*;
 
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
 import java.lang.reflect.Method;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecision;
+import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecisionInput;
+import freemarker.template.Configuration;
 import freemarker.template.DefaultObjectWrapper;
 import freemarker.template.TemplateHashModel;
 import freemarker.template.TemplateMethodModelEx;
@@ -70,7 +75,30 @@ public class FineTuneMethodAppearanceTest {
         assertEquals("getV3()", ((TemplateScalarModel) thm.get("v3")).getAsString());
         assertTrue(thm.get("getV3") instanceof TemplateMethodModelEx);
     }
-    
+
+    @Test
+    public void existingPropertyReplacement() throws TemplateModelException {
+        for (Boolean replaceExistingProperty : new Boolean[] { null, false }) {
+            // The "real" property wins, no mater what:
+            assertSSubvariableValue(replaceExistingProperty, true, "from getS()");
+            assertSSubvariableValue(replaceExistingProperty, false, "from getS()");
+        }
+        
+        // replaceExistingProperty = true; the "real" property can be overridden:
+        assertSSubvariableValue(true, true, "from getS()");
+        assertSSubvariableValue(true, false, "from s()");
+    }
+
+    private void assertSSubvariableValue(Boolean replaceExistingProperty, boolean preferGetS, String expectedValue)
+            throws TemplateModelException {
+        DefaultObjectWrapper ow = new DefaultObjectWrapper(Configuration.VERSION_2_3_27);
+        ow.setMethodAppearanceFineTuner(
+                new PropertyReplacementMethodAppearanceFineTuner(replaceExistingProperty, preferGetS));
+        assertEquals(expectedValue,
+                ((TemplateScalarModel) ((TemplateHashModel) ow.wrap(new PropertyReplacementTestBean())).get("s"))
+                .getAsString());
+    }
+
     static public class C {
         
         public String v1 = "v1";
@@ -98,4 +126,52 @@ public class FineTuneMethodAppearanceTest {
     }
     
     static class DefaultObjectWrapperOverrideExt extends DefaultObjectWrapperOverride { }
+    
+    static public class PropertyReplacementTestBean {
+        
+        public String getS() {
+            return "from getS()";
+        }
+        
+        public String s() {
+            return "from s()";
+        }
+    }
+    
+    static class PropertyReplacementMethodAppearanceFineTuner implements MethodAppearanceFineTuner {
+        private final Boolean replaceExistingProperty; 
+        private final boolean preferGetS;
+        
+        PropertyReplacementMethodAppearanceFineTuner(Boolean replaceExistingProperty, boolean preferGetS) {
+            this.replaceExistingProperty = replaceExistingProperty;
+            this.preferGetS = preferGetS;
+        }
+
+        public void process(MethodAppearanceDecisionInput in, MethodAppearanceDecision out) {
+            if (replaceExistingProperty != null) {
+                out.setReplaceExistingProperty(replaceExistingProperty);
+            }
+            if (preferGetS) {
+                if (in.getMethod().getName().equals("getS")) {
+                    try {
+                        out.setExposeAsProperty(new PropertyDescriptor("s", in.getMethod(), null));
+                    } catch (IntrospectionException e) {
+                        throw new IllegalStateException(e);
+                    }
+                }
+            } else {
+                if (in.getMethod().getName().equals("s")) {
+                    try {
+                        out.setExposeAsProperty(new PropertyDescriptor("s", in.getMethod(), null));
+                        out.setMethodShadowsProperty(false);
+                    } catch (IntrospectionException e) {
+                        throw new IllegalStateException(e);
+                    }
+                }
+            }
+        }
+        
+        
+    }
+    
 }