blob: a3e805602ed355ddb2e9aa51a366100cb18f689e [file] [log] [blame]
/*
* 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 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
* {@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
* {@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
* {@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 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
* {@link #get(String)}. The method should never return something that's not callable in the template language
* as a method or function.
*
* @throws ShouldNotBeGetAsMethodException
* If the value for the given key exists, but it shouldn't be coerced 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.
*/
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;
}
}
}