blob: fe55eb93d52f1394ca8432deedbe0843d45fbf9a [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.core;
import freemarker.template.Configuration;
import freemarker.template.Template;
/**
* A class that allows one to associate custom data with a {@link Configuration}, a {@link Template}, or
* {@link Environment}.
*
* <p>This API has similar approach to that of {@link ThreadLocal} (which allows one to associate
* custom data with a thread). With an example:</p>
*
* <pre>
* // The object identity itself will serve as the attribute identifier; there's no attribute name String:
* public static final CustomAttribute MY_ATTR = new CustomAttribute(CustomAttribute.SCOPE_CONFIGURATION);
* ...
* // Set the attribute in this particular Configuration object:
* MY_ATTR.set(myAttrValue, cfg);
* ...
* // Read the attribute from this particular Configuration object:
* myAttrValue = MY_ATTR.get(cfg);
* </pre>
*/
public class CustomAttribute {
/**
* Constant used in the constructor specifying that this attribute is {@link Environment}-scoped.
*/
public static final int SCOPE_ENVIRONMENT = 0;
/**
* Constant used in the constructor specifying that this attribute is {@link Template}-scoped.
*/
public static final int SCOPE_TEMPLATE = 1;
/**
* Constant used in the constructor specifying that this attribute is {@link Configuration}-scoped.
*/
public static final int SCOPE_CONFIGURATION = 2;
// We use an internal key instead of 'this' so that malicious subclasses
// overriding equals() and hashCode() can't gain access to other attribute
// values. That's also the reason why get() and set() are marked final.
private final Object key = new Object();
private final int scope;
/**
* Creates a new custom attribute with the specified scope
* @param scope one of <tt>SCOPE_</tt> constants.
*/
public CustomAttribute(int scope) {
if (scope != SCOPE_ENVIRONMENT &&
scope != SCOPE_TEMPLATE &&
scope != SCOPE_CONFIGURATION) {
throw new IllegalArgumentException();
}
this.scope = scope;
}
/**
* This method is invoked when {@link #get()} is invoked without
* {@link #set(Object)} being invoked before it to define the value in the
* current scope. Override it to create the attribute value on-demand.
* @return the initial value for the custom attribute. By default returns null.
*/
protected Object create() {
return null;
}
/**
* Gets the attribute from the appropriate scope that's accessible through the specified {@link Environment}. If
* the attribute has {@link #SCOPE_ENVIRONMENT} scope, it will be get from the given {@link Environment} directly.
* If the attribute has {@link #SCOPE_TEMPLATE} scope, it will be get from the parent of the given
* {@link Environment} (that is, in {@link Environment#getParent()}) directly). If the attribute has
* {@link #SCOPE_CONFIGURATION} scope, it will be get from {@link Environment#getConfiguration()}.
*
* @throws NullPointerException
* If {@code env} is null
*
* @return The new value of the attribute (possibly {@code null}), or {@code null} if the attribute doesn't exist.
*
* @since 2.3.22
*/
public final Object get(Environment env) {
return getScopeConfigurable(env).getCustomAttribute(key, this);
}
/**
* Same as {@link #get(Environment)}, but uses {@link Environment#getCurrentEnvironment()} to fill the 2nd argument.
*
* @throws IllegalStateException
* If there is no current {@link Environment}, which is usually the case when the current thread isn't
* processing a template.
*/
public final Object get() {
return getScopeConfigurable(getRequiredCurrentEnvironment()).getCustomAttribute(key, this);
}
/**
* Gets the value of a {@link Template}-scope attribute from the given {@link Template}.
*
* @throws UnsupportedOperationException
* If this custom attribute has different scope than {@link #SCOPE_TEMPLATE}.
* @throws NullPointerException
* If {@code template} is null
*/
public final Object get(Template template) {
if (scope != SCOPE_TEMPLATE) {
throw new UnsupportedOperationException("This is not a template-scope attribute");
}
return ((Configurable) template).getCustomAttribute(key, this);
}
/**
* Same as {@link #get(Template)}, but applies to a {@link TemplateConfiguration}.
*
* @since 2.3.24
*/
public Object get(TemplateConfiguration templateConfiguration) {
if (scope != SCOPE_TEMPLATE) {
throw new UnsupportedOperationException("This is not a template-scope attribute");
}
return templateConfiguration.getCustomAttribute(key, this);
}
/**
* Gets the value of a {@link Configuration}-scope attribute from the given {@link Configuration}.
*
* @throws UnsupportedOperationException
* If this custom attribute has different scope than {@link #SCOPE_CONFIGURATION}.
* @throws NullPointerException
* If {@code cfg} is null
*
* @since 2.3.22
*/
public final Object get(Configuration cfg) {
if (scope != SCOPE_CONFIGURATION) {
throw new UnsupportedOperationException("This is not a template-scope attribute");
}
return ((Configurable) cfg).getCustomAttribute(key, this);
}
/**
* Sets the attribute inside the appropriate scope that's accessible through the specified {@link Environment}. If
* the attribute has {@link #SCOPE_ENVIRONMENT} scope, it will be set in the given {@link Environment} directly. If
* the attribute has {@link #SCOPE_TEMPLATE} scope, it will be set in the parent of the given {@link Environment}
* (that is, in {@link Environment#getParent()}) directly). If the attribute has {@link #SCOPE_CONFIGURATION} scope,
* it will be set in {@link Environment#getConfiguration()}.
*
* @param value
* The new value of the attribute. Can be {@code null}.
*
* @throws NullPointerException
* If {@code env} is null
*
* @since 2.3.22
*/
public final void set(Object value, Environment env) {
getScopeConfigurable(env).setCustomAttribute(key, value);
}
/**
* Same as {@link #set(Object, Environment)}, but uses {@link Environment#getCurrentEnvironment()} to fill the 2nd
* argument.
*
* @throws IllegalStateException
* If there is no current {@link Environment}, which is usually the case when the current thread isn't
* processing a template.
*/
public final void set(Object value) {
getScopeConfigurable(getRequiredCurrentEnvironment()).setCustomAttribute(key, value);
}
/**
* Sets the value of a {@link Template}-scope attribute in the given {@link Template}.
*
* @param value
* The new value of the attribute. Can be {@code null}.
*
* @throws UnsupportedOperationException
* If this custom attribute has different scope than {@link #SCOPE_TEMPLATE}.
* @throws NullPointerException
* If {@code template} is null
*/
public final void set(Object value, Template template) {
if (scope != SCOPE_TEMPLATE) {
throw new UnsupportedOperationException("This is not a template-scope attribute");
}
((Configurable) template).setCustomAttribute(key, value);
}
/**
* Same as {@link #set(Object, Template)}, but applicable to a {@link TemplateConfiguration}.
*
* @since 2.3.24
*/
public final void set(Object value, TemplateConfiguration templateConfiguration) {
if (scope != SCOPE_TEMPLATE) {
throw new UnsupportedOperationException("This is not a template-scope attribute");
}
templateConfiguration.setCustomAttribute(key, value);
}
/**
* Sets the value of a {@link Configuration}-scope attribute in the given {@link Configuration}.
*
* @param value
* The new value of the attribute. Can be {@code null}.
*
* @throws UnsupportedOperationException
* If this custom attribute has different scope than {@link #SCOPE_CONFIGURATION}.
* @throws NullPointerException
* If {@code cfg} is null
*
* @since 2.3.22
*/
public final void set(Object value, Configuration cfg) {
if (scope != SCOPE_CONFIGURATION) {
throw new UnsupportedOperationException("This is not a configuration-scope attribute");
}
((Configurable) cfg).setCustomAttribute(key, value);
}
private Environment getRequiredCurrentEnvironment() {
Environment c = Environment.getCurrentEnvironment();
if (c == null) {
throw new IllegalStateException("No current environment");
}
return c;
}
private Configurable getScopeConfigurable(Environment env) throws Error {
switch (scope) {
case SCOPE_ENVIRONMENT:
return env;
case SCOPE_TEMPLATE:
return env.getParent();
case SCOPE_CONFIGURATION:
return env.getParent().getParent();
default:
throw new BugException();
}
}
}