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