| /* |
| * 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(); |
| } |
| } |
| |
| } |