Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id DA470200C24 for ; Thu, 23 Feb 2017 22:36:11 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id D8DA3160B67; Thu, 23 Feb 2017 21:36:11 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id A516E160B3E for ; Thu, 23 Feb 2017 22:36:10 +0100 (CET) Received: (qmail 90821 invoked by uid 500); 23 Feb 2017 21:36:09 -0000 Mailing-List: contact notifications-help@freemarker.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@freemarker.incubator.apache.org Delivered-To: mailing list notifications@freemarker.incubator.apache.org Received: (qmail 90812 invoked by uid 99); 23 Feb 2017 21:36:09 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 23 Feb 2017 21:36:09 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 6CDEAC06AB for ; Thu, 23 Feb 2017 21:36:09 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -3.221 X-Spam-Level: X-Spam-Status: No, score=-3.221 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001] autolearn=disabled Received: from mx1-lw-us.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id yQJYJD8sBHzL for ; Thu, 23 Feb 2017 21:36:07 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-us.apache.org (ASF Mail Server at mx1-lw-us.apache.org) with SMTP id BDB7761595 for ; Thu, 23 Feb 2017 21:36:06 +0000 (UTC) Received: (qmail 85789 invoked by uid 99); 23 Feb 2017 21:35:29 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 23 Feb 2017 21:35:29 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 9C5E9DFF09; Thu, 23 Feb 2017 21:35:29 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ddekany@apache.org To: notifications@freemarker.incubator.apache.org Date: Thu, 23 Feb 2017 21:36:08 -0000 Message-Id: <1b077ea6ea7642a1953860aca82b5169@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [41/54] [partial] incubator-freemarker git commit: Unifying the o.a.f.core and o.a.f.core.ast archived-at: Thu, 23 Feb 2017 21:36:12 -0000 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}. + * + *

This API has similar approach to that of {@link ThreadLocal} (which allows one to associate + * custom data with a thread). With an example:

+ * + *
+ * // 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);
+ * 
+ */ +// [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 SCOPE_ 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)}. + * + *

+ * 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. + * + *

+ * Don't implement this interface yourself, as new methods can be added to it any time! It's only meant to be + * implemented by the FreeMarker core. + * + *

+ * 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. + * + *

+ * This method will block other calls while the {@code objectFactory} is executing, thus, the object will be + * usually 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: } ${name}{@code}, + * {@code <@foo>Name: <#if showIt>Joe}. Examples of cases that give {@code true}: + * {@code <@foo>Name: Joe}, {@code <@foo />}. Note that we get {@code true} for no nested content, because + * that's equivalent with 0-length nested content in FTL. + * + *

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