Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 70104b4e2 -> 486dc6229


Forward ported from 2.3-gae: Added new property to 
MethodAppearanceFineTuner.Decision: replaceExistingProperty. This is useful 
when a method like size() is exposed as a JavaBean property via 
Decision.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/486dc622
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/486dc622
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/486dc622

Branch: refs/heads/3
Commit: 486dc6229d00f74490aa65e30f3291aac390670b
Parents: 70104b4
Author: ddekany <ddek...@apache.org>
Authored: Sun Jan 7 13:09:02 2018 +0100
Committer: ddekany <ddek...@apache.org>
Committed: Sun Jan 7 13:09:02 2018 +0100

----------------------------------------------------------------------
 .../impl/FineTuneMethodAppearanceTest.java      | 74 ++++++++++++++++++++
 .../core/model/impl/ClassIntrospector.java      |  4 +-
 .../model/impl/MethodAppearanceFineTuner.java   | 64 ++++++++++++++---
 3 files changed, 133 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/486dc622/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
index 76bddd7..682b8ac 100644
--- 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/FineTuneMethodAppearanceTest.java
@@ -21,6 +21,9 @@ package org.apache.freemarker.core.model.impl;
 
 import static org.junit.Assert.*;
 
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.TemplateHashModel;
@@ -50,6 +53,30 @@ public class FineTuneMethodAppearanceTest {
         assertTrue(thm.get("getV3") instanceof JavaMethodModel);
     }
     
+    @Test
+    public void existingPropertyReplacement() throws TemplateException {
+        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 TemplateException {
+        DefaultObjectWrapper ow = new 
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+                .methodAppearanceFineTuner(
+                        new 
PropertyReplacementMethodAppearanceFineTuner(replaceExistingProperty, 
preferGetS))
+                .build();
+        assertEquals(expectedValue,
+                ((TemplateStringModel) ((TemplateHashModel) ow.wrap(new 
PropertyReplacementTestBean())).get("s"))
+                .getAsString());
+    }    
+    
     static public class C {
         
         public String v1 = "v1";
@@ -61,4 +88,51 @@ public class FineTuneMethodAppearanceTest {
         public String getV3() { return "getV3()"; }
     }
     
+    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;
+        }
+
+        @Override
+        public void process(MethodAppearanceFineTuner.DecisionInput in, 
MethodAppearanceFineTuner.Decision 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);
+                    }
+                }
+            }
+        }
+        
+    }
+        
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/486dc622/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index c6c9246..5cad757 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -335,7 +335,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/486dc622/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
index 49b19ef..0482878 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodAppearanceFineTuner.java
@@ -38,20 +38,24 @@ public interface MethodAppearanceFineTuner {
      * With this method you can do the following tweaks:
      * <ul>
      *   <li>Hide a method that would be otherwise shown by calling
-     *     {@link 
org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)}
+     *     {@link MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)}
      *     with <tt>null</tt> parameter. Note that you can't un-hide methods
      *     that are not public or are considered to by unsafe
      *     (like {@link Object#wait()}) because
      *     {@link #process} is not called for those.</li>
      *   <li>Show the method with a different name in the data-model than its
      *     real name by calling
-     *     {@link 
org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)}
+     *     {@link MethodAppearanceFineTuner.Decision#setExposeMethodAs(String)}
      *     with non-<tt>null</tt> parameter.
      *   <li>Create a fake JavaBean property for this method by calling
-     *     {@link 
org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setExposeAsProperty(PropertyDescriptor)}.
+     *     {@link 
MethodAppearanceFineTuner.Decision#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 Decision#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
@@ -61,12 +65,13 @@ 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 Decision#setReplaceExistingProperty(boolean)}.
      *   <li>Prevent the method to hide a JavaBean property (fake or real) of
      *     the same name by calling
-     *     {@link 
org.apache.freemarker.core.model.impl.MethodAppearanceFineTuner.Decision#setMethodShadowsProperty(boolean)}
+     *     {@link 
MethodAppearanceFineTuner.Decision#setMethodShadowsProperty(boolean)}
      *     with <tt>false</tt>. The default is <tt>true</tt>, so if you have
      *     both a property and a method called "foo", then in the template
      *     <tt>myObject.foo</tt> will return the method itself instead
@@ -92,35 +97,78 @@ public interface MethodAppearanceFineTuner {
      */
     final class Decision {
         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)}.
+         */
+        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)}.
+         */
+        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 exposeMethodAs) {
             this.exposeMethodAs = exposeMethodAs;
         }
 
+        /**
+         * 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 methodShadowsProperty) {
             this.methodShadowsProperty = methodShadowsProperty;
         }

Reply via email to