freemarker.template.MethodCallAwareTemplateHashModel Maven / Gradle / Ivy
Show all versions of org.apache.servicemix.bundles.freemarker
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package freemarker.template;
import java.util.Collection;
import java.util.Map;
import freemarker.core.Macro;
import freemarker.core.NonMethodException;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.ZeroArgumentNonVoidMethodPolicy;
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 used by the
* template language for 0-argument method calls that are directly 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"]()}).
*
* 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_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD}.
*
*
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. 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)}).
*
*
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
* {@link Map}-s, {@link Collection}-s, {@link Number}-s, etc.).
*
* @since 2.3.33
*/
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
* 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 {@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; 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 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.
*/
TemplateModel getBeforeMethodCall(String key)
throws TemplateModelException, ShouldNotBeGetAsMethodException;
/**
* Thrown by {@link #getBeforeMethodCall(String)}; see there.
*
* @since 2.3.33
*/
final class ShouldNotBeGetAsMethodException extends Exception {
private final TemplateModel actualValue;
private final String hint;
/**
* Same as {@link ShouldNotBeGetAsMethodException(TemplateModel, String, Throwable)}, with {@code null}
* cause exception argument.
*/
public ShouldNotBeGetAsMethodException(TemplateModel actualValue, String hint) {
this(actualValue, hint, null);
}
/**
* @param actualValue
* The actual value we got instead of a method; can't be {@code null}!
* @param hint
* Hint for the user, that's added to the error message; {@code null} if you just want the plain
* {@link NonMethodException} error message.
* @param cause
* Can be {@code null}.
*/
public ShouldNotBeGetAsMethodException(TemplateModel actualValue, String hint, Throwable cause) {
super(null, cause, true, false);
NullArgumentException.check(actualValue);
this.actualValue = actualValue;
this.hint = hint;
}
/**
* The actual value we got instead of a method; not {@code null}.
*/
public TemplateModel getActualValue() {
return actualValue;
}
/**
* Additional hint for the user; maybe {@code null}.
*/
public String getHint() {
return hint;
}
}
}