http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configuration.java 
b/src/main/java/org/apache/freemarker/core/Configuration.java
index 49573ae..8e4cfd1 100644
--- a/src/main/java/org/apache/freemarker/core/Configuration.java
+++ b/src/main/java/org/apache/freemarker/core/Configuration.java
@@ -40,31 +40,6 @@ import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import org.apache.freemarker.core.ast.BugException;
-import org.apache.freemarker.core.ast.CSSOutputFormat;
-import org.apache.freemarker.core.ast.CombinedMarkupOutputFormat;
-import org.apache.freemarker.core.ast.Configurable;
-import org.apache.freemarker.core.ast.Environment;
-import org.apache.freemarker.core.ast.HTMLOutputFormat;
-import org.apache.freemarker.core.ast.JSONOutputFormat;
-import org.apache.freemarker.core.ast.JavaScriptOutputFormat;
-import org.apache.freemarker.core.ast.MarkupOutputFormat;
-import org.apache.freemarker.core.ast.OutputFormat;
-import org.apache.freemarker.core.ast.ParseException;
-import org.apache.freemarker.core.ast.ParserConfiguration;
-import org.apache.freemarker.core.ast.PlainTextOutputFormat;
-import org.apache.freemarker.core.ast.RTFOutputFormat;
-import org.apache.freemarker.core.ast.TemplateConfiguration;
-import org.apache.freemarker.core.ast.TemplateMarkupOutputModel;
-import org.apache.freemarker.core.ast.UndefinedOutputFormat;
-import org.apache.freemarker.core.ast.UnregisteredOutputFormatException;
-import org.apache.freemarker.core.ast.XHTMLOutputFormat;
-import org.apache.freemarker.core.ast.XMLOutputFormat;
-import org.apache.freemarker.core.ast._CoreAPI;
-import org.apache.freemarker.core.ast._DelayedJQuote;
-import org.apache.freemarker.core.ast._MiscTemplateException;
-import org.apache.freemarker.core.ast._ObjectBuilderSettingEvaluator;
-import org.apache.freemarker.core.ast._SettingEvaluationEnvironment;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
@@ -89,6 +64,7 @@ import 
org.apache.freemarker.core.templateresolver.impl.FileTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.MruCacheStorage;
 import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage;
+import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CaptureOutput;
 import org.apache.freemarker.core.util.HtmlEscape;
 import org.apache.freemarker.core.util.NormalizeNewlines;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/CustomAttribute.java 
b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
new file mode 100644
index 0000000..3dee398
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/CustomAttribute.java
@@ -0,0 +1,264 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import org.apache.freemarker.core.util.BugException;
+
+/**
+ * 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>
+ */
+// [2.4] Use generics; type parameter used for the type of the stored value 
+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();
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java 
b/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
new file mode 100644
index 0000000..bce8659
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
@@ -0,0 +1,137 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import java.util.IdentityHashMap;
+
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateTransformModel;
+import org.apache.freemarker.core.util.ObjectFactory;
+
+/**
+ * Gives information about the place where a directive is called from, also 
lets you attach a custom data object to that
+ * place. Each directive call in a template has its own {@link 
DirectiveCallPlace} object (even when they call the same
+ * directive with the same parameters). The life cycle of the {@link 
DirectiveCallPlace} object is bound to the
+ * {@link Template} object that contains the directive call. Hence, the {@link 
DirectiveCallPlace} object and the custom
+ * data you put into it is cached together with the {@link Template} (and 
templates are normally cached - see
+ * {@link Configuration#getTemplate(String)}). The custom data is normally 
initialized on demand, that is, when the
+ * directive call is first executed, via {@link #getOrCreateCustomData(Object, 
ObjectFactory)}.
+ * 
+ * <p>
+ * Currently this method doesn't give you access to the {@link Template} 
object, because it's probable that future
+ * versions of FreeMarker will be able to use the same parsed representation 
of a "file" for multiple {@link Template}
+ * objects. Then the call place will be bound to the parsed representation, 
not to the {@link Template} objects that are
+ * based on it.
+ * 
+ * <p>
+ * <b>Don't implement this interface yourself</b>, as new methods can be added 
to it any time! It's only meant to be
+ * implemented by the FreeMarker core.
+ * 
+ * <p>
+ * This interface is currently only used for custom directive calls (that is, 
a {@code <@...>} that calls a
+ * {@link TemplateDirectiveModel}, {@link TemplateTransformModel}, or a macro).
+ * 
+ * @see Environment#getCurrentDirectiveCallPlace()
+ * 
+ * @since 2.3.22
+ */
+public interface DirectiveCallPlace {
+
+    /**
+     * The 1-based column number of the first character of the directive call 
in the template source code, or -1 if it's
+     * not known.
+     */
+    int getBeginColumn();
+
+    /**
+     * The 1-based line number of the first character of the directive call in 
the template source code, or -1 if it's
+     * not known.
+     */
+    int getBeginLine();
+
+    /**
+     * The 1-based column number of the last character of the directive call 
in the template source code, or -1 if it's
+     * not known. If the directive has an end-tag ({@code </@...>}), then it 
points to the last character of that.
+     */
+    int getEndColumn();
+
+    /**
+     * The 1-based line number of the last character of the directive call in 
the template source code, or -1 if it's
+     * not known. If the directive has an end-tag ({@code </@...>}), then it 
points to the last character of that.
+     */
+    int getEndLine();
+
+    /**
+     * Returns the custom data, or if that's {@code null}, then it creates and 
stores it in an atomic operation then
+     * returns it. This method is thread-safe, however, it doesn't ensure 
thread safe (like synchronized) access to the
+     * custom data itself. See the top-level documentation of {@link 
DirectiveCallPlace} to understand the scope and
+     * life-cycle of the custom data. Be sure that the custom data only 
depends on things that get their final value
+     * during template parsing, not on runtime settings.
+     * 
+     * <p>
+     * This method will block other calls while the {@code objectFactory} is 
executing, thus, the object will be
+     * <em>usually</em> created only once, even if multiple threads request 
the value when it's still {@code null}. It
+     * doesn't stand though when {@code providerIdentity} mismatches occur 
(see later). Furthermore, then it's also
+     * possible that multiple objects created by the same {@link 
ObjectFactory} will be in use on the same time, because
+     * of directive executions already running in parallel, and because of 
memory synchronization delays (hardware
+     * dependent) between the threads.
+     * 
+     * @param providerIdentity
+     *            This is usually the class of the {@link 
TemplateDirectiveModel} that creates (and uses) the custom
+     *            data, or if you are using your own class for the custom data 
object (as opposed to a class from some
+     *            more generic API), then that class. This is needed as the 
same call place might calls different
+     *            directives depending on runtime conditions, and so it must 
be ensured that these directives won't
+     *            accidentally read each other's custom data, ending up with 
class cast exceptions or worse. In the
+     *            current implementation, if there's a {@code 
providerIdentity} mismatch (means, the
+     *            {@code providerIdentity} object used when the custom data 
was last set isn't the exactly same object
+     *            as the one provided with the parameter now), the previous 
custom data will be just ignored as if it
+     *            was {@code null}. So if multiple directives that use the 
custom data feature use the same call place,
+     *            the caching of the custom data can be inefficient, as they 
will keep overwriting each other's custom
+     *            data. (In a more generic implementation the {@code 
providerIdentity} would be a key in a
+     *            {@link IdentityHashMap}, but then this feature would be 
slower, while {@code providerIdentity}
+     *            mismatches aren't occurring in most applications.)
+     * @param objectFactory
+     *            Called when the custom data wasn't yet set, to create its 
initial value. If this parameter is
+     *            {@code null} and the custom data wasn't set yet, then {@code 
null} will be returned. The returned
+     *            value of {@link ObjectFactory#createObject()} can be any 
kind of object, but can't be {@code null}.
+     * 
+     * @return The current custom data object, or possibly {@code null} if 
there was no {@link ObjectFactory} provided.
+     * 
+     * @throws CallPlaceCustomDataInitializationException
+     *             If the {@link ObjectFactory} had to be invoked but failed.
+     */
+    Object getOrCreateCustomData(Object providerIdentity, ObjectFactory 
objectFactory)
+            throws CallPlaceCustomDataInitializationException;
+
+    /**
+     * Tells if the nested content (the body) can be safely cached, as it only 
depends on the template content (not on
+     * variable values and such) and has no side-effects (other than writing 
to the output). Examples of cases that give
+     * {@code false}: {@code <@foo>Name: } <tt>${name}</tt>{@code</@foo>},
+     * {@code <@foo>Name: <#if showIt>Joe</#if></@foo>}. Examples of cases 
that give {@code true}:
+     * {@code <@foo>Name: Joe</@foo>}, {@code <@foo />}. Note that we get 
{@code true} for no nested content, because
+     * that's equivalent with 0-length nested content in FTL.
+     * 
+     * <p>
+     * This method returns a pessimistic result. For example, if it sees a 
custom directive call, it can't know what it
+     * does, so it will assume that it's not cacheable.
+     */
+    boolean isNestedOutputCacheable();
+
+}

Reply via email to