This is an automated email from the ASF dual-hosted git repository. ddekany pushed a commit to branch 2.3-gae in repository https://gitbox.apache.org/repos/asf/freemarker.git
commit 5afeb9a12b874cf51fbf7723797244e183d545c5 Author: ddekany <[email protected]> AuthorDate: Wed May 8 17:30:42 2024 +0200 Improved JavaDoc related to ZeroArgumentNonVoidMethodPolicy (mostly). --- .../java/freemarker/core/DotBeforeMethodCall.java | 2 + .../java/freemarker/ext/beans/BeansWrapper.java | 22 ++++---- .../ext/beans/BeansWrapperConfiguration.java | 9 ++++ .../freemarker/ext/beans/ClassIntrospector.java | 4 +- .../ext/beans/ClassIntrospectorBuilder.java | 23 +++++++- .../freemarker/ext/beans/MemberAccessPolicy.java | 7 ++- .../ext/beans/MethodAppearanceFineTuner.java | 61 ++++++++++------------ .../ext/beans/ZeroArgumentNonVoidMethodPolicy.java | 32 +++++++++--- .../template/MethodCallAwareTemplateHashModel.java | 44 ++++++++++------ 9 files changed, 130 insertions(+), 74 deletions(-) diff --git a/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java b/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java index 17ae026c..f40ebbd1 100644 --- a/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java +++ b/freemarker-core/src/main/java/freemarker/core/DotBeforeMethodCall.java @@ -33,6 +33,8 @@ import freemarker.template.TemplateModel; * {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD} * (via {@link BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)}). We don't * necessarily want to go beyond that hack, as we don't have separate method namespace in the template language. + * + * @since 2.3.33 */ class DotBeforeMethodCall extends Dot { public DotBeforeMethodCall(Dot dot) { diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java index f7993f19..542ac9d3 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapper.java @@ -107,13 +107,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable { /** * At this level of exposure, all methods and properties of the wrapped * objects are exposed to the template except methods that are deemed - * not safe. The not safe methods are java.lang.Object methods wait() and - * notify(), java.lang.Class methods getClassLoader() and newInstance(), - * java.lang.reflect.Method and java.lang.reflect.Constructor invoke() and - * newInstance() methods, all java.lang.reflect.Field set methods, all - * java.lang.Thread and java.lang.ThreadGroup methods that can change its - * state, as well as the usual suspects in java.lang.System and - * java.lang.Runtime. + * not safe by the {@link MemberAccessPolicy}. * * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible. That mechanism was introduced * much later than "exposure levels", and it's the primary place to look at if you are concerned with safety. @@ -121,19 +115,21 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable { public static final int EXPOSE_SAFE = 1; /** - * At this level of exposure, only property getters are exposed. - * Additionally, property getters that map to unsafe methods are not - * exposed (i.e. Class.classLoader and Thread.contextClassLoader). + * At this level of exposure, only Java Bean properties are exposed. For example, if you have + * {@code public int getX()} in a public class, then you can access that in templates like {@code obj.x} (but + * not as {@code obj.getX()}). * - * <p>Note that the {@link MemberAccessPolicy} will further restrict what's visible. + * <p>Note that the {@link MemberAccessPolicy} will further restricts what's visible. + * Java Bean properties (like {@code obj.x} earlier) whose read method (like {@code getX()} earlier) is not + * accessible according the policy will not be visible. */ public static final int EXPOSE_PROPERTIES_ONLY = 2; /** - * At this level of exposure, no bean properties and methods are exposed. + * At this level of exposure, no Java Bean properties, and no methods are exposed. * Only map items, resource bundle items, and objects retrieved through * the generic get method (on objects of classes that have a generic get - * method) can be retrieved through the hash interface. You might want to + * method) can be retrieved through the {@link TemplateHashModel} interface. You might want to * call {@link #setMethodsShadowItems(boolean)} with {@code false} value to * speed up map item retrieval. */ diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java index e69cf7a3..3d8337c4 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java @@ -252,6 +252,8 @@ public abstract class BeansWrapperConfiguration implements Cloneable { } /** + * Getter pair of {@link #setDefaultZeroArgumentNonVoidMethodPolicy}. + * * @since 2.3.33 */ public ZeroArgumentNonVoidMethodPolicy getDefaultZeroArgumentNonVoidMethodPolicy() { @@ -270,6 +272,8 @@ public abstract class BeansWrapperConfiguration implements Cloneable { } /** + * Getter pair of {@link #setRecordZeroArgumentNonVoidMethodPolicy}. + * * @since 2.3.33 */ public ZeroArgumentNonVoidMethodPolicy getRecordZeroArgumentNonVoidMethodPolicy() { @@ -287,6 +291,9 @@ public abstract class BeansWrapperConfiguration implements Cloneable { classIntrospectorBuilder.setRecordZeroArgumentNonVoidMethodPolicy(recordZeroArgumentNonVoidMethodPolicy); } + /** + * Getter pair of {@link #setMethodAppearanceFineTuner} + */ public MethodAppearanceFineTuner getMethodAppearanceFineTuner() { return classIntrospectorBuilder.getMethodAppearanceFineTuner(); } @@ -295,6 +302,8 @@ public abstract class BeansWrapperConfiguration implements Cloneable { * See {@link BeansWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}; additionally, * note that currently setting this to non-{@code null} will disable class introspection cache sharing, unless * the value implements {@link SingletonCustomizer}. + * + * <p>Note that methods in this class are inherited by {@link DefaultObjectWrapperBuilder}, which is what you normally use. */ public void setMethodAppearanceFineTuner(MethodAppearanceFineTuner methodAppearanceFineTuner) { classIntrospectorBuilder.setMethodAppearanceFineTuner(methodAppearanceFineTuner); diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java index b223868e..45f76de7 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java @@ -203,7 +203,7 @@ class ClassIntrospector { if (recordAware && _JavaVersions.JAVA_16 == null) { throw new IllegalArgumentException( "defaultZeroArgumentNonVoidMethodPolicy != recordZeroArgumentNonVoidMethodPolicy, " + - "but Java 16 support is not available."); + "but record support is not available (as Java 16 support is not available)."); } this.incompatibleImprovements = builder.getIncompatibleImprovements(); @@ -316,7 +316,7 @@ class ClassIntrospector { if (introspData.size() > 1) { return introspData; - } else if (introspData.size() == 0) { + } else if (introspData.isEmpty()) { return Collections.emptyMap(); } else { // map.size() == 1 Entry<Object, Object> e = introspData.entrySet().iterator().next(); diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java index ea75987d..a966f08d 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java @@ -77,7 +77,8 @@ final class ClassIntrospectorBuilder implements Cloneable { treatDefaultMethodsAsBeanMembers = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_26; defaultZeroArgumentNonVoidMethodPolicy = ZeroArgumentNonVoidMethodPolicy.METHOD_ONLY; recordZeroArgumentNonVoidMethodPolicy = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_33 && _JavaVersions.JAVA_16 != null - ? ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD : ZeroArgumentNonVoidMethodPolicy.METHOD_ONLY; + ? ZeroArgumentNonVoidMethodPolicy.BOTH_PROPERTY_AND_METHOD + : defaultZeroArgumentNonVoidMethodPolicy; memberAccessPolicy = DefaultMemberAccessPolicy.getInstance(this.incompatibleImprovements); } @@ -166,6 +167,8 @@ final class ClassIntrospectorBuilder implements Cloneable { } /** + * The getter pair of {@link #setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}. + * * @since 2.3.33 */ public ZeroArgumentNonVoidMethodPolicy getDefaultZeroArgumentNonVoidMethodPolicy() { @@ -173,6 +176,10 @@ final class ClassIntrospectorBuilder implements Cloneable { } /** + * Sets the {@link ZeroArgumentNonVoidMethodPolicy} used for classes that are not records (or any other special + * cases we add support for later). + * The default value is {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}. + * * @since 2.3.33 */ public void setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy defaultZeroArgumentNonVoidMethodPolicy) { @@ -181,6 +188,8 @@ final class ClassIntrospectorBuilder implements Cloneable { } /** + * The getter pair of {@link #setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}. + * * @since 2.3.33 */ public ZeroArgumentNonVoidMethodPolicy getRecordZeroArgumentNonVoidMethodPolicy() { @@ -188,6 +197,13 @@ final class ClassIntrospectorBuilder implements Cloneable { } /** + * Sets the {@link ZeroArgumentNonVoidMethodPolicy} used for records. + * The default value is {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD} if + * {@link #getIncompatibleImprovements()} is at least 2.3.33, and we are on Java 16 or later, otherwise + * it's {@link ZeroArgumentNonVoidMethodPolicy#METHOD_ONLY}. + * + * @see #setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy) + * * @since 2.3.33 */ public void setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy recordZeroArgumentNonVoidMethodPolicy) { @@ -195,6 +211,11 @@ final class ClassIntrospectorBuilder implements Cloneable { this.recordZeroArgumentNonVoidMethodPolicy = recordZeroArgumentNonVoidMethodPolicy; } + /** + * Get getter pair of {@link #setMemberAccessPolicy(MemberAccessPolicy)} + * + * @since 2.3.30 + */ public MemberAccessPolicy getMemberAccessPolicy() { return memberAccessPolicy; } diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java b/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java index 6d7abdbf..e4e3a093 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java @@ -36,11 +36,16 @@ import freemarker.template.TemplateModel; * {@link BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} (or if you use {@link DefaultObjectWrapper}, * with {@link DefaultObjectWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)}). * - * <p>As {@link BeansWrapper}, and its subclasses like {@link DefaultObjectWrapper}, only discover public + * <p>As {@link BeansWrapper}, and its subclasses, like {@link DefaultObjectWrapper}, only discover public * members, it's pointless to whitelist non-public members. (Also, while public members declared in non-public classes * are discovered by {@link BeansWrapper}, Java reflection will not allow accessing those normally, so generally it's * not useful to whitelist those either.) * + * <p>Note {@link BeansWrapper}, and its subclasses, like {@link DefaultObjectWrapper}, also have an + * {@link BeansWrapper#setExposureLevel(int) exposureLevel} a setting that's applied before the + * {@link MemberAccessPolicy}, also, with {@link BeansWrapper#EXPOSE_ALL} the {@link MemberAccessPolicy} will be + * ignored. + * * <p>Note that if you add {@link TemplateModel}-s directly to the data-model, those are not wrapped by the * {@link ObjectWrapper} (from {@link Environment#getObjectWrapper()}), and so the {@link MemberAccessPolicy} won't * affect those. diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java b/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java index 8d0ccd8d..88d16827 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/MethodAppearanceFineTuner.java @@ -24,11 +24,13 @@ import java.beans.PropertyDescriptor; import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecision; import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecisionInput; +import freemarker.template.MethodCallAwareTemplateHashModel; /** - * Used for customizing how the methods are visible from templates, via + * Used for customizing how the Java methods are visible from templates, via * {@link BeansWrapper#setMethodAppearanceFineTuner(MethodAppearanceFineTuner)}. - * The object that implements this should also implement {@link SingletonCustomizer} whenever possible. + * The object that implements this should also implement {@link SingletonCustomizer} whenever possible, to allow reusing + * the class introspection cache in more situations. * * @since 2.3.21 */ @@ -52,24 +54,24 @@ public interface MethodAppearanceFineTuner { * {@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 MethodAppearanceDecision#setExposeMethodAs(String)} - * with non-{@code null} parameter. Also, if set to {@code null}, the method won't be exposed. - * The default is the name of the method. Note that if {@code methodInsteadOfPropertyValueBeforeCall} is - * {@code true}, the method is not exposed if the method name set here is the same as the name of the property - * set for this method with {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}. + * {@link MethodAppearanceDecision#setExposeMethodAs(String)} with non-{@code null} parameter. (If set to + * {@code null}, then the method won't be exposed.) The default is the real name of the method. + * Note that if {@code methodInsteadOfPropertyValueBeforeCall} is {@code true}, the method is not exposed if the + * method name set here is the same as the name of the property set for this method with + * {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}. * <li>Create a fake JavaBean property for this method by calling * {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}. * For example, if you have {@code int size()} in a class, but you - * want it to be accessed from the templates as {@code obj.size}, - * rather than as {@code obj.size()}, 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). + * want it to be accessed from the templates as {@code obj.size} (like a JavaBean property), + * rather than as {@code obj.size()}, 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-{@code null} for the property read (get/is) methods of real JavaBeans - * properties, as bean properties are not seen as methods, and are exposed independently of this mechanism. + * properties, as bean properties are not treated as methods (hence the {@link MethodAppearanceFineTuner} is + * irrelevant), and are exposed or not regardless of this mechanism (based on + * {@link BeansWrapperBuilder#setExposureLevel(int)}). * The property name in the {@link PropertyDescriptor} can be anything, * but the method (or methods) in it must belong to the class that * is given as the {@code clazz} parameter, or it must be inherited from @@ -88,29 +90,22 @@ public interface MethodAppearanceFineTuner { * exposed that as a property via {@link MethodAppearanceDecision#setExposeAsProperty(PropertyDescriptor)}. So * far, you can access the property value from templates as {@code user.name}, but {@code user.name()} will * fail, saying that you try to call a {@code String} (because you apply the {@code ()} operator on the result - * of {@code user.name}). But with + * of {@code user.name}, which is a {@code String}). But with * {@link MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)} {@code true}, - * both {@code user.name}, and {@code user.name()} will do the same. - * The default of this is influenced by + * both {@code user.name}, and {@code user.name()} will do the same (which is possible because if {@code user} + * is a {@link MethodCallAwareTemplateHashModel}, "name" can be resoled to different values in the two cases). + * The default (initial) value of {@code methodInsteadOfPropertyValueBeforeCall} depends on * {@link BeansWrapperConfiguration#setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}, - * {@link BeansWrapperConfiguration#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}. - * <li>Prevent the method to hide a JavaBeans property (fake or real) of - * the same name by calling - * {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)} - * with {@code false}. The default is {@code true}, so if you have - * both a property and a method called "foo", then in the template - * {@code myObject.foo} will return the method itself instead - * of the property value, which is often undesirable. + * and {@link BeansWrapperConfiguration#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy)}. + * <li>Prevent the method to hide a JavaBeans property (fake or real) of the same name by calling + * {@link MethodAppearanceDecision#setMethodShadowsProperty(boolean)} with {@code false}. + * The default is {@code true}, so if you have both a property and a method called "foo", then in the template + * {@code myObject.foo} will return the method itself instead of the property value, which is often undesirable. * </ul> - * - * <p>Note that you can expose a Java method both as a method, and as a - * JavaBeans property on the same time, however you have to chose different - * names for them to prevent shadowing. - * - * @param in Describes the method about which the decision will have to be made. + * + * @param in Describes the method about which the decision will be made. * - * @param out Stores how the method will be exposed in the - * data-model after {@link #process} returns. + * @param out Stores how the method will be exposed in the data-model after {@link #process} returns. * This is initialized so that it reflects the default * behavior of {@link BeansWrapper}, so you don't have to do anything with this * when you don't want to change the default behavior. diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java b/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java index 2864e1ca..187d5bc7 100644 --- a/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java +++ b/freemarker-core/src/main/java/freemarker/ext/beans/ZeroArgumentNonVoidMethodPolicy.java @@ -20,9 +20,12 @@ package freemarker.ext.beans; import freemarker.template.DefaultObjectWrapper; +import freemarker.template.MethodCallAwareTemplateHashModel; +import freemarker.template.ObjectWrapper; +import freemarker.template.TemplateHashModel; /** - * How to show 0 argument non-void public methods to templates, which are not standard Java Beans read methods. + * How to show 0 argument non-void public methods to templates. * Used in {@link BeansWrapper}, and therefore in {@link DefaultObjectWrapper}. * This policy doesn't apply to methods that Java Beans introspector discovers as a property read method (which * typically look like {@code getSomething()}, or {@code isSomething()}). It's only applicable to methods like @@ -31,6 +34,7 @@ import freemarker.template.DefaultObjectWrapper; * @see BeansWrapperConfiguration#setDefaultZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy) * @see BeansWrapperConfiguration#setRecordZeroArgumentNonVoidMethodPolicy(ZeroArgumentNonVoidMethodPolicy) * @see BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean) + * @see MethodCallAwareTemplateHashModel * * @since 2.3.33 */ @@ -38,12 +42,22 @@ public enum ZeroArgumentNonVoidMethodPolicy { /** * Both {@code obj.m}, and {@code obj.m()} gives back the value that the {@code m} Java method returns, and it's - * not possible to get the method itself. + * not possible to get the method itself. But, it's not applicable for Java Bean property read methods + * (like {@code int getX()}), which remain just simple methods (because the Java Bean property is visible regardless + * of the {@link ZeroArgumentNonVoidMethodPolicy}, like {@code obj.x}, for which the Java Bean property read method + * is {@link int getX()}). * - * <p>This is a parse-time trick that only works when the result of the dot operator is called immediately in a - * template (and therefore the dot operator knows that you will call the result of it). The practical reason for - * this feature is that the convention of having {@code SomeType something()} instead of - * {@code SomeType getSomething()} spreads in the Java ecosystem (and is a standard in some other JVM languages), + * <p>This is a parse-time trick that only works when the result of the dot operator (like {@code obj.m}), or of the + * square bracket key operator (like {@code obj["m"]}) is called immediately in a template (like {@code obj.m()}, or + * like {@code obj["m"]()}), and therefore the dot, or square bracket key operator knows that you will call the + * result of it. In such case, if the {@linkplain ObjectWrapper wrapped} {@code obj} implements + * {@link MethodCallAwareTemplateHashModel}, the operator will call + * {@link MethodCallAwareTemplateHashModel#getBeforeMethodCall(String)} instead of + * {@link TemplateHashModel#get(String)}. Also note that at least in 2.3.33 it's only done if the method call has + * 0 arguments. + * + * <p>The practical reason for this feature is that the convention of using {@code SomeType something()} instead + * of {@code SomeType getSomething()} spreads in the Java ecosystem (and is a standard in some other JVM languages), * and thus we can't tell anymore if {@code SomeType something()} just reads a value, and hence should be accessed * like {@code obj.something}, or it's more like an operation that has side effect, and therefore should be * accessed like {@code obj.something()}. So with allowing both, the template author is free to decide which is @@ -54,12 +68,14 @@ public enum ZeroArgumentNonVoidMethodPolicy { BOTH_PROPERTY_AND_METHOD, /** - * Only {@code obj.m()} gives back the value, {@code obj.m} just gives the method itself. + * Only {@code obj.m()} gives back the value in a template, {@code obj.m} in a template just gives the method itself. */ METHOD_ONLY, /** - * {@code obj.m} in gives back the value, and the method itself can't be get. + * {@code obj.m} in a template gives back the value, and you can't get the method itself. But, it's not applicable + * for Java Bean property read methods, which will remain normals methods, just like with + * {@link #BOTH_PROPERTY_AND_METHOD}. */ PROPERTY_ONLY } diff --git a/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java b/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java index a3e80560..bac40768 100644 --- a/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java +++ b/freemarker-core/src/main/java/freemarker/template/MethodCallAwareTemplateHashModel.java @@ -30,23 +30,32 @@ import freemarker.template.utility.NullArgumentException; /** * Adds an extra getter method to {@link TemplateHashModel} that can return different result than {@link #get(String)}, - * knowing that the result of it will be called as a method. At least as of 2.3.33, this is only utilized by the - * template language for 0-argument non-void method calls directly after the dot operator and the key (like - * {@code obj.m()}), or for the equivalent with square brackets ({@code obj["m"]()}). For example, if in the - * template you have {@code someRecord.someComponent()}, and there {@code someRecord} was wrapped by the - * {@link ObjectWrapper} into a {@link TemplateHashModel} that also implements this interface, then the dot operator - * will call {@link #getBeforeMethodCall(String) getBeforeMethodCall("someComponent")}, rather than + * knowing that the result of it will be called as a method. At least as of 2.3.33, this is only used by the + * template language for 0-argument method calls that are <em>directly</em> after the dot operator and the key (like in + * {@code obj.m()}, where the "()" is directly after the key "m"), or for the equivalent of that with square brackets + * ({@code obj["m"]()}). + * + * <p>Background knowledge needed to understand this: In the FreeMarker template language, methods/functions are + * first class values (just like strings, numbers,etc.). Also, unlike in Java, there's no separate namespace for + * methods, and for the other field-like members. When you have {@code obj.m()} in a template, first, the dot operator + * gets the value for the key "m", and after that, and independently of that, the method call operator tries to call + * that value (and if it's not a method or function, that will fail). The dot operator, before 2.3.33, was never aware + * of what the value it gets will be used for (like will it be called, will it be printed, etc.). Now it can be. + * + * For example, if in the template you have {@code someRecord.someComponent()}, and there {@code someRecord} was wrapped + * by the {@link ObjectWrapper} into a {@link TemplateHashModel} that also implements this interface, then the dot + * operator will call {@link #getBeforeMethodCall(String) getBeforeMethodCall("someComponent")}, rather than * {@link #get(String) get("someComponent")}. This is needed to implement subtle features like * {@link BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)}, * which is needed to implement {@link ZeroArgumentNonVoidMethodPolicy#BOTH_PROPERTY_AND_METHOD}. * * <p>While technically we could do the same for method calls with more the 0 arguments, as of 2.3.33 at least, we - * don't want to generalize this to that case. The FreeMarker 2.x template language doesn't have separated namespace for - * methods, so this is already a hack as is, but we had to address the issue with Java records (see that at + * don't want to generalize this to that case. This is a workaround we added to address the issue with accessing + * components in Java records, which are 0-argument methods (see that at * {@link BeansWrapper.MethodAppearanceDecision#setMethodInsteadOfPropertyValueBeforeCall(boolean)}). * * <p>Objects wrapped with {@link BeansWrapper}, and hence with {@link DefaultObjectWrapper}, will implement this - * interface, when they are "generic" objects (that is, when they are not classes with special wrapper, like + * interface when they are "generic" objects (that is, when they are not classes with special wrapper, like * {@link Map}-s, {@link Collection}-s, {@link Number}-s, etc.). * * @since 2.3.33 @@ -54,22 +63,23 @@ import freemarker.template.utility.NullArgumentException; public interface MethodCallAwareTemplateHashModel extends TemplateHashModel { /** - * This is called instead of {@link #get(String)}, if we know that the return value should be callable like a - * method. The advantage of this is that we can coerce the value to a method when desirable, and otherwise can give + * This is called instead of {@link #get(String)} if we know that the return value should be callable like a method. + * The advantage of this is that we can coerce the value to a method when desirable, and otherwise can give * a more specific error message in the resulting exception than the standard {@link NonMethodException} would. * * @param key * Same as for {@link #get(String)} * * @return - * Same as for just like {@link #get(String)}, except it should return a - * {@link TemplateMethodModelEx}, or a {@link TemplateMethodModel}, or in very rare case a {@link Macro} - * that was created with the {@code function} directive. Or, {@code null} in the same case as + * Same as for {@link #get(String)}, except it should return a {@link TemplateMethodModel} + * (a {@link TemplateMethodModelEx} if possible), or in very rare case a {@link Macro} that was created with + * the {@code function} directive. Or, {@code null} in the same case as * {@link #get(String)}. The method should never return something that's not callable in the template language - * as a method or function. + * as a method or function; the return type is {@link TemplateModel} because that's the most specific common + * super-interface of {@link TemplateMethodModel}, and {@link Macro}. * * @throws ShouldNotBeGetAsMethodException - * If the value for the given key exists, but it shouldn't be coerced something callable as a method. This will + * If the value for the given key exists, but shouldn't be coerced to something callable as a method. This will * be converted to {@link NonMethodException} by the engine, but in this exception you can optionally give a * more specific explanation, and that will be added to the resulting {@link NonMethodException} as a hint to * the user. @@ -79,6 +89,8 @@ public interface MethodCallAwareTemplateHashModel extends TemplateHashModel { /** * Thrown by {@link #getBeforeMethodCall(String)}; see there. + * + * @since 2.3.33 */ final class ShouldNotBeGetAsMethodException extends Exception { private final TemplateModel actualValue;
