http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java new file mode 100644 index 0000000..78d8025 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateClassResolver.java @@ -0,0 +1,82 @@ +/* + * 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._ClassUtil; + +/** + * Used by built-ins and other template language features that get a class + * based on a string. This can be handy both for implementing security + * restrictions and for working around local class-loader issues. + * + * The implementation should be thread-safe, unless an + * instance is always only used in a single {@link Environment} object. + * + * @see Configurable#setNewBuiltinClassResolver(TemplateClassResolver) + * + * @since 2.3.17 + */ +public interface TemplateClassResolver { + + /** + * Simply calls {@link _ClassUtil#forName(String)}. + */ + TemplateClassResolver UNRESTRICTED_RESOLVER = new TemplateClassResolver() { + + @Override + public Class resolve(String className, Environment env, Template template) + throws TemplateException { + try { + return _ClassUtil.forName(className); + } catch (ClassNotFoundException e) { + throw new _MiscTemplateException(e, env); + } + } + + }; + + /** + * Doesn't allow resolving any classes. + */ + TemplateClassResolver ALLOWS_NOTHING_RESOLVER = new TemplateClassResolver() { + + @Override + public Class resolve(String className, Environment env, Template template) + throws TemplateException { + throw MessageUtil.newInstantiatingClassNotAllowedException(className, env); + } + + }; + + /** + * Gets a {@link Class} based on the class name. + * + * @param className the full-qualified class name + * @param env the environment in which the template executes + * @param template the template where the operation that require the + * class resolution resides in. This is <code>null</code> if the + * call doesn't come from a template. + * + * @throws TemplateException if the class can't be found or shouldn't be + * accessed from a template for security reasons. + */ + Class resolve(String className, Environment env, Template template) throws TemplateException; + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java b/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java new file mode 100644 index 0000000..fef06d6 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateCombinedMarkupOutputModel.java @@ -0,0 +1,50 @@ +/* + * 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; + +/** + * Stores combined markup to be printed; used with {@link CombinedMarkupOutputFormat}. + * + * @since 2.3.24 + */ +public final class TemplateCombinedMarkupOutputModel + extends CommonTemplateMarkupOutputModel<TemplateCombinedMarkupOutputModel> { + + private final CombinedMarkupOutputFormat outputFormat; + + /** + * See {@link CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, String)}. + * + * @param outputFormat + * The {@link CombinedMarkupOutputFormat} format this value is bound to. Because + * {@link CombinedMarkupOutputFormat} has no singleton, we have to pass it in, unlike with most other + * {@link CommonTemplateMarkupOutputModel}-s. + */ + TemplateCombinedMarkupOutputModel(String plainTextContent, String markupContent, + CombinedMarkupOutputFormat outputFormat) { + super(plainTextContent, markupContent); + this.outputFormat = outputFormat; + } + + @Override + public CombinedMarkupOutputFormat getOutputFormat() { + return outputFormat; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java new file mode 100644 index 0000000..583f064 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java @@ -0,0 +1,921 @@ +/* + * 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.io.Reader; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver; +import org.apache.freemarker.core.util._NullArgumentException; + +/** + * Used for customizing the configuration settings for individual {@link Template}-s (or rather groups of templates), + * relatively to the common setting values coming from the {@link Configuration}. This was designed with the standard + * template loading mechanism of FreeMarker in mind ({@link Configuration#getTemplate(String)} + * and {@link DefaultTemplateResolver}), though can also be reused for custom template loading and caching solutions. + * + * <p> + * Note on the {@code locale} setting: When used with the standard template loading/caching mechanism ( + * {@link Configuration#getTemplate(String)} and its overloads), localized lookup happens before the {@code locale} + * specified here could have effect. The {@code locale} will be only set in the template that the localized lookup has + * already found. + * + * <p> + * Note on the encoding setting {@code encoding}: See {@link #setEncoding(String)}. + * + * <p> + * Note that the result value of the reader methods (getter and "is" methods) is usually not useful unless the value of + * that setting was already set on this object. Otherwise you will get the value from the parent {@link Configuration}, + * or an {@link IllegalStateException} before this object is associated to a {@link Configuration}. + * + * <p> + * If you are using this class for your own template loading and caching solution, rather than with the standard one, + * you should be aware of a few more details: + * + * <ul> + * <li>This class implements both {@link Configurable} and {@link ParserConfiguration}. This means that it can influence + * both the template parsing phase and the runtime settings. For both aspects (i.e., {@link ParserConfiguration} and + * {@link Configurable}) to take effect, you have first pass this object to the {@link Template} constructor + * (this is where the {@link ParserConfiguration} interface is used), and then you have to call {@link #apply(Template)} + * on the resulting {@link Template} object (this is where the {@link Configurable} aspect is used). + * + * <li>{@link #apply(Template)} only change the settings that weren't yet set on the {@link Template} (but are inherited + * from the {@link Configuration}). This is primarily because if the template configures itself via the {@code #ftl} + * header, those values should have precedence. A consequence of this is that if you want to configure the same + * {@link Template} with multiple {@link TemplateConfiguration}-s, you either should merge them to a single one before + * that (with {@link #merge(TemplateConfiguration)}), or you have to apply them in reverse order of their intended + * precedence. + * </ul> + * + * @see Template#Template(String, String, Reader, Configuration, ParserConfiguration, String) + * + * @since 2.3.24 + */ +public final class TemplateConfiguration extends Configurable implements ParserConfiguration { + + private Integer tagSyntax; + private Integer namingConvention; + private Boolean whitespaceStripping; + private Integer autoEscapingPolicy; + private Boolean recognizeStandardFileExtensions; + private OutputFormat outputFormat; + private String encoding; + private Integer tabSize; + + /** + * Creates a new instance. The parent will be {@code null} initially, but it will + * be changed to the real parent {@link Configuration} when this object is added to the {@link Configuration}. (It's + * not allowed to add the same instance to multiple {@link Configuration}-s). + */ + public TemplateConfiguration() { + super((Configuration) null); + } + + /** + * Same as {@link #setParentConfiguration(Configuration)}. + */ + @Override + void setParent(Configurable cfg) { + _NullArgumentException.check("cfg", cfg); + if (!(cfg instanceof Configuration)) { + throw new IllegalArgumentException("The parent of a TemplateConfiguration can only be a Configuration"); + } + + Configurable parent = getParent(); + if (parent != null) { + if (parent != cfg) { + throw new IllegalStateException( + "This TemplateConfiguration is already associated with a different Configuration instance."); + } + return; + } + + super.setParent(cfg); + } + + /** + * Associates this instance with a {@link Configuration}; usually you don't call this, as it's called internally + * when this instance is added to a {@link Configuration}. This method can be called only once (except with the same + * {@link Configuration} parameter again, as that changes nothing anyway). + * + * @throws IllegalArgumentException + * if the argument is {@code null} or not a {@link Configuration} + * @throws IllegalStateException + * if this object is already associated to a different {@link Configuration} object, + * or if the {@code Configuration} has {@code #getIncompatibleImprovements()} less than 2.3.22 and + * this object tries to change any non-parser settings + */ + public void setParentConfiguration(Configuration cfg) { + setParent(cfg); + } + + /** + * Returns the parent {@link Configuration}, or {@code null} if none was associated yet. + */ + public Configuration getParentConfiguration() { + return (Configuration) getParent(); + } + + private Configuration getNonNullParentConfiguration() { + Configurable parent = getParent(); + if (parent == null) { + throw new IllegalStateException("The TemplateConfiguration wasn't associated with a Configuration yet."); + } + return (Configuration) parent; + } + + /** + * Set all settings in this {@link TemplateConfiguration} that were set in the parameter + * {@link TemplateConfiguration}, possibly overwriting the earlier value in this object. (A setting is said to be + * set in a {@link TemplateConfiguration} if it was explicitly set via a setter method, as opposed to be inherited.) + */ + public void merge(TemplateConfiguration tc) { + if (tc.isAPIBuiltinEnabledSet()) { + setAPIBuiltinEnabled(tc.isAPIBuiltinEnabled()); + } + if (tc.isArithmeticEngineSet()) { + setArithmeticEngine(tc.getArithmeticEngine()); + } + if (tc.isAutoEscapingPolicySet()) { + setAutoEscapingPolicy(tc.getAutoEscapingPolicy()); + } + if (tc.isAutoFlushSet()) { + setAutoFlush(tc.getAutoFlush()); + } + if (tc.isBooleanFormatSet()) { + setBooleanFormat(tc.getBooleanFormat()); + } + if (tc.isCustomDateFormatsSet()) { + setCustomDateFormats(mergeMaps( + isCustomDateFormatsSet() ? getCustomDateFormats() : null, tc.getCustomDateFormats(), false)); + } + if (tc.isCustomNumberFormatsSet()) { + setCustomNumberFormats(mergeMaps( + isCustomNumberFormatsSet() ? getCustomNumberFormats() : null, tc.getCustomNumberFormats(), false)); + } + if (tc.isDateFormatSet()) { + setDateFormat(tc.getDateFormat()); + } + if (tc.isDateTimeFormatSet()) { + setDateTimeFormat(tc.getDateTimeFormat()); + } + if (tc.isEncodingSet()) { + setEncoding(tc.getEncoding()); + } + if (tc.isLocaleSet()) { + setLocale(tc.getLocale()); + } + if (tc.isLogTemplateExceptionsSet()) { + setLogTemplateExceptions(tc.getLogTemplateExceptions()); + } + if (tc.isNamingConventionSet()) { + setNamingConvention(tc.getNamingConvention()); + } + if (tc.isNewBuiltinClassResolverSet()) { + setNewBuiltinClassResolver(tc.getNewBuiltinClassResolver()); + } + if (tc.isNumberFormatSet()) { + setNumberFormat(tc.getNumberFormat()); + } + if (tc.isObjectWrapperSet()) { + setObjectWrapper(tc.getObjectWrapper()); + } + if (tc.isOutputEncodingSet()) { + setOutputEncoding(tc.getOutputEncoding()); + } + if (tc.isOutputFormatSet()) { + setOutputFormat(tc.getOutputFormat()); + } + if (tc.isRecognizeStandardFileExtensionsSet()) { + setRecognizeStandardFileExtensions(tc.getRecognizeStandardFileExtensions()); + } + if (tc.isShowErrorTipsSet()) { + setShowErrorTips(tc.getShowErrorTips()); + } + if (tc.isSQLDateAndTimeTimeZoneSet()) { + setSQLDateAndTimeTimeZone(tc.getSQLDateAndTimeTimeZone()); + } + if (tc.isTagSyntaxSet()) { + setTagSyntax(tc.getTagSyntax()); + } + if (tc.isTemplateExceptionHandlerSet()) { + setTemplateExceptionHandler(tc.getTemplateExceptionHandler()); + } + if (tc.isTimeFormatSet()) { + setTimeFormat(tc.getTimeFormat()); + } + if (tc.isTimeZoneSet()) { + setTimeZone(tc.getTimeZone()); + } + if (tc.isURLEscapingCharsetSet()) { + setURLEscapingCharset(tc.getURLEscapingCharset()); + } + if (tc.isWhitespaceStrippingSet()) { + setWhitespaceStripping(tc.getWhitespaceStripping()); + } + if (tc.isTabSizeSet()) { + setTabSize(tc.getTabSize()); + } + if (tc.isLazyImportsSet()) { + setLazyImports(tc.getLazyImports()); + } + if (tc.isLazyAutoImportsSet()) { + setLazyAutoImports(tc.getLazyAutoImports()); + } + if (tc.isAutoImportsSet()) { + setAutoImports(mergeMaps(isAutoImportsSet() ? getAutoImports() : null, tc.getAutoImports(), true)); + } + if (tc.isAutoIncludesSet()) { + setAutoIncludes(mergeLists(isAutoIncludesSet() ? getAutoIncludes() : null, tc.getAutoIncludes())); + } + + tc.copyDirectCustomAttributes(this, true); + } + + /** + * Sets those settings of the {@link Template} which aren't yet set in the {@link Template} and are set in this + * {@link TemplateConfiguration}, leaves the other settings as is. A setting is said to be set in a + * {@link TemplateConfiguration} or {@link Template} if it was explicitly set via a setter method on that object, as + * opposed to be inherited from the {@link Configuration}. + * + * <p> + * Note that this method doesn't deal with settings that influence the parser, as those are already baked in at this + * point via the {@link ParserConfiguration}. + * + * <p> + * Note that the {@code encoding} setting of the {@link Template} counts as unset if it's {@code null}, + * even if {@code null} was set via {@link Template#setEncoding(String)}. + * + * @throws IllegalStateException + * If the parent configuration wasn't yet set. + */ + public void apply(Template template) { + Configuration cfg = getNonNullParentConfiguration(); + if (template.getConfiguration() != cfg) { + // This is actually not a problem right now, but for future BC we enforce this. + throw new IllegalArgumentException( + "The argument Template doesn't belong to the same Configuration as the TemplateConfiguration"); + } + + if (isAPIBuiltinEnabledSet() && !template.isAPIBuiltinEnabledSet()) { + template.setAPIBuiltinEnabled(isAPIBuiltinEnabled()); + } + if (isArithmeticEngineSet() && !template.isArithmeticEngineSet()) { + template.setArithmeticEngine(getArithmeticEngine()); + } + if (isAutoFlushSet() && !template.isAutoFlushSet()) { + template.setAutoFlush(getAutoFlush()); + } + if (isBooleanFormatSet() && !template.isBooleanFormatSet()) { + template.setBooleanFormat(getBooleanFormat()); + } + if (isCustomDateFormatsSet()) { + template.setCustomDateFormats( + mergeMaps(getCustomDateFormats(), template.getCustomDateFormatsWithoutFallback(), false)); + } + if (isCustomNumberFormatsSet()) { + template.setCustomNumberFormats( + mergeMaps(getCustomNumberFormats(), template.getCustomNumberFormatsWithoutFallback(), false)); + } + if (isDateFormatSet() && !template.isDateFormatSet()) { + template.setDateFormat(getDateFormat()); + } + if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) { + template.setDateTimeFormat(getDateTimeFormat()); + } + if (isEncodingSet() && template.getEncoding() == null) { + template.setEncoding(getEncoding()); + } + if (isLocaleSet() && !template.isLocaleSet()) { + template.setLocale(getLocale()); + } + if (isLogTemplateExceptionsSet() && !template.isLogTemplateExceptionsSet()) { + template.setLogTemplateExceptions(getLogTemplateExceptions()); + } + if (isNewBuiltinClassResolverSet() && !template.isNewBuiltinClassResolverSet()) { + template.setNewBuiltinClassResolver(getNewBuiltinClassResolver()); + } + if (isNumberFormatSet() && !template.isNumberFormatSet()) { + template.setNumberFormat(getNumberFormat()); + } + if (isObjectWrapperSet() && !template.isObjectWrapperSet()) { + template.setObjectWrapper(getObjectWrapper()); + } + if (isOutputEncodingSet() && !template.isOutputEncodingSet()) { + template.setOutputEncoding(getOutputEncoding()); + } + if (isShowErrorTipsSet() && !template.isShowErrorTipsSet()) { + template.setShowErrorTips(getShowErrorTips()); + } + if (isSQLDateAndTimeTimeZoneSet() && !template.isSQLDateAndTimeTimeZoneSet()) { + template.setSQLDateAndTimeTimeZone(getSQLDateAndTimeTimeZone()); + } + if (isTemplateExceptionHandlerSet() && !template.isTemplateExceptionHandlerSet()) { + template.setTemplateExceptionHandler(getTemplateExceptionHandler()); + } + if (isTimeFormatSet() && !template.isTimeFormatSet()) { + template.setTimeFormat(getTimeFormat()); + } + if (isTimeZoneSet() && !template.isTimeZoneSet()) { + template.setTimeZone(getTimeZone()); + } + if (isURLEscapingCharsetSet() && !template.isURLEscapingCharsetSet()) { + template.setURLEscapingCharset(getURLEscapingCharset()); + } + if (isLazyImportsSet() && !template.isLazyImportsSet()) { + template.setLazyImports(getLazyImports()); + } + if (isLazyAutoImportsSet() && !template.isLazyAutoImportsSet()) { + template.setLazyAutoImports(getLazyAutoImports()); + } + if (isAutoImportsSet()) { + // Regarding the order of the maps in the merge: + // - Existing template-level imports have precedence over those coming from the TC (just as with the others + // apply()-ed settings), thus for clashing import prefixes they must win. + // - Template-level imports count as more specific, and so come after the more generic ones from TC. + template.setAutoImports(mergeMaps(getAutoImports(), template.getAutoImportsWithoutFallback(), true)); + } + if (isAutoIncludesSet()) { + template.setAutoIncludes(mergeLists(getAutoIncludes(), template.getAutoIncludesWithoutFallback())); + } + + copyDirectCustomAttributes(template, false); + } + + /** + * See {@link Configuration#setTagSyntax(int)}. + */ + public void setTagSyntax(int tagSyntax) { + _TemplateAPI.valideTagSyntaxValue(tagSyntax); + this.tagSyntax = Integer.valueOf(tagSyntax); + } + + /** + * The getter pair of {@link #setTagSyntax(int)}. + */ + @Override + public int getTagSyntax() { + return tagSyntax != null ? tagSyntax.intValue() : getNonNullParentConfiguration().getTagSyntax(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isTagSyntaxSet() { + return tagSyntax != null; + } + + /** + * See {@link Configuration#setNamingConvention(int)}. + */ + public void setNamingConvention(int namingConvention) { + _TemplateAPI.validateNamingConventionValue(namingConvention); + this.namingConvention = Integer.valueOf(namingConvention); + } + + /** + * The getter pair of {@link #setNamingConvention(int)}. + */ + @Override + public int getNamingConvention() { + return namingConvention != null ? namingConvention.intValue() + : getNonNullParentConfiguration().getNamingConvention(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isNamingConventionSet() { + return namingConvention != null; + } + + /** + * See {@link Configuration#setWhitespaceStripping(boolean)}. + */ + public void setWhitespaceStripping(boolean whitespaceStripping) { + this.whitespaceStripping = Boolean.valueOf(whitespaceStripping); + } + + /** + * The getter pair of {@link #getWhitespaceStripping()}. + */ + @Override + public boolean getWhitespaceStripping() { + return whitespaceStripping != null ? whitespaceStripping.booleanValue() + : getNonNullParentConfiguration().getWhitespaceStripping(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isWhitespaceStrippingSet() { + return whitespaceStripping != null; + } + + /** + * Sets the output format of the template; see {@link Configuration#setAutoEscapingPolicy(int)} for more. + */ + public void setAutoEscapingPolicy(int autoEscapingPolicy) { + _TemplateAPI.validateAutoEscapingPolicyValue(autoEscapingPolicy); + this.autoEscapingPolicy = Integer.valueOf(autoEscapingPolicy); + } + + /** + * The getter pair of {@link #setAutoEscapingPolicy(int)}. + */ + @Override + public int getAutoEscapingPolicy() { + return autoEscapingPolicy != null ? autoEscapingPolicy.intValue() + : getNonNullParentConfiguration().getAutoEscapingPolicy(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isAutoEscapingPolicySet() { + return autoEscapingPolicy != null; + } + + /** + * Sets the output format of the template; see {@link Configuration#setOutputFormat(OutputFormat)} for more. + */ + public void setOutputFormat(OutputFormat outputFormat) { + _NullArgumentException.check("outputFormat", outputFormat); + this.outputFormat = outputFormat; + } + + /** + * The getter pair of {@link #setOutputFormat(OutputFormat)}. + */ + @Override + public OutputFormat getOutputFormat() { + return outputFormat != null ? outputFormat : getNonNullParentConfiguration().getOutputFormat(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isOutputFormatSet() { + return outputFormat != null; + } + + /** + * See {@link Configuration#setRecognizeStandardFileExtensions(boolean)}. + */ + public void setRecognizeStandardFileExtensions(boolean recognizeStandardFileExtensions) { + this.recognizeStandardFileExtensions = Boolean.valueOf(recognizeStandardFileExtensions); + } + + /** + * Getter pair of {@link #setRecognizeStandardFileExtensions(boolean)}. + */ + @Override + public boolean getRecognizeStandardFileExtensions() { + return recognizeStandardFileExtensions != null ? recognizeStandardFileExtensions.booleanValue() + : getNonNullParentConfiguration().getRecognizeStandardFileExtensions(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + */ + public boolean isRecognizeStandardFileExtensionsSet() { + return recognizeStandardFileExtensions != null; + } + + public String getEncoding() { + return encoding != null ? encoding : getNonNullParentConfiguration().getDefaultEncoding(); + } + + /** + * When the standard template loading/caching mechanism is used, this forces the charset used for reading the + * template "file", overriding everything but the encoding coming from the {@code #ftl} header. This setting + * overrides the locale-specific encodings set via {@link Configuration#setEncoding(java.util.Locale, String)}. It + * also overrides the {@code encoding} parameter of {@link Configuration#getTemplate(String, String)} (and of its + * overloads) and the {@code encoding} parameter of the {@code #include} directive. This works like that because + * specifying the encoding where you are requesting the template is error prone and deprecated. + * + * <p> + * If you are developing your own template loading/caching mechanism instead of the standard one, note that the + * above behavior is not guaranteed by this class alone; you have to ensure it. Also, read the note on + * {@code encoding} in the documentation of {@link #apply(Template)}. + */ + public void setEncoding(String encoding) { + _NullArgumentException.check("encoding", encoding); + this.encoding = encoding; + } + + public boolean isEncodingSet() { + return encoding != null; + } + + /** + * See {@link Configuration#setTabSize(int)}. + * + * @since 2.3.25 + */ + public void setTabSize(int tabSize) { + this.tabSize = Integer.valueOf(tabSize); + } + + /** + * Getter pair of {@link #setTabSize(int)}. + * + * @since 2.3.25 + */ + @Override + public int getTabSize() { + return tabSize != null ? tabSize.intValue() + : getNonNullParentConfiguration().getTabSize(); + } + + /** + * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}. + * + * @since 2.3.25 + */ + public boolean isTabSizeSet() { + return tabSize != null; + } + + /** + * Returns {@link Configuration#getIncompatibleImprovements()} from the parent {@link Configuration}. This mostly + * just exist to satisfy the {@link ParserConfiguration} interface. + * + * @throws IllegalStateException + * If the parent configuration wasn't yet set. + */ + @Override + public Version getIncompatibleImprovements() { + return getNonNullParentConfiguration().getIncompatibleImprovements(); + } + + + + @Override + public Locale getLocale() { + try { + return super.getLocale(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public TimeZone getTimeZone() { + try { + return super.getTimeZone(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public TimeZone getSQLDateAndTimeTimeZone() { + try { + return super.getSQLDateAndTimeTimeZone(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getNumberFormat() { + try { + return super.getNumberFormat(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public Map<String, ? extends TemplateNumberFormatFactory> getCustomNumberFormats() { + try { + return super.getCustomNumberFormats(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public TemplateNumberFormatFactory getCustomNumberFormat(String name) { + try { + return super.getCustomNumberFormat(name); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public boolean hasCustomFormats() { + try { + return super.hasCustomFormats(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getBooleanFormat() { + try { + return super.getBooleanFormat(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getTimeFormat() { + try { + return super.getTimeFormat(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getDateFormat() { + try { + return super.getDateFormat(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getDateTimeFormat() { + try { + return super.getDateTimeFormat(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public Map<String, ? extends TemplateDateFormatFactory> getCustomDateFormats() { + try { + return super.getCustomDateFormats(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public TemplateDateFormatFactory getCustomDateFormat(String name) { + try { + return super.getCustomDateFormat(name); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public TemplateExceptionHandler getTemplateExceptionHandler() { + try { + return super.getTemplateExceptionHandler(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public ArithmeticEngine getArithmeticEngine() { + try { + return super.getArithmeticEngine(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public ObjectWrapper getObjectWrapper() { + try { + return super.getObjectWrapper(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getOutputEncoding() { + try { + return super.getOutputEncoding(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String getURLEscapingCharset() { + try { + return super.getURLEscapingCharset(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public TemplateClassResolver getNewBuiltinClassResolver() { + try { + return super.getNewBuiltinClassResolver(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public boolean getAutoFlush() { + try { + return super.getAutoFlush(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public boolean getShowErrorTips() { + try { + return super.getShowErrorTips(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public boolean isAPIBuiltinEnabled() { + try { + return super.isAPIBuiltinEnabled(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public boolean getLogTemplateExceptions() { + try { + return super.getLogTemplateExceptions(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public boolean getLazyImports() { + try { + return super.getLazyImports(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public Boolean getLazyAutoImports() { + try { + return super.getLazyAutoImports(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public Map<String, String> getAutoImports() { + try { + return super.getAutoImports(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public List<String> getAutoIncludes() { + try { + return super.getAutoIncludes(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public String[] getCustomAttributeNames() { + try { + return super.getCustomAttributeNames(); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + @Override + public Object getCustomAttribute(String name) { + try { + return super.getCustomAttribute(name); + } catch (NullPointerException e) { + getNonNullParentConfiguration(); + throw e; + } + } + + private boolean hasAnyConfigurableSet() { + return + isAPIBuiltinEnabledSet() + || isArithmeticEngineSet() + || isAutoFlushSet() + || isAutoImportsSet() + || isAutoIncludesSet() + || isBooleanFormatSet() + || isCustomDateFormatsSet() + || isCustomNumberFormatsSet() + || isDateFormatSet() + || isDateTimeFormatSet() + || isLazyImportsSet() + || isLazyAutoImportsSet() + || isLocaleSet() + || isLogTemplateExceptionsSet() + || isNewBuiltinClassResolverSet() + || isNumberFormatSet() + || isObjectWrapperSet() + || isOutputEncodingSet() + || isShowErrorTipsSet() + || isSQLDateAndTimeTimeZoneSet() + || isTemplateExceptionHandlerSet() + || isTimeFormatSet() + || isTimeZoneSet() + || isURLEscapingCharsetSet(); + } + + private Map mergeMaps(Map m1, Map m2, boolean overwriteUpdatesOrder) { + if (m1 == null) return m2; + if (m2 == null) return m1; + if (m1.isEmpty()) return m2 != null ? m2 : m1; + if (m2.isEmpty()) return m1 != null ? m1 : m2; + + LinkedHashMap mergedM = new LinkedHashMap((m1.size() + m2.size()) * 4 / 3 + 1, 0.75f); + mergedM.putAll(m1); + for (Object m2Key : m2.keySet()) { + mergedM.remove(m2Key); // So that duplicate keys are moved after m1 keys + } + mergedM.putAll(m2); + return mergedM; + } + + private List<String> mergeLists(List<String> list1, List<String> list2) { + if (list1 == null) return list2; + if (list2 == null) return list1; + if (list1.isEmpty()) return list2 != null ? list2 : list1; + if (list2.isEmpty()) return list1 != null ? list1 : list2; + + ArrayList<String> mergedList = new ArrayList<>(list1.size() + list2.size()); + mergedList.addAll(list1); + mergedList.addAll(list2); + return mergedList; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java new file mode 100644 index 0000000..07ee8ee --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java @@ -0,0 +1,108 @@ +/* + * 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.text.DateFormat; +import java.util.Date; + +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModelException; + +/** + * Represents a date/time/dateTime format; used in templates for formatting and parsing with that format. This is + * similar to Java's {@link DateFormat}, but made to fit the requirements of FreeMarker. Also, it makes easier to define + * formats that can't be represented with Java's existing {@link DateFormat} implementations. + * + * <p> + * Implementations need not be thread-safe if the {@link TemplateNumberFormatFactory} doesn't recycle them among + * different {@link Environment}-s. As far as FreeMarker's concerned, instances are bound to a single + * {@link Environment}, and {@link Environment}-s are thread-local objects. + * + * @since 2.3.24 + */ +public abstract class TemplateDateFormat extends TemplateValueFormat { + + /** + * @param dateModel + * The date/time/dateTime to format; not {@code null}. Most implementations will just work with the return value of + * {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of + * a custom {@link TemplateDateModel} implementation. + * + * @return The date/time/dateTime as text, with no escaping (like no HTML escaping); can't be {@code null}. + * + * @throws TemplateValueFormatException + * When a problem occurs during the formatting of the value. Notable subclass: + * {@link UnknownDateTypeFormattingUnsupportedException} + * @throws TemplateModelException + * Exception thrown by the {@code dateModel} object when calling its methods. + */ + public abstract String formatToPlainText(TemplateDateModel dateModel) + throws TemplateValueFormatException, TemplateModelException; + + /** + * Formats the model to markup instead of to plain text if the result markup will be more than just plain text + * escaped, otherwise falls back to formatting to plain text. If the markup result would be just the result of + * {@link #formatToPlainText(TemplateDateModel)} escaped, it must return the {@link String} that + * {@link #formatToPlainText(TemplateDateModel)} does. + * + * <p>The implementation in {@link TemplateDateFormat} simply calls {@link #formatToPlainText(TemplateDateModel)}. + * + * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}. + */ + public Object format(TemplateDateModel dateModel) throws TemplateValueFormatException, TemplateModelException { + return formatToPlainText(dateModel); + } + + /** + * Parsers a string to date/time/datetime, according to this format. Some format implementations may throw + * {@link ParsingNotSupportedException} here. + * + * @param s + * The string to parse + * @param dateType + * The expected date type of the result. Not all {@link TemplateDateFormat}-s will care about this; + * though those who return a {@link TemplateDateModel} instead of {@link Date} often will. When strings + * are parsed via {@code ?date}, {@code ?time}, or {@code ?datetime}, then this parameter is + * {@link TemplateDateModel#DATE}, {@link TemplateDateModel#TIME}, or {@link TemplateDateModel#DATETIME}, + * respectively. This parameter rarely if ever {@link TemplateDateModel#UNKNOWN}, but the implementation + * that cares about this parameter should be prepared for that. If nothing else, it should throw + * {@link UnknownDateTypeParsingUnsupportedException} then. + * + * @return The interpretation of the text either as a {@link Date} or {@link TemplateDateModel}. Typically, a + * {@link Date}. {@link TemplateDateModel} is used if you have to attach some application-specific + * meta-information thats also extracted during {@link #formatToPlainText(TemplateDateModel)} (so if you format + * something and then parse it, you get back an equivalent result). It can't be {@code null}. Known issue + * (at least in FTL 2): {@code ?date}/{@code ?time}/{@code ?datetime}, when not invoked as a method, can't + * return the {@link TemplateDateModel}, only the {@link Date} from inside it, hence the additional + * application-specific meta-info will be lost. + */ + public abstract Object parse(String s, int dateType) throws TemplateValueFormatException; + + /** + * Tells if this formatter should be re-created if the locale changes. + */ + public abstract boolean isLocaleBound(); + + /** + * Tells if this formatter should be re-created if the time zone changes. Currently always {@code true}. + */ + public abstract boolean isTimeZoneBound(); + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java new file mode 100644 index 0000000..6a6fb14 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java @@ -0,0 +1,91 @@ +/* + * 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.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.freemarker.core.model.TemplateDateModel; + +/** + * Factory for a certain kind of date/time/dateTime formatting ({@link TemplateDateFormat}). Usually a singleton + * (one-per-VM or one-per-{@link Configuration}), and so must be thread-safe. + * + * @see Configurable#setCustomDateFormats(java.util.Map) + * + * @since 2.3.24 + */ +public abstract class TemplateDateFormatFactory extends TemplateValueFormatFactory { + + /** + * Returns a formatter for the given parameters. + * + * <p> + * The returned formatter can be a new instance or a reused (cached) instance. Note that {@link Environment} itself + * caches the returned instances, though that cache is lost with the {@link Environment} (i.e., when the top-level + * template execution ends), also it might flushes lot of entries if the locale or time zone is changed during + * template execution. So caching on the factory level is still useful, unless creating the formatters is + * sufficiently cheap. + * + * @param params + * The string that further describes how the format should look. For example, when the + * {@link Configurable#getDateFormat() dateFormat} is {@code "@fooBar 1, 2"}, then it will be + * {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the + * {@link TemplateDateFormatFactory} implementation. Not {@code null}, often an empty string. + * @param dateType + * {@link TemplateDateModel#DATE}, {@link TemplateDateModel#TIME}, {@link TemplateDateModel#DATETIME} or + * {@link TemplateDateModel#UNKNOWN}. Supporting {@link TemplateDateModel#UNKNOWN} is not necessary, in + * which case the method should throw an {@link UnknownDateTypeFormattingUnsupportedException} exception. + * @param locale + * The locale to format for. Not {@code null}. The resulting format should be bound to this locale + * forever (i.e. locale changes in the {@link Environment} must not be followed). + * @param timeZone + * The time zone to format for. Not {@code null}. The resulting format must be bound to this time zone + * forever (i.e. time zone changes in the {@link Environment} must not be followed). + * @param zonelessInput + * Indicates that the input Java {@link Date} is not from a time zone aware source. When this is + * {@code true}, the formatters shouldn't override the time zone provided to its constructor (most + * formatters don't do that anyway), and it shouldn't show the time zone, if it can hide it (like a + * {@link SimpleDateFormat} pattern-based formatter may can't do that, as the pattern prescribes what to + * show). + * <p> + * As of FreeMarker 2.3.21, this is {@code true} exactly when the date is an SQL "date without time of + * the day" (i.e., a {@link java.sql.Date java.sql.Date}) or an SQL "time of the day" value (i.e., a + * {@link java.sql.Time java.sql.Time}, although this rule can change in future, depending on + * configuration settings and such, so you shouldn't rely on this rule, just accept what this parameter + * says. + * @param env + * The runtime environment from which the formatting was called. This is mostly meant to be used for + * {@link Environment#setCustomState(Object, Object)}/{@link Environment#getCustomState(Object)}. + * + * @throws TemplateValueFormatException + * If any problem occurs while parsing/getting the format. Notable subclasses: + * {@link InvalidFormatParametersException} if {@code params} is malformed; + * {@link UnknownDateTypeFormattingUnsupportedException} if {@code dateType} is + * {@link TemplateDateModel#UNKNOWN} and that's unsupported by this factory. + */ + public abstract TemplateDateFormat get( + String params, + int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput, + Environment env) + throws TemplateValueFormatException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java b/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java new file mode 100644 index 0000000..cbe745c --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateElementArrayBuilder.java @@ -0,0 +1,102 @@ +/* + * 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._CollectionUtil; + +/** + * Holds an buffer (array) of {@link _ASTElement}-s with the count of the utilized items in it. The un-utilized tail + * of the array must only contain {@code null}-s. + * + * @since 2.3.24 + */ +class TemplateElements { + + static final TemplateElements EMPTY = new TemplateElements(null, 0); + + private final _ASTElement[] buffer; + private final int count; + + /** + * @param buffer + * The buffer; {@code null} exactly if {@code count} is 0. + * @param count + * The number of utilized buffer elements; if 0, then {@code null} must be {@code null}. + */ + TemplateElements(_ASTElement[] buffer, int count) { + /* + // Assertion: + if (count == 0 && buffer != null) { + throw new IllegalArgumentException(); + } + */ + + this.buffer = buffer; + this.count = count; + } + + _ASTElement[] getBuffer() { + return buffer; + } + + int getCount() { + return count; + } + + _ASTElement getFirst() { + return buffer != null ? buffer[0] : null; + } + + _ASTElement getLast() { + return buffer != null ? buffer[count - 1] : null; + } + + /** + * Used for some backward compatibility hacks. + */ + _ASTElement asSingleElement() { + if (count == 0) { + return new ASTStaticText(_CollectionUtil.EMPTY_CHAR_ARRAY, false); + } else { + _ASTElement first = buffer[0]; + if (count == 1) { + return first; + } else { + ASTImplicitParent mixedContent = new ASTImplicitParent(); + mixedContent.setChildren(this); + mixedContent.setLocation(first.getTemplate(), first, getLast()); + return mixedContent; + } + } + } + + /** + * Used for some backward compatibility hacks. + */ + ASTImplicitParent asMixedContent() { + ASTImplicitParent mixedContent = new ASTImplicitParent(); + if (count != 0) { + _ASTElement first = buffer[0]; + mixedContent.setChildren(this); + mixedContent.setLocation(first.getTemplate(), first, getLast()); + } + return mixedContent; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java b/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java new file mode 100644 index 0000000..bd9d473 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateElementsToVisit.java @@ -0,0 +1,48 @@ +/* + * 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.Collection; +import java.util.Collections; + +/** + * Used as the return value of {@link _ASTElement#accept(Environment)} when the invoked element has nested elements + * to invoke. It would be more natural to invoke child elements before returning from + * {@link _ASTElement#accept(Environment)}, however, if there's nothing to do after the child elements were invoked, + * that would mean wasting stack space. + * + * @since 2.3.24 + */ +class TemplateElementsToVisit { + + private final Collection<_ASTElement> templateElements; + + TemplateElementsToVisit(Collection<_ASTElement> templateElements) { + this.templateElements = null != templateElements ? templateElements : Collections.<_ASTElement> emptyList(); + } + + TemplateElementsToVisit(_ASTElement nestedBlock) { + this(Collections.singleton(nestedBlock)); + } + + Collection<_ASTElement> getTemplateElements() { + return templateElements; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateException.java b/src/main/java/org/apache/freemarker/core/TemplateException.java index d1d9a02..4ed9beb 100644 --- a/src/main/java/org/apache/freemarker/core/TemplateException.java +++ b/src/main/java/org/apache/freemarker/core/TemplateException.java @@ -27,14 +27,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Method; -import org.apache.freemarker.core.ast.Environment; -import org.apache.freemarker.core.ast.Expression; -import org.apache.freemarker.core.ast.InvalidReferenceException; -import org.apache.freemarker.core.ast.ParseException; -import org.apache.freemarker.core.ast.TemplateElement; -import org.apache.freemarker.core.ast.TemplateObject; -import org.apache.freemarker.core.ast._CoreAPI; -import org.apache.freemarker.core.ast._ErrorDescriptionBuilder; import org.apache.freemarker.core.util._CollectionUtil; /** @@ -49,8 +41,8 @@ public class TemplateException extends Exception { // Set in constructor: private transient _ErrorDescriptionBuilder descriptionBuilder; private final transient Environment env; - private final transient Expression blamedExpression; - private transient TemplateElement[] ftlInstructionStackSnapshot; + private final transient ASTExpression blamedExpression; + private transient _ASTElement[] ftlInstructionStackSnapshot; // Calculated on demand: private String renderedFtlInstructionStackSnapshot; // clalc. from ftlInstructionStackSnapshot @@ -140,7 +132,7 @@ public class TemplateException extends Exception { * with "template element" granularity, and this can be used to point to the expression inside the * template element. */ - protected TemplateException(Throwable cause, Environment env, Expression blamedExpr, + protected TemplateException(Throwable cause, Environment env, ASTExpression blamedExpr, _ErrorDescriptionBuilder descriptionBuilder) { this(null, cause, env, blamedExpr, descriptionBuilder); } @@ -148,7 +140,7 @@ public class TemplateException extends Exception { private TemplateException( String renderedDescription, Throwable cause, - Environment env, Expression blamedExpression, + Environment env, ASTExpression blamedExpression, _ErrorDescriptionBuilder descriptionBuilder) { // Note: Keep this constructor lightweight. @@ -194,7 +186,7 @@ public class TemplateException extends Exception { synchronized (lock) { if (!positionsCalculated) { // The expressions is the argument of the template element, so we prefer it as it's more specific. - TemplateObject templateObject = blamedExpression != null + ASTNode templateObject = blamedExpression != null ? blamedExpression : ( ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length != 0 @@ -284,7 +276,7 @@ public class TemplateException extends Exception { } } - private TemplateElement getFailingInstruction() { + private _ASTElement getFailingInstruction() { if (ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length > 0) { return ftlInstructionStackSnapshot[0]; } else { @@ -539,7 +531,7 @@ public class TemplateException extends Exception { } } - Expression getBlamedExpression() { + ASTExpression getBlamedExpression() { return blamedExpression; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java index ef871c1..baec030 100644 --- a/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java +++ b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java @@ -23,9 +23,6 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; -import org.apache.freemarker.core.ast.Configurable; -import org.apache.freemarker.core.ast.Environment; -import org.apache.freemarker.core.ast.StopException; import org.apache.freemarker.core.util._StringUtil; /** http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java new file mode 100644 index 0000000..c43d7f2 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java @@ -0,0 +1,76 @@ +/* + * 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.Date; + +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; + +/** + * Utility classes for implementing {@link TemplateValueFormat}-s. + * + * @since 2.3.24 + */ +public final class TemplateFormatUtil { + + private TemplateFormatUtil() { + // Not meant to be instantiated + } + + public static void checkHasNoParameters(String params) throws InvalidFormatParametersException + { + if (params.length() != 0) { + throw new InvalidFormatParametersException( + "This number format doesn't support any parameters."); + } + } + + /** + * Utility method to extract the {@link Number} from an {@link TemplateNumberModel}, and throws + * {@link TemplateModelException} with a standard error message if that's {@code null}. {@link TemplateNumberModel} + * that store {@code null} are in principle not allowed, and so are considered to be bugs in the + * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation. + */ + public static Number getNonNullNumber(TemplateNumberModel numberModel) + throws TemplateModelException, UnformattableValueException { + Number number = numberModel.getAsNumber(); + if (number == null) { + throw EvalUtil.newModelHasStoredNullException(Number.class, numberModel, null); + } + return number; + } + + /** + * Utility method to extract the {@link Date} from an {@link TemplateDateModel}, and throw + * {@link TemplateModelException} with a standard error message if that's {@code null}. {@link TemplateDateModel} + * that store {@code null} are in principle not allowed, and so are considered to be bugs in the + * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation. + */ + public static Date getNonNullDate(TemplateDateModel dateModel) throws TemplateModelException { + Date date = dateModel.getAsDate(); + if (date == null) { + throw EvalUtil.newModelHasStoredNullException(Date.class, dateModel, null); + } + return date; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java b/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java new file mode 100644 index 0000000..f4acd01 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateHTMLOutputModel.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * Stores HTML markup to be printed; used with {@link HTMLOutputFormat}. + * + * @since 2.3.24 + */ +public final class TemplateHTMLOutputModel extends CommonTemplateMarkupOutputModel<TemplateHTMLOutputModel> { + + /** + * See {@link CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, String)}. + */ + TemplateHTMLOutputModel(String plainTextContent, String markupContent) { + super(plainTextContent, markupContent); + } + + @Override + public HTMLOutputFormat getOutputFormat() { + return HTMLOutputFormat.INSTANCE; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java b/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java new file mode 100644 index 0000000..1d4c5af --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateMarkupOutputModel.java @@ -0,0 +1,52 @@ +/* + * 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.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateScalarModel; + +/** + * "markup output" template language data-type; stores markup (some kind of "rich text" / structured format, as opposed + * to plain text) that meant to be printed as template output. This type is related to the {@link OutputFormat} + * mechanism. Values of this kind are exempt from {@link OutputFormat}-based automatic escaping. + * + * <p> + * Each implementation of this type has a {@link OutputFormat} subclass pair, whose singleton instance is returned by + * {@link #getOutputFormat()}. See more about how markup output values work at {@link OutputFormat}. + * + * <p> + * Note that {@link TemplateMarkupOutputModel}-s are by design not treated like {@link TemplateScalarModel}-s, and so + * the implementations of this interface usually shouldn't implement {@link TemplateScalarModel}. (Because, operations + * applicable on plain strings, like converting to upper case, substringing, etc., can corrupt markup.) If the template + * author wants to pass in the "source" of the markup as string somewhere, he should use {@code ?markup_string}. + * + * @param <MO> + * Refers to the interface's own type, which is useful in interfaces that extend + * {@link TemplateMarkupOutputModel} (Java Generics trick). + * + * @since 2.3.24 + */ +public interface TemplateMarkupOutputModel<MO extends TemplateMarkupOutputModel<MO>> extends TemplateModel { + + /** + * Returns the singleton {@link OutputFormat} object that implements the operations for the "markup output" value. + */ + MarkupOutputFormat<MO> getOutputFormat(); + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java new file mode 100644 index 0000000..f4f4e70 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java @@ -0,0 +1,90 @@ +/* + * 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.text.NumberFormat; + +import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateNumberModel; + +/** + * Represents a number format; used in templates for formatting and parsing with that format. This is similar to Java's + * {@link NumberFormat}, but made to fit the requirements of FreeMarker. Also, it makes easier to define formats that + * can't be represented with Java's existing {@link NumberFormat} implementations. + * + * <p> + * Implementations need not be thread-safe if the {@link TemplateNumberFormatFactory} doesn't recycle them among + * different {@link Environment}-s. As far as FreeMarker's concerned, instances are bound to a single + * {@link Environment}, and {@link Environment}-s are thread-local objects. + * + * @since 2.3.24 + */ +public abstract class TemplateNumberFormat extends TemplateValueFormat { + + /** + * @param numberModel + * The number to format; not {@code null}. Most implementations will just work with the return value of + * {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of + * a custom {@link TemplateDateModel} implementation. + * + * @return The number as text, with no escaping (like no HTML escaping); can't be {@code null}. + * + * @throws TemplateValueFormatException + * If any problem occurs while parsing/getting the format. Notable subclass: + * {@link UnformattableValueException}. + * @throws TemplateModelException + * Exception thrown by the {@code dateModel} object when calling its methods. + */ + public abstract String formatToPlainText(TemplateNumberModel numberModel) + throws TemplateValueFormatException, TemplateModelException; + + /** + * Formats the model to markup instead of to plain text if the result markup will be more than just plain text + * escaped, otherwise falls back to formatting to plain text. If the markup result would be just the result of + * {@link #formatToPlainText(TemplateNumberModel)} escaped, it must return the {@link String} that + * {@link #formatToPlainText(TemplateNumberModel)} does. + * + * <p> + * The implementation in {@link TemplateNumberFormat} simply calls {@link #formatToPlainText(TemplateNumberModel)}. + * + * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}. + */ + public Object format(TemplateNumberModel numberModel) + throws TemplateValueFormatException, TemplateModelException { + return formatToPlainText(numberModel); + } + + /** + * Tells if this formatter should be re-created if the locale changes. + */ + public abstract boolean isLocaleBound(); + + /** + * This method is reserved for future purposes; currently it always throws {@link ParsingNotSupportedException}. We + * don't yet support number parsing with {@link TemplateNumberFormat}-s, because currently FTL parses strings to + * number with the {@link ArithmeticEngine} ({@link TemplateNumberFormat} were only introduced in 2.3.24). If it + * will be support, it will be similar to {@link TemplateDateFormat#parse(String, int)}. + */ + public final Object parse(String s) throws TemplateValueFormatException { + throw new ParsingNotSupportedException("Number formats currenly don't support parsing"); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java new file mode 100644 index 0000000..4decb91 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java @@ -0,0 +1,62 @@ +/* + * 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.Locale; + +/** + * Factory for a certain kind of number formatting ({@link TemplateNumberFormat}). Usually a singleton (one-per-VM or + * one-per-{@link Configuration}), and so must be thread-safe. + * + * @see Configurable#setCustomNumberFormats(java.util.Map) + * + * @since 2.3.24 + */ +public abstract class TemplateNumberFormatFactory extends TemplateValueFormatFactory { + + /** + * Returns a formatter for the given parameters. + * + * <p> + * The returned formatter can be a new instance or a reused (cached) instance. Note that {@link Environment} itself + * caches the returned instances, though that cache is lost with the {@link Environment} (i.e., when the top-level + * template execution ends), also it might flushes lot of entries if the locale or time zone is changed during + * template execution. So caching on the factory level is still useful, unless creating the formatters is + * sufficiently cheap. + * + * @param params + * The string that further describes how the format should look. For example, when the + * {@link Configurable#getNumberFormat() numberFormat} is {@code "@fooBar 1, 2"}, then it will be + * {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the + * {@link TemplateNumberFormatFactory} implementation. Not {@code null}, often an empty string. + * @param locale + * The locale to format for. Not {@code null}. The resulting format must be bound to this locale + * forever (i.e. locale changes in the {@link Environment} must not be followed). + * @param env + * The runtime environment from which the formatting was called. This is mostly meant to be used for + * {@link Environment#setCustomState(Object, Object)}/{@link Environment#getCustomState(Object)}. + * + * @throws TemplateValueFormatException + * if any problem occurs while parsing/getting the format. Notable subclasses: + * {@link InvalidFormatParametersException} if the {@code params} is malformed. + */ + public abstract TemplateNumberFormat get(String params, Locale locale, Environment env) + throws TemplateValueFormatException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java b/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java new file mode 100644 index 0000000..f49a7b5 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplatePostProcessor.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Note yet public; will change in 2.4 (as it has to process {@code UnboundTemplate}-s). + */ +abstract class TemplatePostProcessor { + + public abstract void postProcess(Template e) throws TemplatePostProcessorException; + + // TODO: getPriority, getPhase, getMustBeBefore, getMustBeAfter + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java b/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java new file mode 100644 index 0000000..1904aa5 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplatePostProcessorException.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * Not yet public; subject to change. + */ +class TemplatePostProcessorException extends Exception { + + public TemplatePostProcessorException(String message, Throwable cause) { + super(message, cause); + } + + public TemplatePostProcessorException(String message) { + super(message); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7d784b2b/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java b/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java new file mode 100644 index 0000000..008737a --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateRTFOutputModel.java @@ -0,0 +1,40 @@ +/* + * 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; + +/** + * Stores RTF markup to be printed; used with {@link RTFOutputFormat}. + * + * @since 2.3.24 + */ +public final class TemplateRTFOutputModel extends CommonTemplateMarkupOutputModel<TemplateRTFOutputModel> { + + /** + * See {@link CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, String)}. + */ + TemplateRTFOutputModel(String plainTextContent, String markupContent) { + super(plainTextContent, markupContent); + } + + @Override + public RTFOutputFormat getOutputFormat() { + return RTFOutputFormat.INSTANCE; + } + +}