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; }