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 B6547200C2C for ; Fri, 17 Feb 2017 00:08:48 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id B4A63160B61; Thu, 16 Feb 2017 23:08:48 +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 73342160B6F for ; Fri, 17 Feb 2017 00:08:46 +0100 (CET) Received: (qmail 56229 invoked by uid 500); 16 Feb 2017 23:08:45 -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 56220 invoked by uid 99); 16 Feb 2017 23:08:45 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 16 Feb 2017 23:08:45 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id 12094183A68 for ; Thu, 16 Feb 2017 23:08:45 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -6.218 X-Spam-Level: X-Spam-Status: No, score=-6.218 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=-2.999, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id wu_GQ9p-J_Hc for ; Thu, 16 Feb 2017 23:08:31 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id 0644D5FB62 for ; Thu, 16 Feb 2017 23:08:26 +0000 (UTC) Received: (qmail 55458 invoked by uid 99); 16 Feb 2017 23:08:26 -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, 16 Feb 2017 23:08:26 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 160FFDFF80; Thu, 16 Feb 2017 23:08:26 +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, 16 Feb 2017 23:08:29 -0000 Message-Id: <90e7eae289374e6fb5245b7738942fd2@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [04/54] [partial] incubator-freemarker git commit: Top level package name change to org.apache.freemarker, and some of of the internal package structure changes. Other smaller cleanup. To be continued... archived-at: Thu, 16 Feb 2017 23:08:48 -0000 http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/Template.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/Template.java b/src/main/java/org/apache/freemarker/core/Template.java new file mode 100644 index 0000000..9f1ded1 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/Template.java @@ -0,0 +1,1093 @@ +/* + * 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.BufferedReader; +import java.io.FilterReader; +import java.io.IOException; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.apache.freemarker.core.ast.BugException; +import org.apache.freemarker.core.ast.Configurable; +import org.apache.freemarker.core.ast.Environment; +import org.apache.freemarker.core.ast.FMParser; +import org.apache.freemarker.core.ast.LibraryLoad; +import org.apache.freemarker.core.ast.Macro; +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.TemplateConfiguration; +import org.apache.freemarker.core.ast.TemplateElement; +import org.apache.freemarker.core.ast.TemplateSpecifiedEncodingHandler; +import org.apache.freemarker.core.ast.TextBlock; +import org.apache.freemarker.core.ast.TokenMgrError; +import org.apache.freemarker.core.ast._CoreAPI; +import org.apache.freemarker.core.debug.impl.DebuggerService; +import org.apache.freemarker.core.model.ObjectWrapper; +import org.apache.freemarker.core.model.TemplateHashModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateNodeModel; +import org.apache.freemarker.core.model.impl.SimpleHash; +import org.apache.freemarker.core.templateresolver.TemplateCache; +import org.apache.freemarker.core.templateresolver.TemplateLoader; +import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy; +import org.apache.freemarker.core.util.NullArgumentException; + +/** + *

+ * Stores an already parsed template, ready to be processed (rendered) for unlimited times, possibly from multiple + * threads. + * + *

+ * Typically, you will use {@link Configuration#getTemplate(String)} to create/get {@link Template} objects, so you + * don't construct them directly. But you can also construct a template from a {@link Reader} or a {@link String} that + * contains the template source code. But then it's important to know that while the resulting {@link Template} is + * efficient for later processing, creating a new {@link Template} itself is relatively expensive. So try to re-use + * {@link Template} objects if possible. {@link Configuration#getTemplate(String)} (and its overloads) does that + * (caching {@link Template}-s) for you, but the constructor of course doesn't, so it's up to you to solve then. + * + *

+ * Objects of this class meant to be handled as immutable and thus thread-safe. However, it has some setter methods for + * changing FreeMarker settings. Those must not be used while the template is being processed, or if the template object + * is already accessible from multiple threads. If some templates need different settings that those coming from the + * shared {@link Configuration}, and you are using {@link Configuration#getTemplate(String)} (or its overloads), then + * use {@link Configuration#setTemplateConfigurations(org.apache.freemarker.core.templateresolver.TemplateConfigurationFactory)} to achieve that. + */ +public class Template extends Configurable { + public static final String DEFAULT_NAMESPACE_PREFIX = "D"; + public static final String NO_NS_PREFIX = "N"; + + private static final int READER_BUFFER_SIZE = 8192; + + /** This is only non-null during parsing. It's used internally to make some information available through the + * Template API-s earlier than the parsing was finished. */ + private transient FMParser parser; + + private Map macros = new HashMap(); + private List imports = new Vector(); + private TemplateElement rootElement; + private String encoding, defaultNS; + private Object customLookupCondition; + private int actualTagSyntax; + private int actualNamingConvention; + private boolean autoEscaping; + private OutputFormat outputFormat; + private final String name; + private final String sourceName; + private final ArrayList lines = new ArrayList(); + private final ParserConfiguration parserConfiguration; + private Map prefixToNamespaceURILookup = new HashMap(); + private Map namespaceURIToPrefixLookup = new HashMap(); + private Version templateLanguageVersion; + + /** + * A prime constructor to which all other constructors should + * delegate directly or indirectly. + */ + private Template(String name, String sourceName, Configuration cfg, ParserConfiguration customParserConfiguration) { + super(toNonNull(cfg)); + this.name = name; + this.sourceName = sourceName; + this.templateLanguageVersion = normalizeTemplateLanguageVersion(toNonNull(cfg).getIncompatibleImprovements()); + this.parserConfiguration = customParserConfiguration != null ? customParserConfiguration : getConfiguration(); + } + + private static Configuration toNonNull(Configuration cfg) { + return cfg != null ? cfg : Configuration.getDefaultConfiguration(); + } + + /** + * Same as {@link #Template(String, String, Reader, Configuration)} with {@code null} {@code sourceName} parameter. + */ + public Template(String name, Reader reader, Configuration cfg) throws IOException { + this(name, null, reader, cfg); + } + + /** + * Convenience constructor for {@link #Template(String, Reader, Configuration) + * Template(name, new StringReader(reader), cfg)}. + * + * @since 2.3.20 + */ + public Template(String name, String sourceCode, Configuration cfg) throws IOException { + this(name, new StringReader(sourceCode), cfg); + } + + /** + * Convenience constructor for {@link #Template(String, String, Reader, Configuration, String) Template(name, null, + * reader, cfg, encoding)}. + */ + public Template(String name, Reader reader, Configuration cfg, String encoding) throws IOException { + this(name, null, reader, cfg, encoding); + } + + /** + * Constructs a template from a character stream. Note that this is a relatively expensive operation; where higher + * performance matters, you should re-use (cache) {@link Template} instances instead of re-creating them from the + * same source again and again. ({@link Configuration#getTemplate(String) and its overloads already do such reuse.}) + * + * @param name + * The path of the template file relatively to the (virtual) directory that you use to store the + * templates (except if {@link #Template(String, String, Reader, Configuration, String) sourceName} + * differs from it). Shouldn't start with {@code '/'}. Should use {@code '/'}, not {@code '\'}. Check + * {@link #getName()} to see how the name will be used. The name should be independent of the actual + * storage mechanism and physical location as far as possible. Even when the templates are stored + * straightforwardly in real files (they often aren't; see {@link TemplateLoader}), the name shouldn't be + * an absolute file path. Like if the template is stored in {@code "/www/templates/forum/main.ftl"}, and + * you are using {@code "/www/templates/"} as the template root directory via + * {@link Configuration#setDirectoryForTemplateLoading(java.io.File)}, then the template name will be + * {@code "forum/main.ftl"}. The name can be {@code null} (should be used for template made on-the-fly + * instead of being loaded from somewhere), in which case relative paths in it will be relative to + * the template root directory (and here again, it's the {@link TemplateLoader} that knows what that + * "physically" means). + * @param sourceName + * See {@link #getSourceName()} for the meaning. Can be {@code null}, in which case + * {@link #getSourceName()} will return the same as {@link #getName()}. + * @param reader + * The character stream to read from. The {@link Reader} is not closed by this method (unlike + * in FreeMarker 2.x.x), so be sure that it's closed somewhere. (Except of course, readers like + * {@link StringReader} need not be closed.) The {@link Reader} need not be buffered, because this + * method ensures that it will be read in few kilobyte chunks, not byte by byte. + * @param cfg + * The Configuration object that this Template is associated with. If this is {@code null}, the "default" + * {@link Configuration} object is used, which is highly discouraged, because it can easily lead to + * erroneous, unpredictable behavior. (See more {@link Configuration#getDefaultConfiguration() here...}) + * + * @since 2.3.22 + */ + public Template( + String name, String sourceName, Reader reader, Configuration cfg) throws IOException { + this(name, sourceName, reader, cfg, null); + } + + /** + * Same as {@link #Template(String, String, Reader, Configuration)}, but also specifies the template's encoding (not + * recommended). + * + * @param encoding + * This is the encoding that we are supposed to be using. At the first glance it's unnecessary because we + * already have a {@link Reader} (so decoding with the charset has already happened), however, if this is + * non-{@code null} and there's an {@code #ftl} header with {@code encoding} parameter, they must match, + * or else a {@link WrongEncodingException} is thrown. Thus, it should be set if to decode the template, + * we were using an encoding (a charset), otherwise it should be {@code null}. It's also kept as + * meta-info (returned by {@link #getEncoding()}). It also has an impact when {@code #include}-ing or + * {@code #import}-ing another template from this template, as its default encoding will be this. But + * this behavior of said directives is considered to be harmful, and will be probably phased out. + * + * @since 2.3.22 + */ + public Template( + String name, String sourceName, Reader reader, Configuration cfg, String encoding) throws IOException { + this(name, sourceName, reader, cfg, null, encoding); + } + + /** + * Same as {@link #Template(String, String, Reader, Configuration, String)}, but also specifies a + * {@link TemplateConfiguration}. This is mostly meant to be used by FreeMarker internally, but advanced users might + * still find this useful. + * + * @param customParserConfiguration + * Overrides the parsing related configuration settings of the {@link Configuration} parameter; can be + * {@code null}. This is useful as the {@link Configuration} is normally a singleton shared by all + * templates, and so it's not good for specifying template-specific settings. (While {@link Template} + * itself has methods to specify settings just for that template, those don't influence the parsing, and + * you only have opportunity to call them after the parsing anyway.) This objects is often a + * {@link TemplateConfiguration} whose parent is the {@link Configuration} parameter, and then it + * practically just overrides some of the parser settings, as the others are inherited from the + * {@link Configuration}. Note that if this is a {@link TemplateConfiguration}, you will also want to + * call {@link TemplateConfiguration#apply(Template)} on the resulting {@link Template} so that + * {@link Configurable} settings will be set too, because this constructor only uses it as a + * {@link ParserConfiguration}. + * @param encoding + * Same as in {@link #Template(String, String, Reader, Configuration, String)}. + * + * @since 2.3.24 + */ + public Template( + String name, String sourceName, Reader reader, + Configuration cfg, ParserConfiguration customParserConfiguration, + String encoding) throws IOException { + this(name, sourceName, reader, cfg, customParserConfiguration, encoding, + TemplateSpecifiedEncodingHandler.DEFAULT); + } + + /** + * Same as {@link #Template(String, String, Reader, Configuration, ParserConfiguration, String)}, but allows + * specifying a non-default (non-{@link TemplateSpecifiedEncodingHandler#DEFAULT}) behavior regarding encoding + * specified in the template content. + * + * @param templateSpecifiedEncodingHandler Not {@code null}. + * + * @since 2.3.26 + */ + public Template( + String name, String sourceName, Reader reader, + Configuration cfg, ParserConfiguration customParserConfiguration, + String encoding, TemplateSpecifiedEncodingHandler templateSpecifiedEncodingHandler) throws IOException { + this(name, sourceName, cfg, customParserConfiguration); + + NullArgumentException.check("templateSpecifiedEncodingHandler", templateSpecifiedEncodingHandler); + + this.setEncoding(encoding); + LineTableBuilder ltbReader; + try { + ParserConfiguration actualParserConfiguration = getParserConfiguration(); + + // Ensure that the parameter Reader is only read in bigger chunks, as we don't know if the it's buffered. + // In particular, inside the FreeMarker code, we assume that the stream stages need not be buffered. + if (!(reader instanceof BufferedReader) && !(reader instanceof StringReader)) { + reader = new BufferedReader(reader, READER_BUFFER_SIZE); + } + + ltbReader = new LineTableBuilder(reader, actualParserConfiguration); + reader = ltbReader; + + try { + parser = _CoreAPI.newFMParser( + this, reader, actualParserConfiguration, templateSpecifiedEncodingHandler); + try { + this.rootElement = parser.Root(); + } catch (IndexOutOfBoundsException exc) { + // There's a JavaCC bug where the Reader throws a RuntimeExcepton and then JavaCC fails with + // IndexOutOfBoundsException. If that wasn't the case, we just rethrow. Otherwise we suppress the + // IndexOutOfBoundsException and let the real cause to be thrown later. + if (!ltbReader.hasFailure()) { + throw exc; + } + rootElement = null; + } + this.actualTagSyntax = parser._getLastTagSyntax(); + this.actualNamingConvention = parser._getLastNamingConvention(); + } catch (TokenMgrError exc) { + // TokenMgrError VS ParseException is not an interesting difference for the user, so we just convert it + // to ParseException + throw exc.toParseException(this); + } finally { + parser = null; + } + } catch (ParseException e) { + e.setTemplateName(getSourceName()); + throw e; + } + + // Throws any exception that JavaCC has silently treated as EOF: + ltbReader.throwFailure(); + + DebuggerService.registerTemplate(this); + namespaceURIToPrefixLookup = Collections.unmodifiableMap(namespaceURIToPrefixLookup); + prefixToNamespaceURILookup = Collections.unmodifiableMap(prefixToNamespaceURILookup); + } + + /** + * Equivalent to {@link #Template(String, Reader, Configuration) + * Template(name, reader, null)}. + * + * @deprecated This constructor uses the "default" {@link Configuration} + * instance, which can easily lead to erroneous, unpredictable behavior. + * See more {@link Configuration#getDefaultConfiguration() here...}. + */ + @Deprecated + public Template(String name, Reader reader) throws IOException { + this(name, reader, (Configuration) null); + } + + /** + * Only meant to be used internally. + * + * @deprecated Has problems setting actualTagSyntax and templateLanguageVersion; will be removed in 2.4. + */ + @Deprecated + // [2.4] remove this + Template(String name, TemplateElement root, Configuration cfg) { + this(name, null, cfg, (ParserConfiguration) null); + this.rootElement = root; + DebuggerService.registerTemplate(this); + } + + /** + * Same as {@link #getPlainTextTemplate(String, String, String, Configuration)} with {@code null} {@code sourceName} + * argument. + */ + static public Template getPlainTextTemplate(String name, String content, Configuration config) { + return getPlainTextTemplate(name, null, content, config); + } + + /** + * Creates (not "get"-s) a {@link Template} that only contains a single block of static text, no dynamic content. + * + * @param name + * See {@link #getName} for more details. + * @param sourceName + * See {@link #getSourceName} for more details. If {@code null}, it will be the same as the {@code name}. + * @param content + * the block of text that this template represents + * @param config + * the configuration to which this template belongs + * + * @since 2.3.22 + */ + static public Template getPlainTextTemplate(String name, String sourceName, String content, Configuration config) { + Template template; + try { + template = new Template(name, sourceName, new StringReader("X"), config); + } catch (IOException e) { + throw new BugException("Plain text template creation failed", e); + } + _CoreAPI.replaceText((TextBlock) template.rootElement, content); + DebuggerService.registerTemplate(template); + return template; + } + + private static Version normalizeTemplateLanguageVersion(Version incompatibleImprovements) { + _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements); + int v = incompatibleImprovements.intValue(); + if (v < _TemplateAPI.VERSION_INT_2_3_19) { + return Configuration.VERSION_2_3_0; + } else if (v > _TemplateAPI.VERSION_INT_2_3_21) { + return Configuration.VERSION_2_3_21; + } else { // if 2.3.19 or 2.3.20 or 2.3.21 + return incompatibleImprovements; + } + } + + /** + * Executes template, using the data-model provided, writing the generated output to the supplied {@link Writer}. + * + *

+ * For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker + * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead. + * + * @param dataModel + * the holder of the variables visible from the template (name-value pairs); usually a + * {@code Map} or a JavaBean (where the JavaBean properties will be the variables). Can + * be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}. You can + * also use an object that already implements {@link TemplateHashModel}; in that case it won't be + * wrapped. If it's {@code null}, an empty data model is used. + * @param out + * The {@link Writer} where the output of the template will go. Note that unless you have used + * {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at + * the when the template processing was finished. {@link Writer#close()} is not called. Can't be + * {@code null}. + * + * @throws TemplateException + * if an exception occurs during template processing + * @throws IOException + * if an I/O exception occurs during writing to the writer. + */ + public void process(Object dataModel, Writer out) + throws TemplateException, IOException { + createProcessingEnvironment(dataModel, out, null).process(); + } + + /** + * Like {@link #process(Object, Writer)}, but also sets a (XML-)node to be recursively processed by the template. + * That node is accessed in the template with .node, #recurse, etc. See the + * Declarative XML Processing as a + * typical example of recursive node processing. + * + * @param rootNode The root node for recursive processing or {@code null}. + * + * @throws TemplateException if an exception occurs during template processing + * @throws IOException if an I/O exception occurs during writing to the writer. + */ + public void process(Object dataModel, Writer out, ObjectWrapper wrapper, TemplateNodeModel rootNode) + throws TemplateException, IOException { + Environment env = createProcessingEnvironment(dataModel, out, wrapper); + if (rootNode != null) { + env.setCurrentVisitorNode(rootNode); + } + env.process(); + } + + /** + * Like {@link #process(Object, Writer)}, but overrides the {@link Configuration#getObjectWrapper()}. + * + * @param wrapper The {@link ObjectWrapper} to be used instead of what {@link Configuration#getObjectWrapper()} + * provides, or {@code null} if you don't want to override that. + */ + public void process(Object dataModel, Writer out, ObjectWrapper wrapper) + throws TemplateException, IOException { + createProcessingEnvironment(dataModel, out, wrapper).process(); + } + + /** + * Creates a {@link org.apache.freemarker.core.ast.Environment Environment} object, using this template, the data-model provided as + * parameter. You have to call {@link Environment#process()} on the return value to set off the actual rendering. + * + *

Use this method if you want to do some special initialization on the {@link Environment} before template + * processing, or if you want to read the {@link Environment} after template processing. Otherwise using + * {@link Template#process(Object, Writer)} is simpler. + * + *

Example: + * + *

+    * Environment env = myTemplate.createProcessingEnvironment(root, out, null);
+    * env.process();
+ * + *

The above is equivalent with this: + * + *

+    * myTemplate.process(root, out);
+ * + *

But with createProcessingEnvironment, you can manipulate the environment + * before and after the processing: + * + *

+    * Environment env = myTemplate.createProcessingEnvironment(root, out);
+    * 
+    * env.setLocale(myUsersPreferredLocale);
+    * env.setTimeZone(myUsersPreferredTimezone);
+    * 
+    * env.process();  // output is rendered here
+    * 
+    * TemplateModel x = env.getVariable("x");  // read back a variable set by the template
+ * + * @param dataModel the holder of the variables visible from all templates; see {@link #process(Object, Writer)} for + * more details. + * @param wrapper The {@link ObjectWrapper} to use to wrap objects into {@link TemplateModel} + * instances. Normally you left it {@code null}, in which case {@link Configurable#getObjectWrapper()} will be + * used. + * @param out The {@link Writer} where the output of the template will go; see {@link #process(Object, Writer)} for + * more details. + * + * @return the {@link Environment} object created for processing. Call {@link Environment#process()} to process the + * template. + * + * @throws TemplateException if an exception occurs while setting up the Environment object. + * @throws IOException if an exception occurs doing any auto-imports + */ + public Environment createProcessingEnvironment(Object dataModel, Writer out, ObjectWrapper wrapper) + throws TemplateException, IOException { + final TemplateHashModel dataModelHash; + if (dataModel instanceof TemplateHashModel) { + dataModelHash = (TemplateHashModel) dataModel; + } else { + if (wrapper == null) { + wrapper = getObjectWrapper(); + } + + if (dataModel == null) { + dataModelHash = new SimpleHash(wrapper); + } else { + TemplateModel wrappedDataModel = wrapper.wrap(dataModel); + if (wrappedDataModel instanceof TemplateHashModel) { + dataModelHash = (TemplateHashModel) wrappedDataModel; + } else if (wrappedDataModel == null) { + throw new IllegalArgumentException( + wrapper.getClass().getName() + " converted " + dataModel.getClass().getName() + " to null."); + } else { + throw new IllegalArgumentException( + wrapper.getClass().getName() + " didn't convert " + dataModel.getClass().getName() + + " to a TemplateHashModel. Generally, you want to use a Map or a " + + "JavaBean as the root-map (aka. data-model) parameter. The Map key-s or JavaBean " + + "property names will be the variable names in the template."); + } + } + } + return new Environment(this, dataModelHash, out); + } + + /** + * Same as {@link #createProcessingEnvironment(Object, Writer, ObjectWrapper) + * createProcessingEnvironment(dataModel, out, null)}. + */ + public Environment createProcessingEnvironment(Object dataModel, Writer out) + throws TemplateException, IOException { + return createProcessingEnvironment(dataModel, out, null); + } + + /** + * Returns a string representing the raw template + * text in canonical form. + */ + @Override + public String toString() { + StringWriter sw = new StringWriter(); + try { + dump(sw); + } catch (IOException ioe) { + throw new RuntimeException(ioe.getMessage()); + } + return sw.toString(); + } + + + /** + * The usually path-like (or URL-like) identifier of the template, or possibly {@code null} for non-stored + * templates. It usually looks like a relative UN*X path; it should use {@code /}, not {@code \}, and shouldn't + * start with {@code /} (but there are no hard guarantees). It's not a real path in a file-system, it's just a name + * that a {@link TemplateLoader} used to load the backing resource (in simple cases; actually that name is + * {@link #getSourceName()}, but see it there). Or, it can also be a name that was never used to load the template + * (directly created with {@link #Template(String, Reader, Configuration)}). Even if the templates are stored + * straightforwardly in files, this is relative to the base directory of the {@link TemplateLoader}. So it really + * could be anything, except that it has importance in these situations: + * + *

+ * Relative paths to other templates in this template will be resolved relatively to the directory part of this. + * Like if the template name is {@code "foo/this.ftl"}, then {@code <#include "other.ftl">} gets the template with + * name {@code "foo/other.ftl"}. + *

+ * + *

+ * You should not use this name to indicate error locations, or to find the actual templates in general, because + * localized lookup, acquisition and other lookup strategies can transform names before they get to the + * {@link TemplateLoader} (the template storage) mechanism. Use {@link #getSourceName()} for these purposes. + *

+ * + *

+ * Some frameworks use URL-like template names like {@code "someSchema://foo/bar.ftl"}. FreeMarker understands this + * notation, so an absolute path like {@code "/baaz.ftl"} in that template will be resolved too + * {@code "someSchema://baaz.ftl"}. + */ + public String getName() { + return name; + } + + /** + * The name that was actually used to load this template from the {@link TemplateLoader} (or from other custom + * storage mechanism). This is what should be shown in error messages as the error location. This is usually the + * same as {@link #getName()}, except when localized lookup, template acquisition ({@code *} step in the name), or + * other {@link TemplateLookupStrategy} transforms the requested name ({@link #getName()}) to a different final + * {@link TemplateLoader}-level name. For example, when you get a template with name {@code "foo.ftl"} then because + * of localized lookup, it's possible that something like {@code "foo_en.ftl"} will be loaded behind the scenes. + * While the template name will be still the same as the requested template name ({@code "foo.ftl"}), errors should + * point to {@code "foo_de.ftl"}. Note that relative paths are always resolved relatively to the {@code name}, not + * to the {@code sourceName}. + * + * @since 2.3.22 + */ + public String getSourceName() { + return sourceName != null ? sourceName : getName(); + } + + /** + * Returns the Configuration object associated with this template. + */ + public Configuration getConfiguration() { + return (Configuration) getParent(); + } + + /** + * Returns the {@link ParserConfiguration} that was used for parsing this template. This is most often the same + * object as {@link #getConfiguration()}, but sometimes it's a {@link TemplateConfiguration}, or something else. + * It's never {@code null}. + * + * @since 2.3.24 + */ + public ParserConfiguration getParserConfiguration() { + return parserConfiguration; + } + + /** + * Return the template language (FTL) version used by this template. + * For now (2.3.21) this is the same as {@link Configuration#getIncompatibleImprovements()}, except + * that it's normalized to the lowest version where the template language was changed. + */ + Version getTemplateLanguageVersion() { + return templateLanguageVersion; + } + + /** + * @param encoding + * The encoding that was used to read this template. When this template {@code #include}-s or + * {@code #import}-s another template, by default it will use this encoding for those. For backward + * compatibility, this can be {@code null}, which will unset this setting. + * + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + /** + * Returns the default character encoding used for reading included/imported files; if {@code null}, then + * the encoding returned by {@link Configuration#getEncoding(java.util.Locale)} should be used instead. + */ + public String getEncoding() { + return this.encoding; + } + + /** + * Gets the custom lookup condition with which this template was found. See the {@code customLookupCondition} + * parameter of {@link Configuration#getTemplate(String, java.util.Locale, Object, String, boolean, boolean)} for + * more explanation. + * + * @since 2.3.22 + */ + public Object getCustomLookupCondition() { + return customLookupCondition; + } + + /** + * Mostly only used internally; setter pair of {@link #getCustomLookupCondition()}. This meant to be called directly + * after instantiating the template with its constructor, after a successfull lookup that used this condition. So + * this should only be called from code that deals with creating new {@code Template} objects, like from + * {@link TemplateCache}. + * + * @since 2.3.22 + */ + public void setCustomLookupCondition(Object customLookupCondition) { + this.customLookupCondition = customLookupCondition; + } + + /** + * Returns the tag syntax the parser has chosen for this template. If the syntax could be determined, it's + * {@link Configuration#SQUARE_BRACKET_TAG_SYNTAX} or {@link Configuration#ANGLE_BRACKET_TAG_SYNTAX}. If the syntax + * couldn't be determined (like because there was no tags in the template, or it was a plain text template), this + * returns whatever the default is in the current configuration, so it's maybe + * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}. + * + * @since 2.3.20 + */ + public int getActualTagSyntax() { + return actualTagSyntax; + } + + /** + * Returns the naming convention the parser has chosen for this template. If it could be determined, it's + * {@link Configuration#LEGACY_NAMING_CONVENTION} or {@link Configuration#CAMEL_CASE_NAMING_CONVENTION}. If it + * couldn't be determined (like because there no identifier that's part of the template language was used where + * the naming convention matters), this returns whatever the default is in the current configuration, so it's maybe + * {@link Configuration#AUTO_DETECT_TAG_SYNTAX}. + * + * @since 2.3.23 + */ + public int getActualNamingConvention() { + return actualNamingConvention; + } + + /** + * Returns the output format (see {@link Configuration#setOutputFormat(OutputFormat)}) used for this template. + * The output format of a template can come from various places, in order of increasing priority: + * {@link Configuration#getOutputFormat()}, {@link ParserConfiguration#getOutputFormat()} (which is usually + * provided by {@link Configuration#getTemplateConfigurations()}) and the {@code #ftl} header's {@code output_format} + * option in the template. + * + * @since 2.3.24 + */ + public OutputFormat getOutputFormat() { + return outputFormat; + } + + /** + * Meant to be called by the parser only. + */ + void setOutputFormat(OutputFormat outputFormat) { + this.outputFormat = outputFormat; + } + + /** + * Returns if the template actually uses auto-escaping (see {@link Configuration#setAutoEscapingPolicy(int)}). This value + * is decided by the parser based on the actual {@link OutputFormat}, and the auto-escaping enums, in order of + * increasing priority: {@link Configuration#getAutoEscapingPolicy()}, {@link ParserConfiguration#getAutoEscapingPolicy()} + * (which is usually provided by {@link Configuration#getTemplateConfigurations()}), and finally on the {@code #ftl} + * header's {@code auto_esc} option in the template. + * + * @since 2.3.24 + */ + public boolean getAutoEscaping() { + return autoEscaping; + } + + /** + * Meant to be called by the parser only. + */ + void setAutoEscaping(boolean autoEscaping) { + this.autoEscaping = autoEscaping; + } + + /** + * Dump the raw template in canonical form. + */ + public void dump(PrintStream ps) { + ps.print(rootElement.getCanonicalForm()); + } + + /** + * Dump the raw template in canonical form. + */ + public void dump(Writer out) throws IOException { + out.write(rootElement.getCanonicalForm()); + } + + /** + * Called by code internally to maintain a table of macros + * + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public void addMacro(Macro macro) { + macros.put(macro.getName(), macro); + } + + /** + * Called by code internally to maintain a list of imports + * + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public void addImport(LibraryLoad ll) { + imports.add(ll); + } + + /** + * Returns the template source at the location specified by the coordinates given, or {@code null} if unavailable. + * A strange legacy in the behavior of this method is that it replaces tab characters with spaces according the + * value of {@link Template#getParserConfiguration()}/{@link ParserConfiguration#getTabSize()} (which usually + * comes from {@link Configuration#getTabSize()}), because tab characters move the column number with more than + * 1 in error messages. However, if you set the tab size to 1, this method leaves the tab characters as is. + * + * @param beginColumn the first column of the requested source, 1-based + * @param beginLine the first line of the requested source, 1-based + * @param endColumn the last column of the requested source, 1-based + * @param endLine the last line of the requested source, 1-based + * + * @see org.apache.freemarker.core.ast.TemplateObject#getSource() + */ + public String getSource(int beginColumn, + int beginLine, + int endColumn, + int endLine) { + if (beginLine < 1 || endLine < 1) return null; // dynamically ?eval-ed expressions has no source available + + // Our container is zero-based. + --beginLine; + --beginColumn; + --endColumn; + --endLine; + StringBuilder buf = new StringBuilder(); + for (int i = beginLine ; i <= endLine; i++) { + if (i < lines.size()) { + buf.append(lines.get(i)); + } + } + int lastLineLength = lines.get(endLine).toString().length(); + int trailingCharsToDelete = lastLineLength - endColumn - 1; + buf.delete(0, beginColumn); + buf.delete(buf.length() - trailingCharsToDelete, buf.length()); + return buf.toString(); + } + + /** + * Reader that builds up the line table info for us, and also helps in working around JavaCC's exception + * suppression. + */ + private class LineTableBuilder extends FilterReader { + + private final int tabSize; + private final StringBuilder lineBuf = new StringBuilder(); + int lastChar; + boolean closed; + + /** Needed to work around JavaCC behavior where it silently treats any errors as EOF. */ + private Exception failure; + + /** + * @param r the character stream to wrap + */ + LineTableBuilder(Reader r, ParserConfiguration parserConfiguration) { + super(r); + tabSize = parserConfiguration.getTabSize(); + } + + public boolean hasFailure() { + return failure != null; + } + + public void throwFailure() throws IOException { + if (failure != null) { + if (failure instanceof IOException) { + throw (IOException) failure; + } + if (failure instanceof RuntimeException) { + throw (RuntimeException) failure; + } + throw new UndeclaredThrowableException(failure); + } + } + + @Override + public int read() throws IOException { + try { + int c = in.read(); + handleChar(c); + return c; + } catch (Exception e) { + throw rememberException(e); + } + } + + private IOException rememberException(Exception e) throws IOException { + // JavaCC used to read from the Reader after it was closed. So we must not treat that as a failure. + if (!closed) { + failure = e; + } + if (e instanceof IOException) { + return (IOException) e; + } + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + throw new UndeclaredThrowableException(e); + } + + @Override + public int read(char cbuf[], int off, int len) throws IOException { + try { + int numchars = in.read(cbuf, off, len); + for (int i = off; i < off + numchars; i++) { + char c = cbuf[i]; + handleChar(c); + } + return numchars; + } catch (Exception e) { + throw rememberException(e); + } + } + + @Override + public void close() throws IOException { + if (lineBuf.length() > 0) { + lines.add(lineBuf.toString()); + lineBuf.setLength(0); + } + super.close(); + closed = true; + } + + private void handleChar(int c) { + if (c == '\n' || c == '\r') { + if (lastChar == '\r' && c == '\n') { // CRLF under Windoze + int lastIndex = lines.size() - 1; + String lastLine = (String) lines.get(lastIndex); + lines.set(lastIndex, lastLine + '\n'); + } else { + lineBuf.append((char) c); + lines.add(lineBuf.toString()); + lineBuf.setLength(0); + } + } else if (c == '\t' && tabSize != 1) { + int numSpaces = tabSize - (lineBuf.length() % tabSize); + for (int i = 0; i < numSpaces; i++) { + lineBuf.append(' '); + } + } else { + lineBuf.append((char) c); + } + lastChar = c; + } + } + + /** + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public TemplateElement getRootTreeNode() { + return rootElement; + } + + /** + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public Map getMacros() { + return macros; + } + + /** + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public List getImports() { + return imports; + } + + /** + * This is used internally. + * + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public void addPrefixNSMapping(String prefix, String nsURI) { + if (nsURI.length() == 0) { + throw new IllegalArgumentException("Cannot map empty string URI"); + } + if (prefix.length() == 0) { + throw new IllegalArgumentException("Cannot map empty string prefix"); + } + if (prefix.equals(NO_NS_PREFIX)) { + throw new IllegalArgumentException("The prefix: " + prefix + " cannot be registered, it's reserved for special internal use."); + } + if (prefixToNamespaceURILookup.containsKey(prefix)) { + throw new IllegalArgumentException("The prefix: '" + prefix + "' was repeated. This is illegal."); + } + if (namespaceURIToPrefixLookup.containsKey(nsURI)) { + throw new IllegalArgumentException("The namespace URI: " + nsURI + " cannot be mapped to 2 different prefixes."); + } + if (prefix.equals(DEFAULT_NAMESPACE_PREFIX)) { + this.defaultNS = nsURI; + } else { + prefixToNamespaceURILookup.put(prefix, nsURI); + namespaceURIToPrefixLookup.put(nsURI, prefix); + } + } + + public String getDefaultNS() { + return this.defaultNS; + } + + /** + * @return the NamespaceUri mapped to this prefix in this template. (Or null if there is none.) + */ + public String getNamespaceForPrefix(String prefix) { + if (prefix.equals("")) { + return defaultNS == null ? "" : defaultNS; + } + return (String) prefixToNamespaceURILookup.get(prefix); + } + + /** + * @return the prefix mapped to this nsURI in this template. (Or null if there is none.) + */ + public String getPrefixForNamespace(String nsURI) { + if (nsURI == null) { + return null; + } + if (nsURI.length() == 0) { + return defaultNS == null ? "" : NO_NS_PREFIX; + } + if (nsURI.equals(defaultNS)) { + return ""; + } + return (String) namespaceURIToPrefixLookup.get(nsURI); + } + + /** + * @return the prefixed name, based on the ns_prefixes defined + * in this template's header for the local name and node namespace + * passed in as parameters. + */ + public String getPrefixedName(String localName, String nsURI) { + if (nsURI == null || nsURI.length() == 0) { + if (defaultNS != null) { + return NO_NS_PREFIX + ":" + localName; + } else { + return localName; + } + } + if (nsURI.equals(defaultNS)) { + return localName; + } + String prefix = getPrefixForNamespace(nsURI); + if (prefix == null) { + return null; + } + return prefix + ":" + localName; + } + + /** + * @return an array of the {@link TemplateElement}s containing the given column and line numbers. + * @deprecated Should only be used internally, and might will be removed later. + */ + @Deprecated + public List containingElements(int column, int line) { + final ArrayList elements = new ArrayList(); + TemplateElement element = rootElement; + mainloop: while (element.contains(column, line)) { + elements.add(element); + for (Enumeration enumeration = element.children(); enumeration.hasMoreElements(); ) { + TemplateElement elem = (TemplateElement) enumeration.nextElement(); + if (elem.contains(column, line)) { + element = elem; + continue mainloop; + } + } + break; + } + return elements.isEmpty() ? null : elements; + } + + /** + * Thrown by the {@link Template} constructors that specify a non-{@code null} encoding whoch doesn't match the + * encoding specified in the {@code #ftl} header of the template. + */ + static public class WrongEncodingException extends ParseException { + private static final long serialVersionUID = 1L; + + /** @deprecated Use {@link #getTemplateSpecifiedEncoding()} instead. */ + @Deprecated + public String specifiedEncoding; + + private final String constructorSpecifiedEncoding; + + /** + * @deprecated Use {@link #WrongEncodingException(String, String)}. + */ + @Deprecated + public WrongEncodingException(String templateSpecifiedEncoding) { + this(templateSpecifiedEncoding, null); + } + + /** + * @since 2.3.22 + */ + public WrongEncodingException(String templateSpecifiedEncoding, String constructorSpecifiedEncoding) { + this.specifiedEncoding = templateSpecifiedEncoding; + this.constructorSpecifiedEncoding = constructorSpecifiedEncoding; + } + + @Override + public String getMessage() { + return "Encoding specified inside the template (" + specifiedEncoding + + ") doesn't match the encoding specified for the Template constructor" + + (constructorSpecifiedEncoding != null ? " (" + constructorSpecifiedEncoding + ")." : "."); + } + + /** + * @since 2.3.22 + */ + public String getTemplateSpecifiedEncoding() { + return specifiedEncoding; + } + + /** + * @since 2.3.22 + */ + public String getConstructorSpecifiedEncoding() { + return constructorSpecifiedEncoding; + } + + } + +} + http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/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 new file mode 100644 index 0000000..e0739e0 --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateException.java @@ -0,0 +1,661 @@ +/* + * 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.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintStream; +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.CollectionUtils; + +/** + * Runtime exception in a template (as opposed to a parsing-time exception: {@link ParseException}). + * It prints a special stack trace that contains the template-language stack trace along the usual Java stack trace. + */ +public class TemplateException extends Exception { + + private static final String FTL_INSTRUCTION_STACK_TRACE_TITLE + = "FTL stack trace (\"~\" means nesting-related):"; + + // Set in constructor: + private transient _ErrorDescriptionBuilder descriptionBuilder; + private final transient Environment env; + private final transient Expression blamedExpression; + private transient TemplateElement[] ftlInstructionStackSnapshot; + + // Calculated on demand: + private String renderedFtlInstructionStackSnapshot; // clalc. from ftlInstructionStackSnapshot + private String renderedFtlInstructionStackSnapshotTop; // clalc. from ftlInstructionStackSnapshot + private String description; // calc. from descriptionBuilder, or set by the construcor + private transient String messageWithoutStackTop; + private transient String message; + private boolean blamedExpressionStringCalculated; + private String blamedExpressionString; + private boolean positionsCalculated; + private String templateName; + private String templateSourceName; + private Integer lineNumber; + private Integer columnNumber; + private Integer endLineNumber; + private Integer endColumnNumber; + + // Concurrency: + private transient Object lock = new Object(); + private transient ThreadLocal messageWasAlreadyPrintedForThisTrace; + + /** + * Constructs a TemplateException with no specified detail message + * or underlying cause. + */ + public TemplateException(Environment env) { + this((String) null, null, env); + } + + /** + * Constructs a TemplateException with the given detail message, + * but no underlying cause exception. + * + * @param description the description of the error that occurred + */ + public TemplateException(String description, Environment env) { + this(description, null, env); + } + + /** + * The same as {@link #TemplateException(Throwable, Environment)}; it's exists only for binary + * backward-compatibility. + */ + public TemplateException(Exception cause, Environment env) { + this((String) null, cause, env); + } + + /** + * Constructs a TemplateException with the given underlying Exception, + * but no detail message. + * + * @param cause the underlying {@link Exception} that caused this + * exception to be raised + * + * @since 2.3.20 + */ + public TemplateException(Throwable cause, Environment env) { + this((String) null, cause, env); + } + + /** + * The same as {@link #TemplateException(String, Throwable, Environment)}; it's exists only for binary + * backward-compatibility. + */ + public TemplateException(String description, Exception cause, Environment env) { + this(description, cause, env, null, null); + } + + /** + * Constructs a TemplateException with both a description of the error + * that occurred and the underlying Exception that caused this exception + * to be raised. + * + * @param description the description of the error that occurred + * @param cause the underlying {@link Exception} that caused this exception to be raised + * + * @since 2.3.20 + */ + public TemplateException(String description, Throwable cause, Environment env) { + this(description, cause, env, null, null); + } + + /** + * Don't use this; this is to be used internally by FreeMarker. No backward compatibility guarantees. + * + * @param blamedExpr Maybe {@code null}. The FTL stack in the {@link Environment} only specifies the error location + * 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, + _ErrorDescriptionBuilder descriptionBuilder) { + this(null, cause, env, blamedExpr, descriptionBuilder); + } + + private TemplateException( + String renderedDescription, + Throwable cause, + Environment env, Expression blamedExpression, + _ErrorDescriptionBuilder descriptionBuilder) { + // Note: Keep this constructor lightweight. + + super(cause); // Message managed locally. + + if (env == null) env = Environment.getCurrentEnvironment(); + this.env = env; + + this.blamedExpression = blamedExpression; + + this.descriptionBuilder = descriptionBuilder; + description = renderedDescription; + + if (env != null) ftlInstructionStackSnapshot = _CoreAPI.getInstructionStackSnapshot(env); + } + + private void renderMessages() { + String description = getDescription(); + + if (description != null && description.length() != 0) { + messageWithoutStackTop = description; + } else if (getCause() != null) { + messageWithoutStackTop = "No error description was specified for this error; low-level message: " + + getCause().getClass().getName() + ": " + getCause().getMessage(); + } else { + messageWithoutStackTop = "[No error description was available.]"; + } + + String stackTopFew = getFTLInstructionStackTopFew(); + if (stackTopFew != null) { + message = messageWithoutStackTop + "\n\n" + + _CoreAPI.ERROR_MESSAGE_HR + "\n" + + FTL_INSTRUCTION_STACK_TRACE_TITLE + "\n" + + stackTopFew + + _CoreAPI.ERROR_MESSAGE_HR; + messageWithoutStackTop = message.substring(0, messageWithoutStackTop.length()); // to reuse backing char[] + } else { + message = messageWithoutStackTop; + } + } + + private void calculatePosition() { + 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 + ? (TemplateObject) blamedExpression + : ( + ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length != 0 + ? ftlInstructionStackSnapshot[0] : null); + // Line number blow 0 means no info, negative means position in ?eval-ed value that we won't use here. + if (templateObject != null && templateObject.getBeginLine() > 0) { + final Template template = templateObject.getTemplate(); + templateName = template != null ? template.getName() : null; + templateSourceName = template != null ? template.getSourceName() : null; + lineNumber = Integer.valueOf(templateObject.getBeginLine()); + columnNumber = Integer.valueOf(templateObject.getBeginColumn()); + endLineNumber = Integer.valueOf(templateObject.getEndLine()); + endColumnNumber = Integer.valueOf(templateObject.getEndColumn()); + } + positionsCalculated = true; + deleteFTLInstructionStackSnapshotIfNotNeeded(); + } + } + } + + /** + * @deprecated Java 1.4 has introduced {@link #getCause()} - use that instead, especially as this can't return + * runtime exceptions and errors as is. + */ + @Deprecated + public Exception getCauseException() { + return getCause() instanceof Exception + ? (Exception) getCause() + : new Exception("Wrapped to Exception: " + getCause(), getCause()); + } + + /** + * Returns the snapshot of the FTL stack trace at the time this exception was created. + */ + public String getFTLInstructionStack() { + synchronized (lock) { + if (ftlInstructionStackSnapshot != null || renderedFtlInstructionStackSnapshot != null) { + if (renderedFtlInstructionStackSnapshot == null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + _CoreAPI.outputInstructionStack(ftlInstructionStackSnapshot, false, pw); + pw.close(); + if (renderedFtlInstructionStackSnapshot == null) { + renderedFtlInstructionStackSnapshot = sw.toString(); + deleteFTLInstructionStackSnapshotIfNotNeeded(); + } + } + return renderedFtlInstructionStackSnapshot; + } else { + return null; + } + } + } + + private String getFTLInstructionStackTopFew() { + synchronized (lock) { + if (ftlInstructionStackSnapshot != null || renderedFtlInstructionStackSnapshotTop != null) { + if (renderedFtlInstructionStackSnapshotTop == null) { + int stackSize = ftlInstructionStackSnapshot.length; + String s; + if (stackSize == 0) { + s = ""; + } else { + StringWriter sw = new StringWriter(); + _CoreAPI.outputInstructionStack(ftlInstructionStackSnapshot, true, sw); + s = sw.toString(); + } + if (renderedFtlInstructionStackSnapshotTop == null) { + renderedFtlInstructionStackSnapshotTop = s; + deleteFTLInstructionStackSnapshotIfNotNeeded(); + } + } + return renderedFtlInstructionStackSnapshotTop.length() != 0 + ? renderedFtlInstructionStackSnapshotTop : null; + } else { + return null; + } + } + } + + private void deleteFTLInstructionStackSnapshotIfNotNeeded() { + if (renderedFtlInstructionStackSnapshot != null && renderedFtlInstructionStackSnapshotTop != null + && (positionsCalculated || blamedExpression != null)) { + ftlInstructionStackSnapshot = null; + } + + } + + private String getDescription() { + synchronized (lock) { + if (description == null && descriptionBuilder != null) { + description = descriptionBuilder.toString( + getFailingInstruction(), + env != null ? env.getShowErrorTips() : true); + descriptionBuilder = null; + } + return description; + } + } + + private TemplateElement getFailingInstruction() { + if (ftlInstructionStackSnapshot != null && ftlInstructionStackSnapshot.length > 0) { + return ftlInstructionStackSnapshot[0]; + } else { + return null; + } + } + + /** + * @return the execution environment in which the exception occurred. + * {@code null} if the exception was deserialized. + */ + public Environment getEnvironment() { + return env; + } + + /** + * Overrides {@link Throwable#printStackTrace(PrintStream)} so that it will include the FTL stack trace. + */ + @Override + public void printStackTrace(PrintStream out) { + printStackTrace(out, true, true, true); + } + + /** + * Overrides {@link Throwable#printStackTrace(PrintWriter)} so that it will include the FTL stack trace. + */ + @Override + public void printStackTrace(PrintWriter out) { + printStackTrace(out, true, true, true); + } + + /** + * @param heading should the heading at the top be printed + * @param ftlStackTrace should the FTL stack trace be printed + * @param javaStackTrace should the Java stack trace be printed + * + * @since 2.3.20 + */ + public void printStackTrace(PrintWriter out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) { + synchronized (out) { + printStackTrace(new PrintWriterStackTraceWriter(out), heading, ftlStackTrace, javaStackTrace); + } + } + + /** + * @param heading should the heading at the top be printed + * @param ftlStackTrace should the FTL stack trace be printed + * @param javaStackTrace should the Java stack trace be printed + * + * @since 2.3.20 + */ + public void printStackTrace(PrintStream out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) { + synchronized (out) { + printStackTrace(new PrintStreamStackTraceWriter(out), heading, ftlStackTrace, javaStackTrace); + } + } + + private void printStackTrace(StackTraceWriter out, boolean heading, boolean ftlStackTrace, boolean javaStackTrace) { + synchronized (out) { + if (heading) { + out.println("FreeMarker template error:"); + } + + if (ftlStackTrace) { + String stackTrace = getFTLInstructionStack(); + if (stackTrace != null) { + out.println(getMessageWithoutStackTop()); // Not getMessage()! + out.println(); + out.println(_CoreAPI.ERROR_MESSAGE_HR); + out.println(FTL_INSTRUCTION_STACK_TRACE_TITLE); + out.print(stackTrace); + out.println(_CoreAPI.ERROR_MESSAGE_HR); + } else { + ftlStackTrace = false; + javaStackTrace = true; + } + } + + if (javaStackTrace) { + if (ftlStackTrace) { // We are after an FTL stack trace + out.println(); + out.println("Java stack trace (for programmers):"); + out.println(_CoreAPI.ERROR_MESSAGE_HR); + synchronized (lock) { + if (messageWasAlreadyPrintedForThisTrace == null) { + messageWasAlreadyPrintedForThisTrace = new ThreadLocal(); + } + messageWasAlreadyPrintedForThisTrace.set(Boolean.TRUE); + } + + try { + out.printStandardStackTrace(this); + } finally { + messageWasAlreadyPrintedForThisTrace.set(Boolean.FALSE); + } + } else { // javaStackTrace only + out.printStandardStackTrace(this); + } + + if (getCause() != null) { + // Dirty hack to fight with ServletException class whose getCause() method doesn't work properly: + Throwable causeCause = getCause().getCause(); + if (causeCause == null) { + try { + // Reflection is used to prevent dependency on Servlet classes. + Method m = getCause().getClass().getMethod("getRootCause", CollectionUtils.EMPTY_CLASS_ARRAY); + Throwable rootCause = (Throwable) m.invoke(getCause(), CollectionUtils.EMPTY_OBJECT_ARRAY); + if (rootCause != null) { + out.println("ServletException root cause: "); + out.printStandardStackTrace(rootCause); + } + } catch (Throwable exc) { + ; // ignore + } + } + } + } // if (javaStackTrace) + } + } + + /** + * Prints the stack trace as if wasn't overridden by {@link TemplateException}. + * @since 2.3.20 + */ + public void printStandardStackTrace(PrintStream ps) { + super.printStackTrace(ps); + } + + /** + * Prints the stack trace as if wasn't overridden by {@link TemplateException}. + * @since 2.3.20 + */ + public void printStandardStackTrace(PrintWriter pw) { + super.printStackTrace(pw); + } + + @Override + public String getMessage() { + if (messageWasAlreadyPrintedForThisTrace != null + && messageWasAlreadyPrintedForThisTrace.get() == Boolean.TRUE) { + return "[... Exception message was already printed; see it above ...]"; + } else { + synchronized (lock) { + if (message == null) renderMessages(); + return message; + } + } + } + + /** + * Similar to {@link #getMessage()}, but it doesn't contain the position of the failing instruction at then end + * of the text. It might contains the position of the failing expression though as part of the expression + * quotation, as that's the part of the description. + */ + public String getMessageWithoutStackTop() { + synchronized (lock) { + if (messageWithoutStackTop == null) renderMessages(); + return messageWithoutStackTop; + } + } + + /** + * 1-based line number of the failing section, or {@code null} if the information is not available. + * + * @since 2.3.21 + */ + public Integer getLineNumber() { + synchronized (lock) { + if (!positionsCalculated) { + calculatePosition(); + } + return lineNumber; + } + } + + /** + * Returns the name ({@link Template#getName()}) of the template where the error has occurred, or {@code null} if + * the information isn't available. This shouldn't be used for showing the error position; use + * {@link #getTemplateSourceName()} instead. + * + * @deprecated Use {@link #getTemplateSourceName()} instead, unless you are really sure that this is what you want. + * This method isn't really deprecated, it's just marked so to warn users about this. + * + * @since 2.3.21 + */ + @Deprecated + public String getTemplateName() { + synchronized (lock) { + if (!positionsCalculated) { + calculatePosition(); + } + return templateName; + } + } + + /** + * Returns the source name ({@link Template#getSourceName()}) of the template where the error has occurred, or + * {@code null} if the information isn't available. This is what should be used for showing the error position. + * + * @since 2.3.22 + */ + public String getTemplateSourceName() { + synchronized (lock) { + if (!positionsCalculated) { + calculatePosition(); + } + return templateSourceName; + } + } + + /** + * 1-based column number of the failing section, or {@code null} if the information is not available. + * + * @since 2.3.21 + */ + public Integer getColumnNumber() { + synchronized (lock) { + if (!positionsCalculated) { + calculatePosition(); + } + return columnNumber; + } + } + + /** + * 1-based line number of the last line that contains the failing section, or {@code null} if the information is not + * available. + * + * @since 2.3.21 + */ + public Integer getEndLineNumber() { + synchronized (lock) { + if (!positionsCalculated) { + calculatePosition(); + } + return endLineNumber; + } + } + + /** + * 1-based column number of the last character of the failing template section, or {@code null} if the information + * is not available. Note that unlike with Java string API-s, this column number is inclusive. + * + * @since 2.3.21 + */ + public Integer getEndColumnNumber() { + synchronized (lock) { + if (!positionsCalculated) { + calculatePosition(); + } + return endColumnNumber; + } + } + + /** + * If there was a blamed expression attached to this exception, it returns its canonical form, otherwise it returns + * {@code null}. This expression should always be inside the failing FTL instruction. + * + *

The typical application of this is getting the undefined expression from {@link InvalidReferenceException}-s. + * + * @since 2.3.21 + */ + public String getBlamedExpressionString() { + synchronized (lock) { + if (!blamedExpressionStringCalculated) { + if (blamedExpression != null) { + blamedExpressionString = blamedExpression.getCanonicalForm(); + } + blamedExpressionStringCalculated = true; + } + return blamedExpressionString; + } + } + + Expression getBlamedExpression() { + return blamedExpression; + } + + private void writeObject(ObjectOutputStream out) throws IOException, ClassNotFoundException { + // These are calculated from transient fields, so this is the last chance to calculate them: + getFTLInstructionStack(); + getFTLInstructionStackTopFew(); + getDescription(); + calculatePosition(); + getBlamedExpressionString(); + + out.defaultWriteObject(); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + lock = new Object(); + in.defaultReadObject(); + } + + /** Delegate to a {@link PrintWriter} or to a {@link PrintStream}. */ + private interface StackTraceWriter { + void print(Object obj); + void println(Object obj); + void println(); + void printStandardStackTrace(Throwable exception); + } + + private static class PrintStreamStackTraceWriter implements StackTraceWriter { + + private final PrintStream out; + + PrintStreamStackTraceWriter(PrintStream out) { + this.out = out; + } + + public void print(Object obj) { + out.print(obj); + } + + public void println(Object obj) { + out.println(obj); + } + + public void println() { + out.println(); + } + + public void printStandardStackTrace(Throwable exception) { + if (exception instanceof TemplateException) { + ((TemplateException) exception).printStandardStackTrace(out); + } else { + exception.printStackTrace(out); + } + } + + } + + private static class PrintWriterStackTraceWriter implements StackTraceWriter { + + private final PrintWriter out; + + PrintWriterStackTraceWriter(PrintWriter out) { + this.out = out; + } + + public void print(Object obj) { + out.print(obj); + } + + public void println(Object obj) { + out.println(obj); + } + + public void println() { + out.println(); + } + + public void printStandardStackTrace(Throwable exception) { + if (exception instanceof TemplateException) { + ((TemplateException) exception).printStandardStackTrace(out); + } else { + exception.printStackTrace(out); + } + } + + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/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 new file mode 100644 index 0000000..da1addc --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateExceptionHandler.java @@ -0,0 +1,155 @@ +/* + * 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.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; + +/** + * Used for the {@code template_exception_handler} configuration setting; + * see {@link Configurable#setTemplateExceptionHandler(TemplateExceptionHandler)} for more. + */ +public interface TemplateExceptionHandler { + + /** + * Method called after a {@link TemplateException} was raised inside a template. The exception should be re-thrown + * unless you want to suppress the exception. + * + *

Note that you can check with {@link Environment#isInAttemptBlock()} if you are inside a {@code #attempt} + * block, which then will handle handle this exception and roll back the output generated inside it. + * + *

Note that {@link StopException}-s (raised by {@code #stop}) won't be captured. + * + *

Note that you shouldn't log the exception in this method unless you suppress it. If there's a concern that the + * exception might won't be logged after it bubbles up from {@link Template#process(Object, Writer)}, simply + * ensure that {@link Configuration#getLogTemplateExceptions()} is {@code true}. + * + * @param te The exception that occurred; don't forget to re-throw it unless you want to suppress it + * @param env The runtime environment of the template + * @param out This is where the output of the template is written + */ + void handleTemplateException(TemplateException te, Environment env, Writer out) throws TemplateException; + + /** + * {@link TemplateExceptionHandler} that simply skips the failing instructions, letting the template continue + * executing. It does nothing to handle the event. Note that the exception is still logged, as with all + * other {@link TemplateExceptionHandler}-s. + */ + TemplateExceptionHandler IGNORE_HANDLER = new TemplateExceptionHandler() { + public void handleTemplateException(TemplateException te, Environment env, Writer out) { + // Do nothing + } + }; + + /** + * {@link TemplateExceptionHandler} that simply re-throws the exception; this should be used in most production + * systems. + */ + TemplateExceptionHandler RETHROW_HANDLER = new TemplateExceptionHandler() { + public void handleTemplateException(TemplateException te, Environment env, Writer out) + throws TemplateException { + throw te; + } + }; + + /** + * {@link TemplateExceptionHandler} useful when you developing non-HTML templates. This handler + * outputs the stack trace information to the client and then re-throws the exception. + */ + TemplateExceptionHandler DEBUG_HANDLER = new TemplateExceptionHandler() { + public void handleTemplateException(TemplateException te, Environment env, Writer out) + throws TemplateException { + if (!env.isInAttemptBlock()) { + PrintWriter pw = (out instanceof PrintWriter) ? (PrintWriter) out : new PrintWriter(out); + pw.print("FreeMarker template error (DEBUG mode; use RETHROW in production!):\n"); + te.printStackTrace(pw, false, true, true); + + pw.flush(); // To commit the HTTP response + } + throw te; + } + }; + + /** + * {@link TemplateExceptionHandler} useful when you developing HTML templates. This handler + * outputs the stack trace information to the client, formatting it so that it will be usually well readable + * in the browser, and then re-throws the exception. + */ + TemplateExceptionHandler HTML_DEBUG_HANDLER = new TemplateExceptionHandler() { + public void handleTemplateException(TemplateException te, Environment env, Writer out) + throws TemplateException { + if (!env.isInAttemptBlock()) { + boolean externalPw = out instanceof PrintWriter; + PrintWriter pw = externalPw ? (PrintWriter) out : new PrintWriter(out); + try { + pw.print("" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "

" + + "FreeMarker template error " + + " (HTML_DEBUG mode; use RETHROW in production!)" + + "
");
+                    
+                    StringWriter stackTraceSW = new StringWriter();
+                    PrintWriter stackPW = new PrintWriter(stackTraceSW);
+                    te.printStackTrace(stackPW, false, true, true);
+                    stackPW.close();
+                    pw.println();
+                    pw.println(StringUtil.XMLEncNQG(stackTraceSW.toString()));
+                    
+                    pw.println("
"); + pw.flush(); // To commit the HTTP response + } finally { + if (!externalPw) pw.close(); + } + } // if (!env.isInAttemptBlock()) + + throw te; + } + + private static final String FONT_RESET_CSS = + "color:#A80000; font-size:12px; font-style:normal; font-variant:normal; " + + "font-weight:normal; text-decoration:none; text-transform: none"; + + }; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ecb4e230/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java b/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java new file mode 100644 index 0000000..e70ab3d --- /dev/null +++ b/src/main/java/org/apache/freemarker/core/TemplateNotFoundException.java @@ -0,0 +1,63 @@ +/* + * 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.FileNotFoundException; + +import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException; + +/** + * Thrown when {@link Configuration#getTemplate(String)} (or similar) doesn't find a template. + * This extends {@link FileNotFoundException} for backward compatibility, but in fact has nothing to do with files, as + * FreeMarker can load templates from many other sources. + * + * @since 2.3.22 + * + * @see MalformedTemplateNameException + * @see Configuration#getTemplate(String) + */ +public final class TemplateNotFoundException extends FileNotFoundException { + + private final String templateName; + private final Object customLookupCondition; + + public TemplateNotFoundException(String templateName, Object customLookupCondition, String message) { + super(message); + this.templateName = templateName; + this.customLookupCondition = customLookupCondition; + } + + /** + * The name (path) of the template that wasn't found. + */ + public String getTemplateName() { + return templateName; + } + + /** + * The custom lookup condition with which the template was requested, or {@code null} if there's no such condition. + * See the {@code customLookupCondition} parameter of + * {@link Configuration#getTemplate(String, java.util.Locale, Object, String, boolean, boolean)}. + */ + public Object getCustomLookupCondition() { + return customLookupCondition; + } + +}