diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime')
127 files changed, 27034 insertions, 0 deletions
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/DeprecatedRuntimeConstants.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/DeprecatedRuntimeConstants.java new file mode 100644 index 00000000..3789a76e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/DeprecatedRuntimeConstants.java @@ -0,0 +1,285 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +/** + * This class gathers deprecated runtime constants + * + * @author <a href="mailto:claude@renegat.net">Claude Brisson</a> + * @version $$ + */ + +@Deprecated +public interface DeprecatedRuntimeConstants +{ + /** + * Logging of invalid references. + * @deprecated see {@link RuntimeConstants#RUNTIME_LOG_REFERENCE_LOG_INVALID} + */ + String OLD_RUNTIME_LOG_REFERENCE_LOG_INVALID = "runtime.log.invalid.references"; + + /** + * Maximum allowed number of loops. + * @deprecated see {@link RuntimeConstants#MAX_NUMBER_LOOPS} + */ + String OLD_MAX_NUMBER_LOOPS = "directive.foreach.maxloops"; + + /** + * Whether to throw an exception or just skip bad iterables. Default is true. + * @since 1.6 + * @deprecated see {@link RuntimeConstants#SKIP_INVALID_ITERATOR} + */ + String OLD_SKIP_INVALID_ITERATOR = "directive.foreach.skip.invalid"; + + /** + * An empty object (string, collection) or zero number is false. + * @since 2.0 + * @deprecated see {@link RuntimeConstants#CHECK_EMPTY_OBJECTS} + */ + String OLD_CHECK_EMPTY_OBJECTS = "directive.if.emptycheck"; + + /** + * Starting tag for error messages triggered by passing a parameter not allowed in the #include directive. Only string literals, + * and references are allowed. + * @deprecated see {@link RuntimeConstants#ERRORMSG_START} + */ + String OLD_ERRORMSG_START = "directive.include.output.errormsg.start"; + + /** + * Ending tag for error messages triggered by passing a parameter not allowed in the #include directive. Only string literals, + * and references are allowed. + * @deprecated see {@link RuntimeConstants#ERRORMSG_END} + */ + String OLD_ERRORMSG_END = "directive.include.output.errormsg.end"; + + /** + * Maximum recursion depth allowed for the #parse directive. + * @deprecated see {@link RuntimeConstants#PARSE_DIRECTIVE_MAXDEPTH} + */ + String OLD_PARSE_DIRECTIVE_MAXDEPTH = "directive.parse.max.depth"; + + /** + * Maximum recursion depth allowed for the #define directive. + * @deprecated see {@link RuntimeConstants#DEFINE_DIRECTIVE_MAXDEPTH} + */ + String OLD_DEFINE_DIRECTIVE_MAXDEPTH = "directive.define.max.depth"; + + /** + * Vector of custom directives + * @deprecated see {@link RuntimeConstants#CUSTOM_DIRECTIVES} + */ + String OLD_CUSTOM_DIRECTIVES = "userdirective"; + + /** + * The <code>resource.manager.cache.size</code> property specifies the cache upper bound (if relevant). + * @deprecated see {@link RuntimeConstants#RESOURCE_MANAGER_DEFAULTCACHE_SIZE} + */ + String OLD_RESOURCE_MANAGER_DEFAULTCACHE_SIZE = "resource.manager.defaultcache.size"; + + /** + * controls if the finding of a resource is logged. + * @deprecated see {@link RuntimeConstants#RESOURCE_MANAGER_LOGWHENFOUND} + */ + String OLD_RESOURCE_MANAGER_LOGWHENFOUND = "resource.manager.logwhenfound"; + + /** + * Key used to retrieve the names of the resource loaders to be used. In a properties file they may appear as the following: + * <p>resource.loader = file,classpath</p> + * @deprecated see {@link RuntimeConstants#RESOURCE_LOADERS} + */ + String OLD_RESOURCE_LOADERS = "resource.loader"; + + /** + * The public handle for setting a path in the FileResourceLoader. + * @deprecated see {@link RuntimeConstants#FILE_RESOURCE_LOADER_PATH} + */ + String OLD_FILE_RESOURCE_LOADER_PATH = "file.resource.loader.path"; + + /** + * The public handle for turning the caching on in the FileResourceLoader. + * @deprecated see {@link RuntimeConstants#FILE_RESOURCE_LOADER_CACHE} + */ + String OLD_FILE_RESOURCE_LOADER_CACHE = "file.resource.loader.cache"; + + /** + * Resource loader modification check interval property suffix + */ + String OLD_RESOURCE_LOADER_CHECK_INTERVAL = "modificationCheckInterval"; + + /** + * Datasource loader datasource url + * @deprecated see {@link RuntimeConstants#DS_RESOURCE_LOADER_DATASOURCE} + */ + String OLD_DS_RESOURCE_LOADER_DATASOURCE = "ds.resource.loader.resource.datasource"; + + /** + * Datasource loader template key column + * @deprecated see {@link RuntimeConstants#DS_RESOURCE_LOADER_KEY_COLUMN} + */ + String OLD_DS_RESOURCE_LOADER_KEY_COLUMN = "ds.resource.loader.resource.keycolumn"; + + /** + * Datasource loader template content column + * @deprecated see {@link RuntimeConstants#DS_RESOURCE_LOADER_TEMPLATE_COLUMN} + */ + String OLD_DS_RESOURCE_LOADER_TEMPLATE_COLUMN = "ds.resource.loader.resource.templatecolumn"; + + /** + * Datasource loader template timestamp column + * @deprecated see {@link RuntimeConstants#DS_RESOURCE_LOADER_TIMESTAMP_COLUMN} + */ + String OLD_DS_RESOURCE_LOADER_TIMESTAMP_COLUMN = "ds.resource.loader.resource.timestampcolumn"; + + /** + * The default character encoding for the templates. Used by the parser in processing the input streams. + * @deprecated see {@link RuntimeConstants#INPUT_ENCODING} + */ + String OLD_INPUT_ENCODING = "input.encoding"; + + /** + * The <code>eventhandler.referenceinsertion.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.ReferenceInsertionEventHandler} implementations to use. + * @deprecated see {@link RuntimeConstants#EVENTHANDLER_REFERENCEINSERTION} + */ + String OLD_EVENTHANDLER_REFERENCEINSERTION = "eventhandler.referenceinsertion.class"; + + /** + * The <code>eventhandler.methodexception.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.MethodExceptionEventHandler} implementations to use. + * @deprecated see {@link RuntimeConstants#EVENTHANDLER_METHODEXCEPTION} + */ + String OLD_EVENTHANDLER_METHODEXCEPTION = "eventhandler.methodexception.class"; + + /** + * The <code>eventhandler.include.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.IncludeEventHandler} implementations to use. + * @deprecated see {@link RuntimeConstants#EVENTHANDLER_INCLUDE} + */ + String OLD_EVENTHANDLER_INCLUDE = "eventhandler.include.class"; + + /** + * The <code>eventhandler.invalidreferences.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.InvalidReferenceEventHandler} implementations to use. + * @deprecated see {@link RuntimeConstants#EVENTHANDLER_INVALIDREFERENCES} + */ + String OLD_EVENTHANDLER_INVALIDREFERENCES = "eventhandler.invalidreferences.class"; + + /** + * Name of local Velocimacro library template. + * @deprecated see {@link RuntimeConstants#VM_LIBRARY} + */ + String OLD_VM_LIBRARY = "velocimacro.library"; + + /** + * Default Velocimacro library template. + * @deprecated see {@link RuntimeConstants#VM_LIBRARY_DEFAULT} + */ + String OLD_VM_LIBRARY_DEFAULT = "VM_global_library.vm"; + + /** + * boolean (true/false) default true: allow inline (in-template) macro definitions. + * @deprecated see {@link RuntimeConstants#VM_PERM_ALLOW_INLINE} + */ + String OLD_VM_PERM_ALLOW_INLINE = "velocimacro.permissions.allow.inline"; + + /** + * boolean (true/false) default false: allow inline (in-template) macro definitions to replace existing. + * @deprecated see {@link RuntimeConstants#VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL} + */ + String OLD_VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL = "velocimacro.permissions.allow.inline.to.replace.global"; + + /** + * Switch for forcing inline macros to be local: default false. + * @deprecated see {@link RuntimeConstants#VM_PERM_INLINE_LOCAL} + */ + String OLD_VM_PERM_INLINE_LOCAL = "velocimacro.permissions.allow.inline.local.scope"; + + /** + * Specify the maximum depth for macro calls + * @since 1.6 + * @deprecated see {@link RuntimeConstants#VM_MAX_DEPTH} + */ + String OLD_VM_MAX_DEPTH = "velocimacro.max.depth"; + + /** + * Defines name of the reference that can be used to get the AST block passed to block macro calls. + * @since 1.7 + * @deprecated see {@link RuntimeConstants#VM_BODY_REFERENCE} + */ + String OLD_VM_BODY_REFERENCE = "velocimacro.body.reference"; + + /** + * Properties referenced in the template are required to exist the object + * @deprecated see {@link RuntimeConstants#RUNTIME_REFERENCES_STRICT} + */ + String OLD_RUNTIME_REFERENCES_STRICT = "runtime.references.strict"; + + /** + * Indicates we are going to use modified escape behavior in strict mode + * @deprecated see {@link RuntimeConstants#RUNTIME_REFERENCES_STRICT_ESCAPE} + */ + String OLD_RUNTIME_REFERENCES_STRICT_ESCAPE = "runtime.references.strict.escape"; + + /** + * key name for uberspector. Multiple classnames can be specified,in which case uberspectors will be chained. + * @deprecated see {@link RuntimeConstants#UBERSPECT_CLASSNAME} + */ + String OLD_UBERSPECT_CLASSNAME = "runtime.introspector.uberspect"; + + /** + * key for Conversion Manager class + * @deprecated see {@link RuntimeConstants#CONVERSION_HANDLER_INSTANCE} + */ + String OLD_CONVERSION_HANDLER_CLASS = "introspector.conversion_handler.class"; + + /** + * Switch for the interpolation facility for string literals. + * @deprecated see {@link RuntimeConstants#INTERPOLATE_STRINGLITERALS} + */ + String OLD_INTERPOLATE_STRINGLITERALS = "runtime.interpolate.string.literals"; + + /** + * Switch for ignoring nulls in math equations vs throwing exceptions. + * @deprecated see {@link RuntimeConstants#STRICT_MATH} + */ + String OLD_STRICT_MATH = "runtime.strict.math"; + + /** + * Key upon which a context should be accessible within itself + * @deprecated see {@link RuntimeConstants#CONTEXT_AUTOREFERENCE_KEY} + */ + String OLD_CONTEXT_AUTOREFERENCE_KEY = "context.autoreference.key"; + + /** + * Space gobbling mode + * @since 2.0 + * @deprecated see {@link RuntimeConstants#SPACE_GOBBLING} + */ + String OLD_SPACE_GOBBLING = "space.gobbling"; + + /** + * When displaying null or invalid non-quiet references, use the argument literal reference + * instead of the one in the macro block. Defaults to false. + * @since 2.1 + * @Deprecated since 2.2, see {@link RuntimeConstants#VM_ENABLE_BC_MODE} + **/ + String OLD_VM_ENABLE_BC_MODE = "velocimacro.arguments.preserve_literals"; + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserConfiguration.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserConfiguration.java new file mode 100644 index 00000000..6ec74b4a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserConfiguration.java @@ -0,0 +1,116 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +/** + * Class gathering configured replacement characters for a specific parser class. + * @since 2.2 + */ + +public class ParserConfiguration +{ + /** + * Configured replacement character for '$' + */ + private char dollar = '$'; + + /** + * Configured replacement character for '#' + */ + private char hash = '#'; + + /** + * Configured replacement character for '@' + */ + private char at = '@'; + + /** + * Configured replacement character for '*' + */ + private char asterisk = '*'; + + /** + * Getter for '$' configured replacement character + * @return configured replacement character for '$' + */ + public char getDollarChar() + { + return dollar; + } + + /** + * Setter for '$' configured replacement character + */ + void setDollarChar(char dollar) + { + this.dollar = dollar; + } + + /** + * Getter for '#' configured replacement character + * @return configured replacement character for '#' + */ + public char getHashChar() + { + return hash; + } + + /** + * Setter for '#' configured replacement character + */ + void setHashChar(char hash) + { + this.hash = hash; + } + + /** + * Getter for '@' configured replacement character + * @return configured replacement character for '@' + */ + public char getAtChar() + { + return at; + } + + /** + * Setter for '@' configured replacement character + */ + void setAtChar(char at) + { + this.at = at; + } + + /** + * Getter for '*' configured replacement character + * @return configured replacement character for '*' + */ + public char getAsteriskChar() + { + return asterisk; + } + + /** + * Setter for '*' configured replacement character + */ + void setAsteriskChar(char asterisk) + { + this.asterisk = asterisk; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPool.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPool.java new file mode 100644 index 00000000..0af4403c --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPool.java @@ -0,0 +1,53 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; + + +/** + * Provides instances of parsers as needed. get() will return a new parser if + * available. If a parser is acquired from the pool, put() should be called + * with that parser to make it available again for reuse. + * + * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a> + * @version $Id: RuntimeInstance.java 384374 2006-03-08 23:19:30Z nbubna $ + * @since 1.5 + */ +public interface ParserPool +{ + /** + * Initialize the pool so that it can begin serving parser instances. + * @param svc + */ + void initialize(RuntimeServices svc); + + /** + * Retrieve an instance of a parser pool. + * @return A parser object. + */ + Parser get(); + + /** + * Return the parser to the pool so that it may be reused. + * @param parser + */ + void put(Parser parser); +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPoolImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPoolImpl.java new file mode 100644 index 00000000..3934f2d0 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPoolImpl.java @@ -0,0 +1,81 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.CharStream; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.SimplePool; +import org.slf4j.Logger; + +/** + * This wraps the original parser SimplePool class. It also handles + * instantiating ad-hoc parsers if none are available. + * + * @author <a href="mailto:sergek@lokitech.com">Serge Knystautas</a> + * @version $Id: RuntimeInstance.java 384374 2006-03-08 23:19:30Z nbubna $ + * @since 1.5 + */ +public class ParserPoolImpl implements ParserPool { + + SimplePool pool = null; + int max = RuntimeConstants.NUMBER_OF_PARSERS; + Logger log; + + /** + * Create the underlying "pool". + * @param rsvc + */ + @Override + public void initialize(RuntimeServices rsvc) + { + log = rsvc.getLog("parser"); + max = rsvc.getInt(RuntimeConstants.PARSER_POOL_SIZE, RuntimeConstants.NUMBER_OF_PARSERS); + pool = new SimplePool(max); + + for (int i = 0; i < max; i++) + { + pool.put(rsvc.createNewParser()); + } + + log.debug("Created '{}' parsers.", max); + } + + /** + * Call the wrapped pool. If none are available, it will create a new + * temporary one. + * @return A parser Object. + */ + @Override + public Parser get() + { + return (Parser) pool.get(); + } + + /** + * Call the wrapped pool. + * @param parser + */ + @Override + public void put(Parser parser) + { + parser.ReInit((CharStream) null); + pool.put(parser); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/Renderable.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/Renderable.java new file mode 100644 index 00000000..157ba6a0 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/Renderable.java @@ -0,0 +1,44 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; + +import java.io.IOException; +import java.io.Writer; + +/** + * This interface characterize objects other than ASTNodes that can be rendered + * to a writer using a context. + * + * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a> + * @version $Id:$ + * @since 1.6 + */ + +public interface Renderable { + + boolean render(InternalContextAdapter context, Writer writer) + throws IOException, MethodInvocationException, ParseErrorException, ResourceNotFoundException; + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java new file mode 100644 index 00000000..89d3c41f --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java @@ -0,0 +1,499 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +/** + * This class defines the keys that are used in the velocity.properties file so that they can be referenced as a constant within + * Java code. + * + * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @version $Id$ + */ + +public interface RuntimeConstants extends DeprecatedRuntimeConstants +{ + /* + * ---------------------------------------------------------------------- + * These are public constants that are used as handles for the + * properties that can be specified in your typical + * velocity.properties file. + * ---------------------------------------------------------------------- + */ + + /* + * ---------------------------------------------------------------------- + * L O G G I N G C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** externally provided logger. */ + String RUNTIME_LOG_INSTANCE = "runtime.log.instance"; + + /** externally provided logger name. */ + String RUNTIME_LOG_NAME = "runtime.log.name"; + + /** Logging of invalid references. */ + String RUNTIME_LOG_REFERENCE_LOG_INVALID = "runtime.log.log_invalid_references"; + + /** Logging of invalid method calls. */ + String RUNTIME_LOG_METHOD_CALL_LOG_INVALID = "runtime.log.log_invalid_method_calls"; + + /** <p>Whether to:</p> + * <ul> + * <li>populate slf4j's MDC with location in template file</li> + * <li>display VTL stack trace on errors</li> + * </ul> + * @since 2.2 + */ + String RUNTIME_LOG_TRACK_LOCATION = "runtime.log.track_location"; + + /* + * ---------------------------------------------------------------------- + * D I R E C T I V E C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + * Directive properties are of the form: + * + * directive.<directive-name>.<property> + * ---------------------------------------------------------------------- + */ + + /** Maximum allowed number of loops. */ + String MAX_NUMBER_LOOPS = "directive.foreach.max_loops"; + + /** + * Whether to throw an exception or just skip bad iterables. Default is true. + * @since 1.6 + */ + String SKIP_INVALID_ITERATOR = "directive.foreach.skip_invalid"; + + /** + * An empty object (string, collection) or zero number is false. + * @since 2.0 + */ + String CHECK_EMPTY_OBJECTS = "directive.if.empty_check"; + + /** + * Starting tag for error messages triggered by passing a parameter not allowed in the #include directive. Only string literals, + * and references are allowed. + * @deprecated if/how errors are displayed is not the concern of the engine, which should throw in all cases + */ + String ERRORMSG_START = "directive.include.output_error_start"; + + /** + * Ending tag for error messages triggered by passing a parameter not allowed in the #include directive. Only string literals, + * and references are allowed. + * @deprecated if/how errors are displayed is not the concern of the engine, which should throw in all cases + */ + String ERRORMSG_END = "directive.include.output_error_end"; + + /** Maximum recursion depth allowed for the #parse directive. */ + String PARSE_DIRECTIVE_MAXDEPTH = "directive.parse.max_depth"; + + /** Maximum recursion depth allowed for the #define directive. */ + String DEFINE_DIRECTIVE_MAXDEPTH = "directive.define.max_depth"; + + /** + * Used to suppress various scope control objects (property suffix). + * @since 1.7 + * @deprecated use <code>context.scope_control.<scope_name> = true/false</code> + * @see #CONTEXT_SCOPE_CONTROL + */ + @Deprecated + String PROVIDE_SCOPE_CONTROL = "provide.scope.control"; + + /** + * Used to enable or disable a scope control (false by default): + * <code>context.scope_control.<scope_name> = true/false</code> + * where <i>scope_name</i> is one of: <code>template, evaluate, foreach, macro, define</code> + * or the name of a body macro. + * @since 2.1 + */ + String CONTEXT_SCOPE_CONTROL = "context.scope_control"; + + /** + * Vector of custom directives + */ + String CUSTOM_DIRECTIVES = "runtime.custom_directives"; + + /* + * ---------------------------------------------------------------------- + * R E S O U R C E M A N A G E R C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** + * The <code>resource.manager.instance</code> property specifies an existing instance of a + * {@link org.apache.velocity.runtime.resource.ResourceManager} implementation to use + */ + String RESOURCE_MANAGER_INSTANCE = "resource.manager.instance"; + + /** + * The <code>resource.manager.class</code> property specifies the name of the + * {@link org.apache.velocity.runtime.resource.ResourceManager} implementation to use. + */ + String RESOURCE_MANAGER_CLASS = "resource.manager.class"; + + /** + * The <code>resource.manager.cache.class</code> property specifies the name of the + * {@link org.apache.velocity.runtime.resource.ResourceCache} implementation to use. + */ + String RESOURCE_MANAGER_CACHE_CLASS = "resource.manager.cache.class"; + + /** The <code>resource.manager.cache.size</code> property specifies the cache upper bound (if relevant). */ + String RESOURCE_MANAGER_DEFAULTCACHE_SIZE = "resource.manager.cache.default_size"; + + /* + * ---------------------------------------------------------------------- + * R E S O U R C E L O A D E R C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** controls if the finding of a resource is logged. */ + String RESOURCE_MANAGER_LOGWHENFOUND = "resource.manager.log_when_found"; + + /** + * Key used to retrieve the names of the resource loaders to be used. In a properties file they may appear as the following: + * + * <p>resource.loaders = file,classpath</p> + */ + String RESOURCE_LOADERS = "resource.loaders"; + + /** + * Key prefix for a specific resource loader properties + * + * <p>resource.loader.file.path = ...</p> + */ + String RESOURCE_LOADER = "resource.loader"; + + /** The public handle for setting paths in the FileResourceLoader. + * (this constant is used by test cases only) + */ + String FILE_RESOURCE_LOADER_PATH = "resource.loader.file.path"; + + /** The public handle for turning the caching on in the FileResourceLoader. */ + String FILE_RESOURCE_LOADER_CACHE = "resource.loader.file.cache"; + + /** + * Resource loader class property suffix + */ + String RESOURCE_LOADER_CLASS = "class"; + + /** + * Resource loader instance property suffix + */ + String RESOURCE_LOADER_INSTANCE = "instance"; + + /** + * Resource loader cache property suffix + */ + String RESOURCE_LOADER_CACHE = "cache"; + + /** + * File resource loader paths property suffix + */ + String RESOURCE_LOADER_PATHS = "path"; + + /** + * Resource loader modification check interval property suffix + */ + String RESOURCE_LOADER_CHECK_INTERVAL = "modification_check_interval"; + + /** + * Datasource loader datasource url + */ + String DS_RESOURCE_LOADER_DATASOURCE = "resource.loader.ds.resource.datasource_url"; + + /** + * Datasource loader templates table + */ + String DS_RESOURCE_LOADER_TABLE = "resource.loader.ds.resource.table"; + + /** + * Datasource loader template key column + */ + String DS_RESOURCE_LOADER_KEY_COLUMN = "resource.loader.ds.resource.key_column"; + + /** + * Datasource loader template content column + */ + String DS_RESOURCE_LOADER_TEMPLATE_COLUMN = "resource.loader.ds.resource.template_column"; + + /** + * Datasource loader template timestamp column + */ + String DS_RESOURCE_LOADER_TIMESTAMP_COLUMN = "resource.loader.ds.resource.timestamp_column"; + + /** The default character encoding for the templates. Used by the parser in processing the input streams. */ + String INPUT_ENCODING = "resource.default_encoding"; + + /** Default Encoding is UTF-8. */ + String ENCODING_DEFAULT = "UTF-8"; + + /* + * ---------------------------------------------------------------------- + * E V E N T H A N D L E R C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** + * The <code>event_handler.reference_insertion.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.ReferenceInsertionEventHandler} implementations to use. + */ + String EVENTHANDLER_REFERENCEINSERTION = "event_handler.reference_insertion.class"; + + /** + * The <code>event_handler.method_exception.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.MethodExceptionEventHandler} implementations to use. + */ + String EVENTHANDLER_METHODEXCEPTION = "event_handler.method_exception.class"; + + /** + * The <code>event_handler.include.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.IncludeEventHandler} implementations to use. + */ + String EVENTHANDLER_INCLUDE = "event_handler.include.class"; + + /** + * The <code>event_handler.invalid_references.class</code> property specifies a list of the + * {@link org.apache.velocity.app.event.InvalidReferenceEventHandler} implementations to use. + */ + String EVENTHANDLER_INVALIDREFERENCES = "event_handler.invalid_references.class"; + + /** + * The <code>event_handler.invalid_references.quiet</code> property specifies if invalid quiet references + * (as in <code>$!foo</code>) trigger events (defaults to false). + * {@link org.apache.velocity.app.event.InvalidReferenceEventHandler} implementations to use. + * @since 2.2 + */ + String EVENTHANDLER_INVALIDREFERENCES_QUIET = "event_handler.invalid_references.quiet"; + + /** + * The <code>event_handler.invalid_references.null</code> property specifies if invalid null references + * (aka the value is present in the context or parent object but is null or a method returned null) + * trigger invalid reference events (defaults to false). + * {@link org.apache.velocity.app.event.InvalidReferenceEventHandler} implementations to use. + * @since 2.2 + */ + String EVENTHANDLER_INVALIDREFERENCES_NULL = "event_handler.invalid_references.null"; + + /** + * The <code>event_handler.invalid_references.tested</code> property specifies if invalid tested references + * (as in <code>#if($foo)</code> ) trigger invalid reference events (defaults to false). + * {@link org.apache.velocity.app.event.InvalidReferenceEventHandler} implementations to use. + * @since 2.2 + */ + String EVENTHANDLER_INVALIDREFERENCES_TESTED = "event_handler.invalid_references.tested"; + + /* + * ---------------------------------------------------------------------- + * V E L O C I M A C R O C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** Filename of local Velocimacro library template. */ + String VM_LIBRARY = "velocimacro.library.path"; + + /** Default Velocimacro library template. */ + String VM_LIBRARY_DEFAULT = "velocimacros.vtl"; + + /** switch for autoloading library-sourced VMs (for development). */ + String VM_LIBRARY_AUTORELOAD = "velocimacro.library.autoreload"; + + /** boolean (true/false) default true: allow inline (in-template) macro definitions. */ + String VM_PERM_ALLOW_INLINE = "velocimacro.inline.allow"; + + /** boolean (true/false) default false: allow inline (in-template) macro definitions to replace existing. */ + String VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL = "velocimacro.inline.replace_global"; + + /** Switch for forcing inline macros to be local: default false. */ + String VM_PERM_INLINE_LOCAL = "velocimacro.inline.local_scope"; + + /** if true, throw an exception for wrong number of arguments **/ + String VM_ARGUMENTS_STRICT = "velocimacro.arguments.strict"; + + /** + * This flag enable the 1.7 backward compatible mode for velocimacros (defaults to false): + * <ul> + * <li> + * preserve argument literals: when displaying null or invalid non-quiet references, + * use the argument literal reference instead of the one in the macro block. Defaults to false. + * </li> + * <li> + * use global values for missing arguments: when calling a macro with fewer arguments than declared, + * if those arguments don't have an explicit default value in the macro definition, default values will + * be looked for in the global context + * </li> + * </ul> + * @since 2.2 + */ + String VM_ENABLE_BC_MODE = "velocimacro.enable_bc_mode"; + + /** + * Specify the maximum depth for macro calls + * @since 1.6 + */ + String VM_MAX_DEPTH = "velocimacro.max_depth"; + + /** + * Defines name of the reference that can be used to get the AST block passed to block macro calls. + * @since 1.7 + */ + String VM_BODY_REFERENCE = "velocimacro.body_reference"; + + /** + * <p>Switch for VM blather: default true. Unused since 2.0.</p> + * @deprecated since 2.1 + */ + @Deprecated + String VM_MESSAGES_ON = "velocimacro.messages.on"; + + /* + * ---------------------------------------------------------------------- + * S T I C T M O D E B E H A V I O U R + * ---------------------------------------------------------------------- + */ + + /** + * Properties referenced in the template are required to exist the object + */ + String RUNTIME_REFERENCES_STRICT = "runtime.strict_mode.enable"; + + /** + * Indicates we are going to use modified escape behavior in strict mode + */ + String RUNTIME_REFERENCES_STRICT_ESCAPE = "runtime.strict_mode.escape"; + + /* + * ---------------------------------------------------------------------- + * I N T R O S P E C T I O N C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** key name for uberspector. Multiple classnames can be specified,in which case uberspectors will be chained. */ + String UBERSPECT_CLASSNAME = "introspector.uberspect.class"; + + /** A comma separated list of packages to restrict access to in the SecureIntrospector. */ + String INTROSPECTOR_RESTRICT_PACKAGES = "introspector.restrict.packages"; + + /** A comma separated list of classes to restrict access to in the SecureIntrospector. */ + String INTROSPECTOR_RESTRICT_CLASSES = "introspector.restrict.classes"; + + /** key for Conversion Manager class */ + String CONVERSION_HANDLER_CLASS = "introspector.conversion_handler.class"; + + /** key for Conversion Manager instance */ + String CONVERSION_HANDLER_INSTANCE = "introspector.conversion_handler.instance"; + + /* + * ---------------------------------------------------------------------- + * P A R S E R C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** + * Property specifying the parser class to use + * @since 2.2 + */ + String PARSER_CLASS = "parser.class"; + + /** + * Default parser class + * @since 2.2 + */ + String DEFAULT_PARSER_CLASS = "org.apache.velocity.runtime.parser.StandardParser"; + + /** + * The <code>parser.pool.class</code> property specifies the name of the {@link org.apache.velocity.util.SimplePool} + * implementation to use. + */ + String PARSER_POOL_CLASS = "parser.pool.class"; + + /** + * @see #NUMBER_OF_PARSERS + */ + String PARSER_POOL_SIZE = "parser.pool.size"; + + /** + * Allow hyphen in identifiers (backward compatibility option) + * @since 2.1 + */ + String PARSER_HYPHEN_ALLOWED = "parser.allow_hyphen_in_identifiers"; + + /* + * ---------------------------------------------------------------------- + * G E N E R A L R U N T I M E C O N F I G U R A T I O N + * ---------------------------------------------------------------------- + */ + + /** Whether to use string interning. */ + String RUNTIME_STRING_INTERNING = "runtime.string_interning"; + + /** Switch for the interpolation facility for string literals. */ + String INTERPOLATE_STRINGLITERALS = "runtime.interpolate_string_literals"; + + /** Switch for ignoring nulls in math equations vs throwing exceptions. */ + String STRICT_MATH = "runtime.strict_math"; + + /** Key upon which a context should be accessible within itself */ + String CONTEXT_AUTOREFERENCE_KEY = "context.self_reference_key"; + + /** + * Space gobbling mode + * @since 2.0 + */ + String SPACE_GOBBLING = "parser.space_gobbling"; + + /** + * Space gobbling modes + * @since 2.0 + */ + enum SpaceGobbling + { + NONE, BC, LINES, STRUCTURED + } + + /* + * ---------------------------------------------------------------------- + * These constants are used internally by the Velocity runtime i.e. + * the constants listed below are strictly used in the Runtime + * class itself. + * ---------------------------------------------------------------------- + */ + + /** Default Runtime properties. */ + String DEFAULT_RUNTIME_PROPERTIES = "org/apache/velocity/runtime/defaults/velocity.properties"; + + /** Default Runtime properties. */ + String DEFAULT_RUNTIME_DIRECTIVES = "org/apache/velocity/runtime/defaults/directive.properties"; + + /** externally provided logger name. */ + String DEFAULT_RUNTIME_LOG_NAME = "org.apache.velocity"; + + /** token used to identify the loader internally. */ + String RESOURCE_LOADER_IDENTIFIER = "_RESOURCE_LOADER_IDENTIFIER_"; + + /** + * The default number of parser instances to create. Configurable via the parameter named by the {@link #PARSER_POOL_SIZE} + * constant. + */ + int NUMBER_OF_PARSERS = 20; +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java new file mode 100644 index 00000000..3d77055d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java @@ -0,0 +1,2025 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.app.event.EventCartridge; +import org.apache.velocity.app.event.EventHandler; +import org.apache.velocity.app.event.IncludeEventHandler; +import org.apache.velocity.app.event.InvalidReferenceEventHandler; +import org.apache.velocity.app.event.MethodExceptionEventHandler; +import org.apache.velocity.app.event.ReferenceInsertionEventHandler; +import org.apache.velocity.context.Context; +import org.apache.velocity.context.InternalContextAdapterImpl; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.directive.Macro; +import org.apache.velocity.runtime.directive.Scope; +import org.apache.velocity.runtime.directive.StopCommand; +import org.apache.velocity.runtime.parser.LogContext; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.runtime.resource.ContentResource; +import org.apache.velocity.runtime.resource.ResourceManager; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.ExtProperties; +import org.apache.velocity.util.RuntimeServicesAware; +import org.apache.velocity.util.introspection.ChainableUberspector; +import org.apache.velocity.util.introspection.LinkingUberspector; +import org.apache.velocity.util.introspection.Uberspect; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Properties; +import java.util.Set; + +/** + * <p>This is the Runtime system for Velocity. It is the + * single access point for all functionality in Velocity. + * It adheres to the mediator pattern and is the only + * structure that developers need to be familiar with + * in order to get Velocity to perform.</p> + * + * <p>The Runtime will also cooperate with external + * systems, which can make all needed setProperty() calls + * before calling init().</p> + * <pre> + * ----------------------------------------------------------------------- + * N O T E S O N R U N T I M E I N I T I A L I Z A T I O N + * ----------------------------------------------------------------------- + * init() + * + * If init() is called by itself the RuntimeInstance will initialize + * with a set of default values. + * ----------------------------------------------------------------------- + * init(String/Properties) + * + * In this case the default velocity properties are layed down + * first to provide a solid base, then any properties provided + * in the given properties object will override the corresponding + * default property. + * ----------------------------------------------------------------------- + * </pre> + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:jlb@houseofdistraction.com">Jeff Bowden</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a> + * @version $Id$ + */ +public class RuntimeInstance implements RuntimeConstants, RuntimeServices +{ + /** + * VelocimacroFactory object to manage VMs + */ + private VelocimacroFactory vmFactory = null; + + /** + * The Runtime logger. The default instance is the "org.apache.velocity" logger. + */ + private Logger log = LoggerFactory.getLogger(DEFAULT_RUNTIME_LOG_NAME); + + /** + * The Runtime parser pool + */ + private ParserPool parserPool; + + /** + * Indicate whether the Runtime is in the midst of initialization. + */ + private boolean initializing = false; + + /** + * Indicate whether the Runtime has been fully initialized. + */ + private volatile boolean initialized = false; + + /** + * These are the properties that are laid down over top + * of the default properties when requested. + */ + private ExtProperties overridingProperties = null; + + /** + * This is a hashtable of initialized directives. + * The directives that populate this hashtable are + * taken from the RUNTIME_DEFAULT_DIRECTIVES + * property file. + */ + private Map<String, Directive> runtimeDirectives = new Hashtable<>(); + /** + * Copy of the actual runtimeDirectives that is shared between + * parsers. Whenever directives are updated, the synchronized + * runtimeDirectives is first updated and then an unsynchronized + * copy of it is passed to parsers. + */ + private Map<String, Directive> runtimeDirectivesShared; + + /** + * Object that houses the configuration options for + * the velocity runtime. The ExtProperties object allows + * the convenient retrieval of a subset of properties. + * For example all the properties for a resource loader + * can be retrieved from the main ExtProperties object + * using something like the following: + * + * ExtProperties loaderConfiguration = + * configuration.subset(loaderID); + * + * And a configuration is a lot more convenient to deal + * with then conventional properties objects, or Maps. + */ + private ExtProperties configuration = new ExtProperties(); + + private ResourceManager resourceManager = null; + + /** + * This stores the engine-wide set of event handlers. Event handlers for + * each specific merge are stored in the context. + */ + private EventCartridge eventCartridge = null; + + /** + * Whether to use string interning + */ + private boolean stringInterning = false; + + /** + * Scope name for evaluate(...) calls. + */ + private String evaluateScopeName = "evaluate"; + + /** + * Scope names for which to provide scope control objects in the context + */ + private Set<String> enabledScopeControls = new HashSet<>(); + + /** + * Opaque reference to something specified by the + * application for use in application supplied/specified + * pluggable components + */ + private Map<Object, Object> applicationAttributes = null; + + /** + * Uberspector + */ + private Uberspect uberSpect; + + /** + * Default encoding + */ + private String defaultEncoding; + + /** + * Space gobbling mode + */ + private SpaceGobbling spaceGobbling; + + /** + * Whether hyphen is allowed in identifiers + */ + private boolean hyphenAllowedInIdentifiers; + + /** + * The LogContext object used to track location in templates + */ + private LogContext logContext; + + /** + * Configured parser class + * @since 2.2 + */ + private Constructor<? extends Parser> parserConstructor; + + /** + * Configured replacement characters in parser grammar + * @since 2.2 + */ + private ParserConfiguration parserConfiguration; + + /** + * Creates a new RuntimeInstance object. + */ + public RuntimeInstance() + { + reset(); + } + + /** + * This is the primary initialization method in the Velocity + * Runtime. The systems that are setup/initialized here are + * as follows: + * + * <ul> + * <li>Logging System</li> + * <li>ResourceManager</li> + * <li>EventHandler</li> + * <li>Parser Pool</li> + * <li>Global Cache</li> + * <li>Static Content Include System</li> + * <li>Velocimacro System</li> + * </ul> + */ + @Override + public synchronized void init() + { + if (!initialized && !initializing) + { + try + { + log.debug("Initializing Velocity, Calling init()..."); + initializing = true; + + log.trace("*****************************"); + log.debug("Starting Apache Velocity v" + VelocityEngineVersion.VERSION); + log.trace("RuntimeInstance initializing."); + + initializeProperties(); + initializeSelfProperties(); + initializeLog(); + initializeResourceManager(); + initializeDirectives(); + initializeEventHandlers(); + initializeParserPool(); + + initializeIntrospection(); + initializeScopeSettings(); + /* + * initialize the VM Factory. It will use the properties + * accessible from Runtime, so keep this here at the end. + */ + vmFactory.initVelocimacro(); + + log.trace("RuntimeInstance successfully initialized."); + + initialized = true; + initializing = false; + } + catch(RuntimeException re) + { + // initialization failed at some point... try to reset everything + try + { + reset(); + } + catch(RuntimeException re2) {} // prefer throwing the original exception + throw re; + } + finally + { + initializing = false; + } + } + } + + /** + * Resets the instance, so Velocity can be re-initialized again. + * + * @since 2.0.0 + */ + public synchronized void reset() + { + this.configuration = new ExtProperties(); + this.defaultEncoding = null; + this.evaluateScopeName = "evaluate"; + this.eventCartridge = null; + this.initialized = false; + this.initializing = false; + this.overridingProperties = null; + this.parserPool = null; + this.enabledScopeControls.clear(); + this.resourceManager = null; + this.runtimeDirectives = new Hashtable<>(); + this.runtimeDirectivesShared = null; + this.uberSpect = null; + this.stringInterning = false; + this.parserConfiguration = new ParserConfiguration(); + + /* + * create a VM factory, introspector, and application attributes + */ + vmFactory = new VelocimacroFactory( this ); + + /* + * and a store for the application attributes + */ + applicationAttributes = new HashMap<>(); + } + + /** + * Returns true if the RuntimeInstance has been successfully initialized. + * @return True if the RuntimeInstance has been successfully initialized. + * @since 1.5 + */ + @Override + public boolean isInitialized() + { + return initialized; + } + + /** + * Init or die! (with some log help, of course) + */ + private void requireInitialization() + { + if (!initialized) + { + try + { + init(); + } + catch (Exception e) + { + log.error("Could not auto-initialize Velocity", e); + throw new RuntimeException("Velocity could not be initialized!", e); + } + } + } + + /** + * Initialize runtime internal properties + */ + private void initializeSelfProperties() + { + /* initialize string interning (defaults to false) */ + stringInterning = getBoolean(RUNTIME_STRING_INTERNING, true); + + /* initialize indentation mode (defaults to 'lines') */ + String im = getString(SPACE_GOBBLING, "lines"); + try + { + spaceGobbling = SpaceGobbling.valueOf(im.toUpperCase(Locale.ROOT)); + } + catch (NoSuchElementException nse) + { + spaceGobbling = SpaceGobbling.LINES; + } + + /* init parser behavior */ + hyphenAllowedInIdentifiers = getBoolean(PARSER_HYPHEN_ALLOWED, false); + } + + private char getConfiguredCharacter(String configKey, char defaultChar) + { + String configuredChar = getString(configKey); + if (configuredChar != null) + { + if (configuredChar.length() != 1) + { + throw new IllegalArgumentException(String.format("value of '%s' must be a single character string, but is '%s'", configKey, configuredChar)); + } + return configuredChar.charAt(0); + } + return defaultChar; + } + + /** + * Gets the classname for the Uberspect introspection package and + * instantiates an instance. + */ + private void initializeIntrospection() + { + String[] uberspectors = configuration.getStringArray(RuntimeConstants.UBERSPECT_CLASSNAME); + for (String rm : uberspectors) + { + Object o = null; + + try + { + o = ClassUtils.getNewInstance(rm); + } + catch (ClassNotFoundException cnfe) + { + String err = "The specified class for Uberspect (" + rm + + ") does not exist or is not accessible to the current classloader."; + log.error(err); + throw new VelocityException(err, cnfe); + } + catch (InstantiationException ie) + { + throw new VelocityException("Could not instantiate class '" + rm + "'", ie); + } + catch (IllegalAccessException ae) + { + throw new VelocityException("Cannot access class '" + rm + "'", ae); + } + + if (!(o instanceof Uberspect)) + { + String err = "The specified class for Uberspect (" + + rm + ") does not implement " + Uberspect.class.getName() + + "; Velocity is not initialized correctly."; + + log.error(err); + throw new VelocityException(err); + } + + Uberspect u = (Uberspect) o; + + if (u instanceof RuntimeServicesAware) + { + ((RuntimeServicesAware) u).setRuntimeServices(this); + } + + if (uberSpect == null) + { + uberSpect = u; + } else + { + if (u instanceof ChainableUberspector) + { + ((ChainableUberspector) u).wrap(uberSpect); + uberSpect = u; + } else + { + uberSpect = new LinkingUberspector(uberSpect, u); + } + } + } + + if(uberSpect != null) + { + uberSpect.init(); + } + else + { + /* + * someone screwed up. Lets not fool around... + */ + + String err = "It appears that no class was specified as the" + + " Uberspect. Please ensure that all configuration" + + " information is correct."; + + log.error(err); + throw new VelocityException(err); + } + } + + /** + * Initializes the Velocity Runtime with properties file. + * The properties file may be in the file system proper, + * or the properties file may be in the classpath. + */ + private void setDefaultProperties() + { + InputStream inputStream = null; + try + { + inputStream = getClass().getClassLoader() + .getResourceAsStream(DEFAULT_RUNTIME_PROPERTIES); + + if (inputStream == null) + throw new IOException("Resource not found: " + DEFAULT_RUNTIME_PROPERTIES); + + configuration.load( inputStream ); + + /* populate 'defaultEncoding' member */ + defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT); + + log.debug("Default Properties resource: {}", DEFAULT_RUNTIME_PROPERTIES); + } + catch (IOException ioe) + { + String msg = "Cannot get Velocity Runtime default properties!"; + log.error(msg, ioe); + throw new RuntimeException(msg, ioe); + } + finally + { + try + { + if (inputStream != null) + { + inputStream.close(); + } + } + catch (IOException ioe) + { + String msg = "Cannot close Velocity Runtime default properties!"; + log.error(msg, ioe); + throw new RuntimeException(msg, ioe); + } + } + } + + /** + * Allows an external system to set a property in + * the Velocity Runtime. + * + * @param key property key + * @param value property value + */ + @Override + public void setProperty(String key, Object value) + { + if (overridingProperties == null) + { + overridingProperties = new ExtProperties(); + } + + overridingProperties.setProperty(key, value); + } + + + /** + * Add all properties contained in the file fileName to the RuntimeInstance properties + * @param fileName + */ + public void setProperties(String fileName) + { + ExtProperties props = null; + try + { + props = new ExtProperties(fileName); + } + catch (IOException e) + { + throw new VelocityException("Error reading properties from '" + + fileName + "'", e); + } + + Enumeration<String> en = props.keys(); + while (en.hasMoreElements()) + { + String key = en.nextElement(); + setProperty(key, props.get(key)); + } + } + + + /** + * Add all the properties in props to the RuntimeInstance properties + * @param props + */ + public void setProperties(Properties props) + { + Enumeration en = props.keys(); + while (en.hasMoreElements()) + { + String key = en.nextElement().toString(); + setProperty(key, props.get(key)); + } + } + + /** + * Allow an external system to set an ExtProperties + * object to use. + * + * @param configuration + * @since 2.0 + */ + @Override + public void setConfiguration(ExtProperties configuration) + { + if (overridingProperties == null) + { + overridingProperties = configuration; + } + else + { + // Avoid possible ConcurrentModificationException + if (overridingProperties != configuration) + { + overridingProperties.combine(configuration); + } + } + } + + /** + * Add a property to the configuration. If it already + * exists then the value stated here will be added + * to the configuration entry. For example, if + * + * resource.loader = file + * + * is already present in the configuration and you + * + * addProperty("resource.loader", "classpath") + * + * Then you will end up with a Vector like the + * following: + * + * ["file", "classpath"] + * + * @param key + * @param value + */ + @Override + public void addProperty(String key, Object value) + { + if (overridingProperties == null) + { + overridingProperties = new ExtProperties(); + } + + overridingProperties.addProperty(key, value); + } + + /** + * Clear the values pertaining to a particular + * property. + * + * @param key of property to clear + */ + @Override + public void clearProperty(String key) + { + if (overridingProperties != null) + { + overridingProperties.clearProperty(key); + } + } + + /** + * Allows an external caller to get a property. The calling + * routine is required to know the type, as this routine + * will return an Object, as that is what properties can be. + * + * @param key property to return + * @return Value of the property or null if it does not exist. + */ + @Override + public Object getProperty(String key) + { + Object o = null; + + /* + * Before initialization, check the user-entered properties first. + */ + if (!initialized && overridingProperties != null) + { + o = overridingProperties.get(key); + } + + /* + * After initialization, configuration will hold all properties. + */ + if (o == null) + { + o = configuration.getProperty(key); + } + if (o instanceof String) + { + return StringUtils.trim((String) o); + } + else + { + return o; + } + } + + /** + * Initialize Velocity properties, if the default + * properties have not been laid down first then + * do so. Then proceed to process any overriding + * properties. Laying down the default properties + * gives a much greater chance of having a + * working system. + */ + private void initializeProperties() + { + /* + * Always lay down the default properties first as + * to provide a solid base. + */ + if ( !configuration.isInitialized() ) + { + setDefaultProperties(); + } + + if( overridingProperties != null ) + { + configuration.combine(overridingProperties); + + /* reinitialize defaultEncoding in case it is overridden */ + defaultEncoding = getString(INPUT_ENCODING, ENCODING_DEFAULT); + } + } + + /** + * Initialize the Velocity Runtime with a Properties + * object. + * + * @param p Velocity properties for initialization + */ + @Override + public void init(Properties p) + { + setConfiguration(ExtProperties.convertProperties(p)); + init(); + } + + /** + * Initialize the Velocity Runtime with a + * properties file path. + * + * @param configurationFile + */ + @Override + public void init(String configurationFile) + { + setProperties(configurationFile); + init(); + } + + private void initializeResourceManager() + { + /* + * Which resource manager? + */ + Object inst = getProperty(RuntimeConstants.RESOURCE_MANAGER_INSTANCE); + String rm = getString(RuntimeConstants.RESOURCE_MANAGER_CLASS); + + if (inst != null) + { + if (ResourceManager.class.isAssignableFrom(inst.getClass())) + { + resourceManager = (ResourceManager)inst; + resourceManager.initialize(this); + } + else + { + String msg = inst.getClass().getName() + " object set as resource.manager.instance is not a valid org.apache.velocity.runtime.resource.ResourceManager."; + log.error(msg); + throw new VelocityException(msg); + } + } + else if (rm != null && rm.length() > 0) + { + /* + * if something was specified, then make one. + * if that isn't a ResourceManager, consider + * this a huge error and throw + */ + + Object o = null; + + try + { + o = ClassUtils.getNewInstance( rm ); + } + catch (ClassNotFoundException cnfe ) + { + String err = "The specified class for ResourceManager (" + rm + + ") does not exist or is not accessible to the current classloader."; + log.error(err); + throw new VelocityException(err, cnfe); + } + catch (InstantiationException ie) + { + throw new VelocityException("Could not instantiate class '" + rm + "'", ie); + } + catch (IllegalAccessException ae) + { + throw new VelocityException("Cannot access class '" + rm + "'", ae); + } + + if (!(o instanceof ResourceManager)) + { + String err = "The specified class for ResourceManager (" + rm + + ") does not implement " + ResourceManager.class.getName() + + "; Velocity is not initialized correctly."; + + log.error(err); + throw new VelocityException(err); + } + + resourceManager = (ResourceManager) o; + resourceManager.initialize(this); + setProperty(RESOURCE_MANAGER_INSTANCE, resourceManager); + } + else + { + /* + * someone screwed up. Lets not fool around... + */ + + String err = "It appears that no class or instance was specified as the" + + " ResourceManager. Please ensure that all configuration" + + " information is correct."; + + log.error(err); + throw new VelocityException( err ); + } + } + + private void initializeEventHandlers() + { + + eventCartridge = new EventCartridge(); + eventCartridge.setRuntimeServices(this); + + /* + * For each type of event handler, get the class name, instantiate it, and store it. + */ + + String[] referenceinsertion = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION); + if ( referenceinsertion != null ) + { + for (String aReferenceinsertion : referenceinsertion) + { + EventHandler ev = initializeSpecificEventHandler(aReferenceinsertion, RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, ReferenceInsertionEventHandler.class); + if (ev != null) + eventCartridge.addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev); + } + } + + String[] methodexception = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION); + if ( methodexception != null ) + { + for (String aMethodexception : methodexception) + { + EventHandler ev = initializeSpecificEventHandler(aMethodexception, RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, MethodExceptionEventHandler.class); + if (ev != null) + eventCartridge.addMethodExceptionHandler((MethodExceptionEventHandler) ev); + } + } + + String[] includeHandler = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INCLUDE); + if ( includeHandler != null ) + { + for (String anIncludeHandler : includeHandler) + { + EventHandler ev = initializeSpecificEventHandler(anIncludeHandler, RuntimeConstants.EVENTHANDLER_INCLUDE, IncludeEventHandler.class); + if (ev != null) + eventCartridge.addIncludeEventHandler((IncludeEventHandler) ev); + } + } + + String[] invalidReferenceSet = configuration.getStringArray(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES); + if ( invalidReferenceSet != null ) + { + for (String anInvalidReferenceSet : invalidReferenceSet) + { + EventHandler ev = initializeSpecificEventHandler(anInvalidReferenceSet, RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, InvalidReferenceEventHandler.class); + if (ev != null) + { + eventCartridge.addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev); + } + } + } + + + } + + private EventHandler initializeSpecificEventHandler(String classname, String paramName, Class<?> EventHandlerInterface) + { + if ( classname != null && classname.length() > 0) + { + Object o = null; + try + { + o = ClassUtils.getNewInstance(classname); + } + catch (ClassNotFoundException cnfe ) + { + String err = "The specified class for " + + paramName + " (" + classname + + ") does not exist or is not accessible to the current classloader."; + log.error(err); + throw new VelocityException(err, cnfe); + } + catch (InstantiationException ie) + { + throw new VelocityException("Could not instantiate class '" + classname + "'", ie); + } + catch (IllegalAccessException ae) + { + throw new VelocityException("Cannot access class '" + classname + "'", ae); + } + + if (!EventHandlerInterface.isAssignableFrom(EventHandlerInterface)) + { + String err = "The specified class for " + paramName + " (" + + classname + ") does not implement " + + EventHandlerInterface.getName() + + "; Velocity is not initialized correctly."; + + log.error(err); + throw new VelocityException(err); + } + + EventHandler ev = (EventHandler) o; + if ( ev instanceof RuntimeServicesAware ) + ((RuntimeServicesAware) ev).setRuntimeServices(this); + return ev; + + } else + return null; + } + + /** + * Initialize the Velocity logging system. + */ + private void initializeLog() + { + // if we were provided a specific logger or logger name, let's use it + try + { + /* If a Logger instance was set as a configuration + * value, use that. This is any class the user specifies. + */ + Object o = getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE); + if (o != null) + { + // check for a Logger + if (Logger.class.isAssignableFrom(o.getClass())) + { + //looks ok + log = (Logger)o; + } + else + { + String msg = o.getClass().getName() + " object set as runtime.log.instance is not a valid org.slf4j.Logger implementation."; + log.error(msg); + throw new VelocityException(msg); + } + } + else + { + /* otherwise, see if a logger name was specified. + */ + o = getProperty(RuntimeConstants.RUNTIME_LOG_NAME); + if (o != null) + { + if (o instanceof String) + { + log = LoggerFactory.getLogger((String)o); + } + else + { + String msg = o.getClass().getName() + " object set as runtime.log.name is not a valid string."; + log.error(msg); + throw new VelocityException(msg); + } + } + } + /* else keep our default Velocity logger + */ + + /* Initialize LogContext */ + boolean trackLocation = getBoolean(RUNTIME_LOG_TRACK_LOCATION, false); + logContext = new LogContext(trackLocation); + } + catch (Exception e) + { + throw new VelocityException("Error initializing log: " + e.getMessage(), e); + } + } + + + /** + * This methods initializes all the directives + * that are used by the Velocity Runtime. The + * directives to be initialized are listed in + * the RUNTIME_DEFAULT_DIRECTIVES properties + * file. + */ + private void initializeDirectives() + { + Properties directiveProperties = new Properties(); + + /* + * Grab the properties file with the list of directives + * that we should initialize. + */ + + InputStream inputStream = null; + + try + { + inputStream = getClass().getResourceAsStream('/' + DEFAULT_RUNTIME_DIRECTIVES); + + if (inputStream == null) + { + throw new VelocityException("Error loading directive.properties! " + + "Something is very wrong if these properties " + + "aren't being located. Either your Velocity " + + "distribution is incomplete or your Velocity " + + "jar file is corrupted!"); + } + + directiveProperties.load(inputStream); + + } + catch (IOException ioe) + { + String msg = "Error while loading directive properties!"; + log.error(msg, ioe); + throw new RuntimeException(msg, ioe); + } + finally + { + try + { + if (inputStream != null) + { + inputStream.close(); + } + } + catch (IOException ioe) + { + String msg = "Cannot close directive properties!"; + log.error(msg, ioe); + throw new RuntimeException(msg, ioe); + } + } + + + /* + * Grab all the values of the properties. These + * are all class names for example: + * + * org.apache.velocity.runtime.directive.Foreach + */ + Enumeration directiveClasses = directiveProperties.elements(); + + while (directiveClasses.hasMoreElements()) + { + String directiveClass = (String) directiveClasses.nextElement(); + loadDirective(directiveClass); + log.debug("Loaded System Directive: {}", directiveClass); + } + + /* + * now the user's directives + */ + + String[] userdirective = configuration.getStringArray(CUSTOM_DIRECTIVES); + + for (String anUserdirective : userdirective) + { + loadDirective(anUserdirective); + log.debug("Loaded User Directive: {}", anUserdirective); + } + + } + + /** + * Programatically add a directive. + * @param directive + */ + public synchronized void addDirective(Directive directive) + { + runtimeDirectives.put(directive.getName(), directive); + updateSharedDirectivesMap(); + } + + /** + * Retrieve a previously instantiated directive. + * @param name name of the directive + * @return the {@link Directive} for that name + */ + @Override + public Directive getDirective(String name) + { + return runtimeDirectivesShared.get(name); + } + + /** + * Remove a directive. + * @param name name of the directive. + */ + public synchronized void removeDirective(String name) + { + runtimeDirectives.remove(name); + updateSharedDirectivesMap(); + } + + /** + * Makes an unsynchronized copy of the directives map + * that is used for Directive lookups by all parsers. + * + * This follows Copy-on-Write pattern. The cost of creating + * a new map is acceptable since directives are typically + * set and modified only during Velocity setup phase. + */ + private void updateSharedDirectivesMap() + { + runtimeDirectivesShared = new HashMap<>(runtimeDirectives); + } + + /** + * instantiates and loads the directive with some basic checks + * + * @param directiveClass classname of directive to load + */ + public void loadDirective(String directiveClass) + { + try + { + Object o = ClassUtils.getNewInstance( directiveClass ); + + if (o instanceof Directive) + { + Directive directive = (Directive) o; + addDirective(directive); + } + else + { + String msg = directiveClass + " does not implement " + + Directive.class.getName() + "; it cannot be loaded."; + log.error(msg); + throw new VelocityException(msg); + } + } + // The ugly threesome: ClassNotFoundException, + // IllegalAccessException, InstantiationException. + // Ignore Findbugs complaint for now. + catch (Exception e) + { + String msg = "Failed to load Directive: " + directiveClass; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + + /** + * Initializes the Velocity parser pool. + */ + private void initializeParserPool() + { + /* + * First initialize parser class. If it's not valid or not found, it will generate an error + * later on in this method when parser creation is tester. + */ + String parserClassName = getString(PARSER_CLASS, DEFAULT_PARSER_CLASS); + Class<? extends Parser> parserClass; + try + { + parserClass = (Class<? extends Parser>)ClassUtils.getClass(parserClassName); + } + catch (ClassNotFoundException cnfe) + { + throw new VelocityException("parser class not found: " + parserClassName, cnfe); + } + try + { + parserConstructor = parserClass.getConstructor(RuntimeServices.class); + } + catch (NoSuchMethodException nsme) + { + throw new VelocityException("parser class must provide a constructor taking a RuntimeServices argument", nsme); + } + + /* + * Which parser pool? + */ + String pp = getString(RuntimeConstants.PARSER_POOL_CLASS); + + if (pp != null && pp.length() > 0) + { + /* + * if something was specified, then make one. + * if that isn't a ParserPool, consider + * this a huge error and throw + */ + + Object o = null; + + try + { + o = ClassUtils.getNewInstance( pp ); + } + catch (ClassNotFoundException cnfe ) + { + String err = "The specified class for ParserPool (" + + pp + + ") does not exist (or is not accessible to the current classloader."; + log.error(err); + throw new VelocityException(err, cnfe); + } + catch (InstantiationException ie) + { + throw new VelocityException("Could not instantiate class '" + pp + "'", ie); + } + catch (IllegalAccessException ae) + { + throw new VelocityException("Cannot access class '" + pp + "'", ae); + } + + if (!(o instanceof ParserPool)) + { + String err = "The specified class for ParserPool (" + + pp + ") does not implement " + ParserPool.class + + " Velocity not initialized correctly."; + + log.error(err); + throw new VelocityException(err); + } + + parserPool = (ParserPool) o; + + parserPool.initialize(this); + + /* + * test parser creation and use generated parser to fill up customized characters + */ + Parser parser = parserPool.get(); + parserConfiguration = new ParserConfiguration(); + parserConfiguration.setDollarChar(parser.dollar()); + parserConfiguration.setHashChar(parser.hash()); + parserConfiguration.setAtChar(parser.at()); + parserConfiguration.setAsteriskChar(parser.asterisk()); + parserPool.put(parser); + } + else + { + /* + * someone screwed up. Lets not fool around... + */ + + String err = "It appears that no class was specified as the" + + " ParserPool. Please ensure that all configuration" + + " information is correct."; + + log.error(err); + throw new VelocityException( err ); + } + + } + + /** + * Returns a JavaCC generated Parser. + * + * @return Parser javacc generated parser + */ + @Override + public Parser createNewParser() + { + requireInitialization(); + try + { + return parserConstructor.newInstance(this); + } + catch (IllegalAccessException | InstantiationException | InvocationTargetException e) + { + throw new VelocityException("could not build new parser class", e); + } + } + + /** + * Parse the input and return the root of + * AST node structure. + * <br><br> + * In the event that it runs out of parsers in the + * pool, it will create and let them be GC'd + * dynamically, logging that it has to do that. This + * is considered an exceptional condition. It is + * expected that the user will set the + * PARSER_POOL_SIZE property appropriately for their + * application. We will revisit this. + * + * @param reader Reader retrieved by a resource loader + * @param template template being parsed + * @return A root node representing the template as an AST tree. + * @throws ParseException When the template could not be parsed. + */ + @Override + public SimpleNode parse(Reader reader, Template template) + throws ParseException + { + requireInitialization(); + + Parser parser = parserPool.get(); + boolean keepParser = true; + if (parser == null) + { + /* + * if we couldn't get a parser from the pool make one and log it. + */ + log.info("Runtime: ran out of parsers. Creating a new one. " + + " Please increment the parser.pool.size property." + + " The current value is too small."); + parser = createNewParser(); + keepParser = false; + } + + try + { + return parser.parse(reader, template); + } + finally + { + if (keepParser) + { + /* drop the parser Template reference to allow garbage collection */ + parser.resetCurrentTemplate(); + parserPool.put(parser); + } + + } + } + + private void initializeScopeSettings() + { + ExtProperties scopes = configuration.subset(CONTEXT_SCOPE_CONTROL); + if (scopes != null) + { + Iterator<String> scopeIterator = scopes.getKeys(); + while (scopeIterator.hasNext()) + { + String scope = scopeIterator.next(); + boolean enabled = scopes.getBoolean(scope); + if (enabled) enabledScopeControls.add(scope); + } + } + } + + /** + * Renders the input string using the context into the output writer. + * To be used when a template is dynamically constructed, or want to use + * Velocity as a token replacer. + * <br> + * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call + * does not know about macros defined during previous calls. + * + * @param context context to use in rendering input string + * @param out Writer in which to render the output + * @param logTag string to be used as the template name for log + * messages in case of error + * @param instring input string containing the VTL to be rendered + * + * @return true if successful, false otherwise. If false, see + * Velocity runtime log + * @throws ParseErrorException The template could not be parsed. + * @throws MethodInvocationException A method on a context object could not be invoked. + * @throws ResourceNotFoundException A referenced resource could not be loaded. + * @since Velocity 1.6 + */ + @Override + public boolean evaluate(Context context, Writer out, + String logTag, String instring) + { + return evaluate(context, out, logTag, new StringReader(instring)); + } + + /** + * Renders the input reader using the context into the output writer. + * To be used when a template is dynamically constructed, or want to + * use Velocity as a token replacer. + * <br> + * Note! Macros defined in evaluate() calls are not persisted in memory so next evaluate() call + * does not know about macros defined during previous calls. + * + * @param context context to use in rendering input string + * @param writer Writer in which to render the output + * @param logTag string to be used as the template name for log messages + * in case of error + * @param reader Reader containing the VTL to be rendered + * + * @return true if successful, false otherwise. If false, see + * Velocity runtime log + * @throws ParseErrorException The template could not be parsed. + * @throws MethodInvocationException A method on a context object could not be invoked. + * @throws ResourceNotFoundException A referenced resource could not be loaded. + * @since Velocity 1.6 + */ + @Override + public boolean evaluate(Context context, Writer writer, + String logTag, Reader reader) + { + if (logTag == null) + { + throw new NullPointerException("logTag (i.e. template name) cannot be null, you must provide an identifier for the content being evaluated"); + } + + SimpleNode nodeTree = null; + Template t = new Template(); + t.setName(logTag); + try + { + nodeTree = parse(reader, t); + } + catch (ParseException pex) + { + throw new ParseErrorException(pex, null); + } + catch (TemplateInitException pex) + { + throw new ParseErrorException(pex, null); + } + + if (nodeTree == null) + { + return false; + } + else + { + return render(context, writer, logTag, nodeTree); + } + } + + + /** + * Initializes and renders the AST {@link SimpleNode} using the context + * into the output writer. + * + * @param context context to use in rendering input string + * @param writer Writer in which to render the output + * @param logTag string to be used as the template name for log messages + * in case of error + * @param nodeTree SimpleNode which is the root of the AST to be rendered + * + * @return true if successful, false otherwise. If false, see + * Velocity runtime log for errors + * @throws ParseErrorException The template could not be parsed. + * @throws MethodInvocationException A method on a context object could not be invoked. + * @throws ResourceNotFoundException A referenced resource could not be loaded. + * @since Velocity 1.6 + */ + public boolean render(Context context, Writer writer, + String logTag, SimpleNode nodeTree) + { + /* + * we want to init then render + */ + InternalContextAdapterImpl ica = + new InternalContextAdapterImpl(context); + + ica.pushCurrentTemplateName(logTag); + + try + { + try + { + nodeTree.init(ica, this); + } + catch (TemplateInitException pex) + { + throw new ParseErrorException(pex, null); + } + /* + * pass through application level runtime exceptions + */ + catch(RuntimeException e) + { + throw e; + } + catch(Exception e) + { + String msg = "RuntimeInstance.render(): init exception for tag = "+logTag; + log.error(msg, e); + throw new VelocityException(msg, e, getLogContext().getStackTrace()); + } + + try + { + if (isScopeControlEnabled(evaluateScopeName)) + { + Object previous = ica.get(evaluateScopeName); + context.put(evaluateScopeName, new Scope(this, previous)); + } + /* + * optionally put the context in itself if asked so + */ + String self = getString(CONTEXT_AUTOREFERENCE_KEY); + if (self != null) context.put(self, context); + nodeTree.render(ica, writer); + } + catch (StopCommand stop) + { + if (!stop.isFor(this)) + { + throw stop; + } + else + { + log.debug(stop.getMessage()); + } + } + catch (IOException e) + { + throw new VelocityException("IO Error in writer: " + e.getMessage(), e, getLogContext().getStackTrace()); + } + } + finally + { + ica.popCurrentTemplateName(); + if (isScopeControlEnabled(evaluateScopeName)) + { + Object obj = ica.get(evaluateScopeName); + if (obj instanceof Scope) + { + Scope scope = (Scope)obj; + if (scope.getParent() != null) + { + ica.put(evaluateScopeName, scope.getParent()); + } + else if (scope.getReplaced() != null) + { + ica.put(evaluateScopeName, scope.getReplaced()); + } + else + { + ica.remove(evaluateScopeName); + } + } + } + } + + return true; + } + + /** + * Invokes a currently registered Velocimacro with the params provided + * and places the rendered stream into the writer. + * <br> + * Note: currently only accepts args to the VM if they are in the context. + * <br> + * Note: only macros in the global context can be called. This method doesn't find macros defined by + * templates during previous mergeTemplate calls if Velocity.VM_PERM_INLINE_LOCAL has been enabled. + * + * @param vmName name of Velocimacro to call + * @param logTag string to be used for template name in case of error. if null, + * the vmName will be used + * @param params keys for args used to invoke Velocimacro, in java format + * rather than VTL (eg "foo" or "bar" rather than "$foo" or "$bar") + * @param context Context object containing data/objects used for rendering. + * @param writer Writer for output stream + * @return true if Velocimacro exists and successfully invoked, false otherwise. + * @since 1.6 + */ + @Override + public boolean invokeVelocimacro(final String vmName, String logTag, + String[] params, final Context context, + final Writer writer) + { + /* check necessary parameters */ + if (vmName == null || context == null || writer == null) + { + String msg = "RuntimeInstance.invokeVelocimacro(): invalid call: vmName, context, and writer must not be null"; + log.error(msg); + throw new NullPointerException(msg); + } + + /* handle easily corrected parameters */ + if (logTag == null) + { + logTag = vmName; + } + if (params == null) + { + params = new String[0]; + } + + /* does the VM exist? (only global scope is scanned so this doesn't find inline macros in templates) */ + if (!isVelocimacro(vmName, null)) + { + String msg = "RuntimeInstance.invokeVelocimacro(): VM '" + vmName + + "' is not registered."; + log.error(msg); + throw new VelocityException(msg, null, getLogContext().getStackTrace()); + } + + /* now just create the VM call, and use evaluate */ + StringBuilder template = new StringBuilder(String.valueOf(parserConfiguration.getHashChar())); + template.append(vmName); + template.append("("); + for (String param : params) + { + template.append(" $"); + template.append(param); + } + template.append(" )"); + + return evaluate(context, writer, logTag, template.toString()); + } + + /** + * Retrieves and caches the configured default encoding + * for better performance. (VELOCITY-606) + */ + private String getDefaultEncoding() + { + return defaultEncoding; + } + + /** + * Returns a <code>Template</code> from the resource manager. + * This method assumes that the character encoding of the + * template is set by the <code>resource.default_encoding</code> + * property. The default is UTF-8. + * + * @param name The file name of the desired template. + * @return The template. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + */ + @Override + public Template getTemplate(String name) + throws ResourceNotFoundException, ParseErrorException + { + return getTemplate(name, null); + } + + /** + * Returns a <code>Template</code> from the resource manager + * + * @param name The name of the desired template. + * @param encoding Character encoding of the template + * @return The template. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + */ + @Override + public Template getTemplate(String name, String encoding) + throws ResourceNotFoundException, ParseErrorException + { + requireInitialization(); + if (encoding == null) encoding = getDefaultEncoding(); + return (Template) + resourceManager.getResource(name, + ResourceManager.RESOURCE_TEMPLATE, encoding); + } + + /** + * Returns a static content resource from the + * resource manager. Uses the current value + * if INPUT_ENCODING as the character encoding. + * + * @param name Name of content resource to get + * @return parsed ContentResource object ready for use + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException When the template could not be parsed. + */ + @Override + public ContentResource getContent(String name) + throws ResourceNotFoundException, ParseErrorException + { + /* + * the encoding is irrelvant as we don't do any converstion + * the bytestream should be dumped to the output stream + */ + + return getContent(name, getDefaultEncoding()); + } + + /** + * Returns a static content resource from the + * resource manager. + * + * @param name Name of content resource to get + * @param encoding Character encoding to use + * @return parsed ContentResource object ready for use + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException When the template could not be parsed. + */ + @Override + public ContentResource getContent(String name, String encoding) + throws ResourceNotFoundException, ParseErrorException + { + requireInitialization(); + + return (ContentResource) + resourceManager.getResource(name, + ResourceManager.RESOURCE_CONTENT, encoding); + } + + + /** + * Determines if a template exists and returns name of the loader that + * provides it. This is a slightly less hokey way to support + * the Velocity.resourceExists() utility method, which was broken + * when per-template encoding was introduced. We can revisit this. + * + * @param resourceName Name of template or content resource + * @return class name of loader than can provide it + */ + @Override + public String getLoaderNameForResource(String resourceName) + { + requireInitialization(); + + return resourceManager.getLoaderNameForResource(resourceName); + } + + /** + * Returns the configured logger. + * + * @return A Logger instance + * @since 1.5 + */ + @Override + public Logger getLog() + { + return log; + } + + /** + * Get a logger for the specified child namespace. + * If a logger was configured using the runtime.log.instance configuration property, returns this instance. + * Otherwise, uses SLF4J LoggerFactory on baseNamespace '.' childNamespace. + * @param childNamespace + * @return child namespace logger + */ + @Override + public Logger getLog(String childNamespace) + { + Logger log = (Logger)getProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE); + if (log == null) + { + String loggerName = getString(RUNTIME_LOG_NAME, DEFAULT_RUNTIME_LOG_NAME) + "." + childNamespace; + log = LoggerFactory.getLogger(loggerName); + } + return log; + } + + /** + * Get the LogContext object used to tack locations in templates. + * @return LogContext object + * @since 2.2 + */ + @Override + public LogContext getLogContext() + { + return logContext; + } + + /** + * String property accessor method with default to hide the + * configuration implementation. + * + * @param key property key + * @param defaultValue default value to return if key not + * found in resource manager. + * @return value of key or default + */ + @Override + public String getString(String key, String defaultValue) + { + return configuration.getString(key, defaultValue); + } + + /** + * Returns the appropriate VelocimacroProxy object if vmName + * is a valid current Velocimacro. + * + * @param vmName Name of velocimacro requested + * @param renderingTemplate Template we are currently rendering. This + * information is needed when VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true + * and template contains a macro with the same name as the global macro library. + * @param template Template which acts as the host for the macro + * + * @return VelocimacroProxy + */ + @Override + public Directive getVelocimacro(String vmName, Template renderingTemplate, Template template) + { + return vmFactory.getVelocimacro(vmName, renderingTemplate, template); + } + + /** + * Adds a new Velocimacro. Usually called by Macro only while parsing. + * + * @param name Name of velocimacro + * @param macro root AST node of the parsed macro + * @param macroArgs Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + * @param definingTemplate Template containing the source of the macro + * + * @return boolean True if added, false if rejected for some + * reason (either parameters or permission settings) + */ + @Override + public boolean addVelocimacro(String name, + Node macro, + List<Macro.MacroArg> macroArgs, + Template definingTemplate) + { + return vmFactory.addVelocimacro(stringInterning ? name.intern() : name, macro, macroArgs, definingTemplate); + } + + /** + * Checks to see if a VM exists + * + * @param vmName Name of the Velocimacro. + * @param template Template on which to look for the Macro. + * @return True if VM by that name exists, false if not + */ + @Override + public boolean isVelocimacro(String vmName, Template template) + { + return vmFactory.isVelocimacro(stringInterning ? vmName.intern() : vmName, template); + } + + /* -------------------------------------------------------------------- + * R U N T I M E A C C E S S O R M E T H O D S + * -------------------------------------------------------------------- + * These are the getXXX() methods that are a simple wrapper + * around the configuration object. This is an attempt + * to make a the Velocity Runtime the single access point + * for all things Velocity, and allow the Runtime to + * adhere as closely as possible the the Mediator pattern + * which is the ultimate goal. + * -------------------------------------------------------------------- + */ + + /** + * String property accessor method to hide the configuration implementation + * @param key property key + * @return value of key or null + */ + @Override + public String getString(String key) + { + return StringUtils.trim(configuration.getString(key)); + } + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key Property key + * @return value + */ + @Override + public int getInt(String key) + { + return configuration.getInt(key); + } + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key property key + * @param defaultValue The default value. + * @return value + */ + @Override + public int getInt(String key, int defaultValue) + { + return configuration.getInt(key, defaultValue); + } + + /** + * Boolean property accessor method to hide the configuration implementation. + * + * @param key property key + * @param def The default value if property not found. + * @return value of key or default value + */ + @Override + public boolean getBoolean(String key, boolean def) + { + return configuration.getBoolean(key, def); + } + + /** + * Return the velocity runtime configuration object. + * + * @return Configuration object which houses the Velocity runtime + * properties. + */ + @Override + public ExtProperties getConfiguration() + { + return configuration; + } + + /** + * Returns the event handlers for the application. + * @return The event handlers for the application. + * @since 1.5 + */ + @Override + public EventCartridge getApplicationEventCartridge() + { + return eventCartridge; + } + + + /** + * Gets the application attribute for the given key + * + * @param key + * @return The application attribute for the given key. + */ + @Override + public Object getApplicationAttribute(Object key) + { + return applicationAttributes.get(key); + } + + /** + * Sets the application attribute for the given key + * + * @param key + * @param o The new application attribute. + * @return The old value of this attribute or null if it hasn't been set before. + */ + @Override + public Object setApplicationAttribute(Object key, Object o) + { + return applicationAttributes.put(key, o); + } + + /** + * Returns the Uberspect object for this Instance. + * + * @return The Uberspect object for this Instance. + */ + @Override + public Uberspect getUberspect() + { + return uberSpect; + } + + /** + * Whether to use string interning + * + * @return boolean + */ + @Override + public boolean useStringInterning() + { + return stringInterning; + } + + /** + * get space gobbling mode + * @return indentation mode + */ + @Override + public SpaceGobbling getSpaceGobbling() + { + return spaceGobbling; + } + + /** + * get whether hyphens are allowed in identifiers + * @return configured boolean flag + */ + @Override + public boolean isHyphenAllowedInIdentifiers() + { + return hyphenAllowedInIdentifiers; + } + + /** + * Get whether to provide a scope control object for this scope + * @param scopeName + * @return scope control enabled + * @since 2.1 + */ + @Override + public boolean isScopeControlEnabled(String scopeName) + { + return enabledScopeControls.contains(scopeName); + } + + @Override + public ParserConfiguration getParserConfiguration() + { + return parserConfiguration; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java new file mode 100644 index 00000000..eec0e335 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java @@ -0,0 +1,498 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.app.event.EventCartridge; +import org.apache.velocity.context.Context; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.directive.Macro; +import org.apache.velocity.runtime.parser.LogContext; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.runtime.resource.ContentResource; +import org.apache.velocity.util.ExtProperties; +import org.apache.velocity.util.introspection.Uberspect; +import org.slf4j.Logger; + +import java.io.Reader; +import java.io.Writer; +import java.util.List; +import java.util.Properties; + + +/** + * Interface for internal runtime services that are needed by the + * various components w/in Velocity. This was taken from the old + * Runtime singleton, and anything not necessary was removed. + * + * Currently implemented by RuntimeInstance. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a> + * @version $Id$ + */ +public interface RuntimeServices +{ + + /** + * This is the primary initialization method in the Velocity + * Runtime. The systems that are setup/initialized here are + * as follows: + * + * <ul> + * <li>Logging System</li> + * <li>ResourceManager</li> + * <li>Parser Pool</li> + * <li>Global Cache</li> + * <li>Static Content Include System</li> + * <li>Velocimacro System</li> + * </ul> + */ + void init(); + + /** + * Allows an external system to set a property in + * the Velocity Runtime. + * + * @param key property key + * @param value property value + */ + void setProperty(String key, Object value); + + /** + * Allow an external system to set an ExtProperties + * object to use. + * + * @param configuration + * @since 2.0 + */ + void setConfiguration(ExtProperties configuration); + + /** + * Add a property to the configuration. If it already + * exists then the value stated here will be added + * to the configuration entry. For example, if + * + * resource.loader = file + * + * is already present in the configuration and you + * + * addProperty("resource.loader", "classpath") + * + * Then you will end up with a Vector like the + * following: + * + * ["file", "classpath"] + * + * @param key + * @param value + */ + void addProperty(String key, Object value); + + /** + * Clear the values pertaining to a particular + * property. + * + * @param key of property to clear + */ + void clearProperty(String key); + + /** + * Allows an external caller to get a property. The calling + * routine is required to know the type, as this routine + * will return an Object, as that is what properties can be. + * + * @param key property to return + * @return The value. + */ + Object getProperty(String key); + + /** + * Initialize the Velocity Runtime with a Properties + * object. + * + * @param p + */ + void init(Properties p); + + /** + * Initialize the Velocity Runtime with the name of + * ExtProperties object. + * + * @param configurationFile + */ + void init(String configurationFile); + + /** + * Parse the input and return the root of + * AST node structure. + * <br><br> + * In the event that it runs out of parsers in the + * pool, it will create and let them be GC'd + * dynamically, logging that it has to do that. This + * is considered an exceptional condition. It is + * expected that the user will set the + * PARSER_POOL_SIZE property appropriately for their + * application. We will revisit this. + * + * @param reader inputstream retrieved by a resource loader + * @param template template being parsed + * @return The AST representing the template. + * @throws ParseException + */ + SimpleNode parse(Reader reader, Template template) + throws ParseException; + + /** + * Renders the input string using the context into the output writer. + * To be used when a template is dynamically constructed, or want to use + * Velocity as a token replacer. + * + * @param context context to use in rendering input string + * @param out Writer in which to render the output + * @param logTag string to be used as the template name for log + * messages in case of error + * @param instring input string containing the VTL to be rendered + * + * @return true if successful, false otherwise. If false, see + * Velocity runtime log + * @throws ParseErrorException The template could not be parsed. + * @throws MethodInvocationException A method on a context object could not be invoked. + * @throws ResourceNotFoundException A referenced resource could not be loaded. + * @since Velocity 1.6 + */ + boolean evaluate(Context context, Writer out, + String logTag, String instring); + + /** + * Renders the input reader using the context into the output writer. + * To be used when a template is dynamically constructed, or want to + * use Velocity as a token replacer. + * + * @param context context to use in rendering input string + * @param writer Writer in which to render the output + * @param logTag string to be used as the template name for log messages + * in case of error + * @param reader Reader containing the VTL to be rendered + * + * @return true if successful, false otherwise. If false, see + * Velocity runtime log + * @throws ParseErrorException The template could not be parsed. + * @throws MethodInvocationException A method on a context object could not be invoked. + * @throws ResourceNotFoundException A referenced resource could not be loaded. + * @since Velocity 1.6 + */ + boolean evaluate(Context context, Writer writer, + String logTag, Reader reader); + + /** + * Invokes a currently registered Velocimacro with the params provided + * and places the rendered stream into the writer. + * <br> + * Note : currently only accepts args to the VM if they are in the context. + * + * @param vmName name of Velocimacro to call + * @param logTag string to be used for template name in case of error. if null, + * the vmName will be used + * @param params keys for args used to invoke Velocimacro, in java format + * rather than VTL (eg "foo" or "bar" rather than "$foo" or "$bar") + * @param context Context object containing data/objects used for rendering. + * @param writer Writer for output stream + * @return true if Velocimacro exists and successfully invoked, false otherwise. + * @since 1.6 + */ + boolean invokeVelocimacro(final String vmName, String logTag, + String[] params, final Context context, + final Writer writer); + + /** + * Returns a <code>Template</code> from the resource manager. + * This method assumes that the character encoding of the + * template is set by the <code>input.encoding</code> + * property. The default is UTF-8. + * + * @param name The file name of the desired template. + * @return The template. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + */ + Template getTemplate(String name) + throws ResourceNotFoundException, ParseErrorException; + + /** + * Returns a <code>Template</code> from the resource manager + * + * @param name The name of the desired template. + * @param encoding Character encoding of the template + * @return The template. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + */ + Template getTemplate(String name, String encoding) + throws ResourceNotFoundException, ParseErrorException; + + /** + * Returns a static content resource from the + * resource manager. Uses the current value + * if INPUT_ENCODING as the character encoding. + * + * @param name Name of content resource to get + * @return parsed ContentResource object ready for use + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException + */ + ContentResource getContent(String name) + throws ResourceNotFoundException, ParseErrorException; + + /** + * Returns a static content resource from the + * resource manager. + * + * @param name Name of content resource to get + * @param encoding Character encoding to use + * @return parsed ContentResource object ready for use + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException + */ + ContentResource getContent(String name, String encoding) + throws ResourceNotFoundException, ParseErrorException; + + /** + * Determines is a template exists, and returns name of the loader that + * provides it. This is a slightly less hokey way to support + * the Velocity.templateExists() utility method, which was broken + * when per-template encoding was introduced. We can revisit this. + * + * @param resourceName Name of template or content resource + * @return class name of loader than can provide it + */ + String getLoaderNameForResource(String resourceName); + + /** + * String property accessor method with default to hide the + * configuration implementation. + * + * @param key property key + * @param defaultValue default value to return if key not + * found in resource manager. + * @return String value of key or default + */ + String getString(String key, String defaultValue); + + /** + * Returns the appropriate VelocimacroProxy object if strVMname + * is a valid current Velocimacro. + * + * @param vmName Name of velocimacro requested + * @param renderingTemplate Template we are currently rendering. This + * information is needed when VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true + * and template contains a macro with the same name as the global macro library. + * @param template current template + * + * @return VelocimacroProxy + */ + Directive getVelocimacro(String vmName, Template renderingTemplate, Template template); + + /** + * Adds a new Velocimacro. Usually called by Macro only while parsing. + * + * @param name Name of velocimacro + * @param macro root AST node of the parsed macro + * @param macroArgs Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + * @param definingTemplate template containing macro definition + * + * @return boolean True if added, false if rejected for some + * reason (either parameters or permission settings) + */ + boolean addVelocimacro(String name, + Node macro, + List<Macro.MacroArg> macroArgs, + Template definingTemplate); + + + /** + * Checks to see if a VM exists + * + * @param vmName Name of velocimacro + * @param template Template "namespace" + * @return boolean True if VM by that name exists, false if not + */ + boolean isVelocimacro(String vmName, Template template); + + /** + * String property accessor method to hide the configuration implementation + * @param key property key + * @return value of key or null + */ + String getString(String key); + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key property key + * @return int value + */ + int getInt(String key); + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key property key + * @param defaultValue default value + * @return int value + */ + int getInt(String key, int defaultValue); + + /** + * Boolean property accessor method to hide the configuration implementation. + * + * @param key property key + * @param def default default value if property not found + * @return boolean value of key or default value + */ + boolean getBoolean(String key, boolean def); + + /** + * Return the velocity runtime configuration object. + * + * @return ExtProperties configuration object which houses + * the velocity runtime properties. + */ + ExtProperties getConfiguration(); + + /** + * Return the specified application attribute. + * + * @param key The name of the attribute to retrieve. + * @return The value of the attribute. + */ + Object getApplicationAttribute(Object key); + + /** + * Set the specified application attribute. + * + * @param key The name of the attribute to set. + * @param value The attribute value to set. + * @return the displaced attribute value + */ + Object setApplicationAttribute(Object key, Object value); + + /** + * Returns the configured class introspection/reflection + * implementation. + * @return The current Uberspect object. + */ + Uberspect getUberspect(); + + /** + * Returns the configured logger. + * @return A Logger object. + */ + Logger getLog(); + + /** + * Get a logger for the specified child namespace. + * If a logger was configured using the runtime.log.instance configuration property, returns this instance. + * Otherwise, uses SLF4J LoggerFactory on baseNamespace + childNamespace. + * @param childNamespace + * @return child name space logger + */ + Logger getLog(String childNamespace); + + /** + * Get the LogContext object used to tack locations in templates. + * @return LogContext object + * @since 2.2 + */ + LogContext getLogContext(); + + /** + * Returns the event handlers for the application. + * @return The event handlers for the application. + */ + EventCartridge getApplicationEventCartridge(); + + /** + * Returns true if the RuntimeInstance has been successfully initialized. + * @return True if the RuntimeInstance has been successfully initialized. + */ + boolean isInitialized(); + + /** + * Create a new parser instance. + * @return A new parser instance. + */ + Parser createNewParser(); + + /** + * Retrieve a previously instantiated directive. + * @param name name of the directive + * @return the directive with that name, if any + * @since 1.6 + */ + Directive getDirective(String name); + + /** + * Check whether the engine uses string interning + * @return true if string interning is active + */ + boolean useStringInterning(); + + /** + * get space gobbling mode + * @return space gobbling mode + */ + SpaceGobbling getSpaceGobbling(); + + /** + * Get whether hyphens are allowed in identifiers + * @return configured boolean flag + * @since 2.1 + */ + boolean isHyphenAllowedInIdentifiers(); + + /** + * Get whether to provide a scope control object for this scope + * @param scopeName + * @return scope control enabled + * @since 2.1 + */ + boolean isScopeControlEnabled(String scopeName); + + /** + * Get the replacement characters configured for this runtime service's parser + * @return configured replacement characters + * @since 2.2 + */ + ParserConfiguration getParserConfiguration(); +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java new file mode 100644 index 00000000..bc36c26f --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java @@ -0,0 +1,571 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.app.event.EventCartridge; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.directive.Macro; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.runtime.resource.ContentResource; +import org.apache.velocity.util.ExtProperties; +import org.apache.velocity.util.introspection.Uberspect; +import org.slf4j.Logger; + +import java.io.Reader; +import java.util.List; +import java.util.Properties; + +/** + * <p>This is the Runtime system for Velocity. It is the + * single access point for all functionality in Velocity. + * It adheres to the mediator pattern and is the only + * structure that developers need to be familiar with + * in order to get Velocity to perform.</p> + * + * <p>The Runtime will also cooperate with external + * systems, which can make all needed setProperty() calls + * before calling init().</p> + * <pre> + * ----------------------------------------------------------------------- + * N O T E S O N R U N T I M E I N I T I A L I Z A T I O N + * ----------------------------------------------------------------------- + * RuntimeSingleton.init() + * + * If Runtime.init() is called by itself the Runtime will + * initialize with a set of default values. + * ----------------------------------------------------------------------- + * RuntimeSingleton.init(String/Properties) + * + * In this case the default velocity properties are laid down + * first to provide a solid base, then any properties provided + * in the given properties object will override the corresponding + * default property. + * ----------------------------------------------------------------------- + * </pre> + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:jlb@houseofdistraction.com">Jeff Bowden</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magusson Jr.</a> + * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> + * + * @see org.apache.velocity.runtime.RuntimeInstance + * + * @version $Id$ + */ +public class RuntimeSingleton implements RuntimeConstants +{ + private static RuntimeInstance ri = new RuntimeInstance(); + + /** + * This is the primary initialization method in the Velocity + * Runtime. The systems that are setup/initialized here are + * as follows: + * + * <ul> + * <li>Logging System</li> + * <li>ResourceManager</li> + * <li>Event Handlers</li> + * <li>Parser Pool</li> + * <li>Global Cache</li> + * <li>Static Content Include System</li> + * <li>Velocimacro System</li> + * </ul> + * @see RuntimeInstance#init() + */ + public synchronized static void init() + { + ri.init(); + } + + /** + * Resets the instance, so Velocity can be re-initialized again. + * + * @since 2.0.0 + */ + public synchronized static void reset() + { + ri.reset(); + } + + /** + * Returns true if the RuntimeInstance has been successfully initialized. + * @return True if the RuntimeInstance has been successfully initialized. + * @see RuntimeInstance#isInitialized() + * @since 1.5 + */ + public static boolean isInitialized() + { + return ri.isInitialized(); + } + + /** + * Returns the RuntimeServices Instance used by this wrapper. + * + * @return The RuntimeServices Instance used by this wrapper. + */ + public static RuntimeServices getRuntimeServices() + { + return ri; + } + + + /** + * Allows an external system to set a property in + * the Velocity Runtime. + * + * @param key property key + * @param value property value + * @see RuntimeInstance#setProperty(String, Object) + */ + public static void setProperty(String key, Object value) + { + ri.setProperty(key, value); + } + + /** + * Allow an external system to set a Properties + * object to use. + * + * @param configuration + * @see RuntimeInstance#setProperties(Properties) + */ + public static void setProperties(Properties configuration) + { + ri.setProperties(configuration); + } + + /** + * Set an entire configuration at once from a named properties file + * + * @param propsFilename properties filename + * @since 2.1 + */ + public static void setProperties(String propsFilename) + { + ri.setProperties(propsFilename); + } + + /** + * Add a property to the configuration. If it already + * exists then the value stated here will be added + * to the configuration entry. For example, if + * + * resource.loader = file + * + * is already present in the configuration and you + * + * addProperty("resource.loader", "classpath") + * + * Then you will end up with a Vector like the + * following: + * + * ["file", "classpath"] + * + * @param key + * @param value + * @see RuntimeInstance#addProperty(String, Object) + */ + public static void addProperty(String key, Object value) + { + ri.addProperty(key, value); + } + + /** + * Clear the values pertaining to a particular + * property. + * + * @param key of property to clear + * @see RuntimeInstance#clearProperty(String) + */ + public static void clearProperty(String key) + { + ri.clearProperty( key ); + } + + /** + * Allows an external caller to get a property. The calling + * routine is required to know the type, as this routine + * will return an Object, as that is what properties can be. + * + * @param key property to return + * @return Value of the property or null if it does not exist. + * @see RuntimeInstance#getProperty(String) + */ + public static Object getProperty( String key ) + { + return ri.getProperty( key ); + } + + /** + * Initialize the Velocity Runtime with a Properties + * object. + * + * @param p + * @see RuntimeInstance#init(Properties) + */ + public static void init(Properties p) + { + ri.init(p); + } + + /** + * Initialize the Velocity Runtime with the name of + * a properties file. + * + * @param configurationFile + * @see RuntimeInstance#init(String) + */ + public static void init(String configurationFile) + { + ri.init( configurationFile ); + } + + /** + * Parse the input and return the root of + * AST node structure. + * <br><br> + * In the event that it runs out of parsers in the + * pool, it will create and let them be GC'd + * dynamically, logging that it has to do that. This + * is considered an exceptional condition. It is + * expected that the user will set the + * PARSER_POOL_SIZE property appropriately for their + * application. We will revisit this. + * + * @param reader Reader retrieved by a resource loader + * @param template Template being parsed + * @return A root node representing the template as an AST tree. + * @throws ParseException When the template could not be parsed. + * @see RuntimeInstance#parse(Reader, Template) + */ + public static SimpleNode parse( Reader reader, Template template ) + throws ParseException + { + return ri.parse(reader, template); + } + + /** + * Returns a <code>Template</code> from the resource manager. + * This method assumes that the character encoding of the + * template is set by the <code>resource.default_encoding</code> + * property. The default is UTF-8. + * + * @param name The file name of the desired template. + * @return The template. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + * @see RuntimeInstance#getTemplate(String) + */ + public static Template getTemplate(String name) + throws ResourceNotFoundException, ParseErrorException + { + return ri.getTemplate(name); + } + + /** + * Returns a <code>Template</code> from the resource manager + * + * @param name The name of the desired template. + * @param encoding Character encoding of the template + * @return The template. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + * @see RuntimeInstance#getTemplate(String, String) + */ + public static Template getTemplate(String name, String encoding) + throws ResourceNotFoundException, ParseErrorException + { + return ri.getTemplate(name, encoding); + } + + /** + * Returns a static content resource from the + * resource manager. Uses the current value + * if INPUT_ENCODING as the character encoding. + * + * @param name Name of content resource to get + * @return parsed ContentResource object ready for use + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException When the template could not be parsed. + * @see RuntimeInstance#getContent(String) + */ + public static ContentResource getContent(String name) + throws ResourceNotFoundException, ParseErrorException + { + return ri.getContent(name); + } + + /** + * Returns a static content resource from the + * resource manager. + * + * @param name Name of content resource to get + * @param encoding Character encoding to use + * @return parsed ContentResource object ready for use + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException When the template could not be parsed. + * @see RuntimeInstance#getContent(String, String) + */ + public static ContentResource getContent( String name, String encoding ) + throws ResourceNotFoundException, ParseErrorException + { + return ri.getContent(name, encoding); + } + + + /** + * Determines is a template exists, and returns name of the loader that + * provides it. This is a slightly less hokey way to support + * the Velocity.templateExists() utility method, which was broken + * when per-template encoding was introduced. We can revisit this. + * + * @param resourceName Name of template or content resource + * @return class name of loader than can provide it + * @see RuntimeInstance#getLoaderNameForResource(String) + */ + public static String getLoaderNameForResource( String resourceName ) + { + return ri.getLoaderNameForResource(resourceName); + } + + + /** + * Returns the configured logger. + * + * @return A Logger instance + * @see RuntimeInstance#getLog() + * @since 1.5 + */ + public static Logger getLog() + { + return ri.getLog(); + } + + /** + * String property accessor method with default to hide the + * configuration implementation. + * + * @param key property key + * @param defaultValue default value to return if key not + * found in resource manager. + * @return value of key or default + * @see RuntimeInstance#getString(String, String) + */ + public static String getString( String key, String defaultValue) + { + return ri.getString(key, defaultValue); + } + + /** + * Returns the appropriate VelocimacroProxy object if strVMname + * is a valid current Velocimacro. + * + * @param vmName Name of velocimacro requested + * @param renderingTemplate Template we are currently rendering. This + * information is needed when VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL setting is true + * and template contains a macro with the same name as the global macro library. + * @param template current template + * + * @return VelocimacroProxy + */ + public static Directive getVelocimacro(String vmName, Template renderingTemplate, Template template) + { + return ri.getVelocimacro(vmName, renderingTemplate, template); + } + + /** + * Adds a new Velocimacro. Usually called by Macro only while parsing. + * + * @param name Name of a new velocimacro. + * @param macro root AST node of the parsed macro + * @param macroArgs Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + * @param definingTemplate Template containing the definition of the macro. + * @return true for success + */ + public static boolean addVelocimacro(String name, Node macro, + List<Macro.MacroArg> macroArgs, Template definingTemplate) + { + return ri.addVelocimacro(name, macro, macroArgs, definingTemplate); + } + + /** + * Checks to see if a VM exists + * + * @param vmName Name of the Velocimacro. + * @param template Template on which to look for the Macro. + * @return True if VM by that name exists, false if not + */ + public static boolean isVelocimacro(String vmName, Template template) + { + return ri.isVelocimacro(vmName, template); + } + + /* -------------------------------------------------------------------- + * R U N T I M E A C C E S S O R M E T H O D S + * -------------------------------------------------------------------- + * These are the getXXX() methods that are a simple wrapper + * around the configuration object. This is an attempt + * to make a the Velocity Runtime the single access point + * for all things Velocity, and allow the Runtime to + * adhere as closely as possible the the Mediator pattern + * which is the ultimate goal. + * -------------------------------------------------------------------- + */ + + /** + * String property accessor method to hide the configuration implementation + * @param key property key + * @return value of key or null + * @see RuntimeInstance#getString(String) + */ + public static String getString(String key) + { + return ri.getString( key ); + } + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key Property key + * @return value + * @see RuntimeInstance#getInt(String) + */ + public static int getInt( String key ) + { + return ri.getInt(key); + } + + /** + * Int property accessor method to hide the configuration implementation. + * + * @param key property key + * @param defaultValue The default value. + * @return value + * @see RuntimeInstance#getInt(String, int) + */ + public static int getInt( String key, int defaultValue ) + { + return ri.getInt( key, defaultValue ); + } + + /** + * Boolean property accessor method to hide the configuration implementation. + * + * @param key property key + * @param def The default value if property not found. + * @return value of key or default value + * @see RuntimeInstance#getBoolean(String, boolean) + */ + public static boolean getBoolean( String key, boolean def ) + { + return ri.getBoolean(key, def); + } + + /** + * Directly set the ExtProperties configuration object + * @param configuration + * @see RuntimeInstance#setConfiguration(ExtProperties) + */ + public static void setConfiguration(ExtProperties configuration) + { + ri.setConfiguration(configuration); + } + + /** + * Return the velocity runtime configuration object. + * + * @return ExtProperties configuration object which houses + * the velocity runtime properties. + * @see RuntimeInstance#getConfiguration() + */ + public static ExtProperties getConfiguration() + { + return ri.getConfiguration(); + } + + /** + * Returns the event handlers for the application. + * @return The event handlers for the application. + * @see RuntimeInstance#getApplicationEventCartridge() + * @since 1.5 + */ + public EventCartridge getEventCartridge() + { + return ri.getApplicationEventCartridge(); + } + + /** + * Gets the application attribute for the given key + * + * @see org.apache.velocity.runtime.RuntimeServices#getApplicationAttribute(Object) + * @param key + * @return The application attribute for the given key. + * @see RuntimeInstance#getApplicationAttribute(Object) + */ + public static Object getApplicationAttribute(Object key) + { + return ri.getApplicationAttribute(key); + } + + /** + * Returns the Uberspect object for this Instance. + * + * @return The Uberspect object for this Instance. + * @see org.apache.velocity.runtime.RuntimeServices#getUberspect() + * @see RuntimeInstance#getUberspect() + */ + public static Uberspect getUberspect() + { + return ri.getUberspect(); + } + + + /** + * Remove a directive. + * + * @param name name of the directive. + */ + public static void removeDirective(String name) + { + ri.removeDirective(name); + } + + /** + * Instantiates and loads the directive with some basic checks. + * + * @param directiveClass classname of directive to load + */ + public static void loadDirective(String directiveClass) + { + ri.loadDirective(directiveClass); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java new file mode 100644 index 00000000..b7961a5d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java @@ -0,0 +1,639 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.Template; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.directive.Macro; +import org.apache.velocity.runtime.directive.VelocimacroProxy; +import org.apache.velocity.runtime.parser.node.Node; + +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +/** + * VelocimacroFactory.java + * + * manages the set of VMs in a running Velocity engine. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class VelocimacroFactory +{ + /** + * runtime services for this instance + */ + private final RuntimeServices rsvc; + + /** + * the log for this instance + */ + private Logger log = null; + + /** + * VMManager: deal with namespace management + * and actually keeps all the VM definitions + */ + private VelocimacroManager vmManager = null; + + /** + * determines if replacement of global VMs are allowed + * controlled by VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL + */ + private boolean replaceAllowed = false; + + /** + * controls if new VMs can be added. Set by + * VM_PERM_ALLOW_INLINE Note the assumption that only + * through inline defs can this happen. + * additions through autoloaded VMs is allowed + */ + private boolean addNewAllowed = true; + + /** + * sets if template-local namespace in used + */ + private boolean templateLocal = false; + + /** + * determines if the libraries are auto-loaded + * when they change + */ + private boolean autoReloadLibrary = false; + + /** + * vector of the library names + */ + private List<String> macroLibVec = null; + + /** + * map of the library Template objects + * used for reload determination + */ + private Map<String, Twonk> libModMap; + + /** + * C'tor for the VelociMacro factory. + * + * @param rsvc Reference to a runtime services object. + */ + public VelocimacroFactory(final RuntimeServices rsvc) + { + this.rsvc = rsvc; + + /* + * we always access in a synchronized(), so we + * can use an unsynchronized hashmap + */ + libModMap = new HashMap<>(); + vmManager = new VelocimacroManager(rsvc); + } + + /** + * initialize the factory - setup all permissions + * load all global libraries. + */ + public void initVelocimacro() + { + /* + * maybe I'm just paranoid... + */ + synchronized(this) + { + log = rsvc.getLog("macro"); + log.trace("initialization starting."); + + /* + * allow replacements while we add the libraries, if exist + */ + setReplacementPermission(true); + + /* + * add all library macros to the global namespace + */ + + vmManager.setNamespaceUsage(false); + + /* + * now, if there is a global or local libraries specified, use them. + * All we have to do is get the template. The template will be parsed; + * VM's are added during the parse phase + */ + + Object libfiles = rsvc.getProperty(RuntimeConstants.VM_LIBRARY); + + if (libfiles == null) + { + log.debug("\"{}\" is not set. Trying default library: {}", RuntimeConstants.VM_LIBRARY, RuntimeConstants.VM_LIBRARY_DEFAULT); + + // try the default library. + if (rsvc.getLoaderNameForResource(RuntimeConstants.VM_LIBRARY_DEFAULT) != null) + { + libfiles = RuntimeConstants.VM_LIBRARY_DEFAULT; + } + else + { + // try the old default library + log.debug("Default library {} not found. Trying old default library: {}", RuntimeConstants.VM_LIBRARY_DEFAULT, DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT); + if (rsvc.getLoaderNameForResource(RuntimeConstants.OLD_VM_LIBRARY_DEFAULT) != null) + { + libfiles = RuntimeConstants.OLD_VM_LIBRARY_DEFAULT; + } + else + { + log.debug("Old default library {} not found.", DeprecatedRuntimeConstants.OLD_VM_LIBRARY_DEFAULT); + } + } + } + + if(libfiles != null) + { + macroLibVec = new ArrayList<>(); + if (libfiles instanceof Vector) + { + macroLibVec.addAll((Vector<String>)libfiles); + } + else if (libfiles instanceof String) + { + macroLibVec.add((String)libfiles); + } + + for (String lib : macroLibVec) + { + /* + * only if it's a non-empty string do we bother + */ + + if (StringUtils.isNotEmpty(lib)) + { + /* + * let the VMManager know that the following is coming + * from libraries - need to know for auto-load + */ + + vmManager.setRegisterFromLib(true); + + log.debug("adding VMs from VM library: {}", lib); + + try + { + Template template = rsvc.getTemplate(lib); + + /* + * save the template. This depends on the assumption + * that the Template object won't change - currently + * this is how the Resource manager works + */ + + Twonk twonk = new Twonk(); + twonk.template = template; + twonk.modificationTime = template.getLastModified(); + libModMap.put(lib, twonk); + } + catch (Exception e) + { + String msg = "Velocimacro: Error using VM library: " + lib; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + log.trace("VM library registration complete."); + + vmManager.setRegisterFromLib(false); + } + } + } + + /* + * now, the permissions + */ + + + /* + * allowinline: anything after this will be an inline macro, I think + * there is the question if a #include is an inline, and I think so + * + * default = true + */ + setAddMacroPermission(true); + + if (!rsvc.getBoolean( RuntimeConstants.VM_PERM_ALLOW_INLINE, true)) + { + setAddMacroPermission(false); + + log.debug("allowInline = false: VMs can NOT be defined inline in templates"); + } + else + { + log.debug("allowInline = true: VMs can be defined inline in templates"); + } + + /* + * allowInlineToReplaceGlobal: allows an inline VM , if allowed at all, + * to replace an existing global VM + * + * default = false + */ + setReplacementPermission(false); + + if (rsvc.getBoolean( + RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, false)) + { + setReplacementPermission(true); + + log.debug("allowInlineToOverride = true: VMs " + + "defined inline may replace previous VM definitions"); + } + else + { + log.debug("allowInlineToOverride = false: VMs " + + "defined inline may NOT replace previous VM definitions"); + } + + /* + * now turn on namespace handling as far as permissions allow in the + * manager, and also set it here for gating purposes + */ + vmManager.setNamespaceUsage(true); + + /* + * template-local inline VM mode: default is off + */ + setTemplateLocalInline(rsvc.getBoolean( + RuntimeConstants.VM_PERM_INLINE_LOCAL, false)); + + if (getTemplateLocalInline()) + { + log.debug("allowInlineLocal = true: VMs " + + "defined inline will be local to their defining template only."); + } + else + { + log.debug("allowInlineLocal = false: VMs " + + "defined inline will be global in scope if allowed."); + } + + vmManager.setTemplateLocalInlineVM(getTemplateLocalInline()); + + /* + * autoload VM libraries + */ + setAutoload(rsvc.getBoolean(RuntimeConstants.VM_LIBRARY_AUTORELOAD, false)); + + if (getAutoload()) + { + log.debug("autoload on: VM system " + + "will automatically reload global library macros"); + } + else + { + log.debug("autoload off: VM system " + + "will not automatically reload global library macros"); + } + + log.trace("Velocimacro: initialization complete."); + } + } + + /** + * Adds a macro to the factory. + * + * @param name Name of the Macro to add. + * @param macroBody root node of the parsed macro AST + * @param macroArgs Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + * @param definingTemplate template containing the macro definition + * @return true if Macro was registered successfully. + * @since 1.6 + */ + public boolean addVelocimacro(String name, Node macroBody, + List<Macro.MacroArg> macroArgs, Template definingTemplate) + { + // Called by RuntimeInstance.addVelocimacro + + /* + * maybe we should throw an exception, maybe just tell + * the caller like this... + * + * I hate this: maybe exceptions are in order here... + * They definitely would be if this was only called by directly + * by users, but Velocity calls this internally. + */ + if (name == null || macroBody == null || macroArgs == null || + definingTemplate == null) + { + String msg = "VM '"+name+"' addition rejected: "; + if (name == null) + { + msg += "name"; + } + else if (macroBody == null) + { + msg += "macroBody"; + } + else if (macroArgs == null) + { + msg += "macroArgs"; + } + else + { + msg += "sourceTemplate"; + } + msg += " argument was null"; + log.error(msg); + throw new NullPointerException(msg); + } + + /* + * see if the current ruleset allows this addition + */ + + if (!canAddVelocimacro(name, definingTemplate)) + { + return false; + } + + synchronized(this) + { + vmManager.addVM(name, macroBody, macroArgs, definingTemplate, replaceAllowed); + } + log.debug("added VM {}: source={}", name, definingTemplate); + return true; + } + + + /** + * determines if a given macro/namespace (name, source) combo is allowed + * to be added + * + * @param name Name of VM to add + * @param definingTemplate template containing the source of the macro + * @return true if it is allowed to be added, false otherwise + */ + private synchronized boolean canAddVelocimacro(String name, Template definingTemplate) + { + /* + * short circuit and do it if autoloader is on, and the + * template is one of the library templates + */ + + if (autoReloadLibrary && (macroLibVec != null)) + { + if( macroLibVec.contains(definingTemplate.getName()) ) + return true; + } + + + /* + * maybe the rules should be in manager? I dunno. It's to manage + * the namespace issues first, are we allowed to add VMs at all? + * This trumps all. + */ + if (!addNewAllowed) + { + log.warn("VM addition rejected: {}: inline VelociMacros not allowed.", name); + return false; + } + + /* + * are they local in scope? Then it is ok to add. + */ + if (!templateLocal) + { + /* + * otherwise, if we have it already in global namespace, and they can't replace + * since local templates are not allowed, the global namespace is implied. + * remember, we don't know anything about namespace management here, so lets + * note do anything fancy like trying to give it the global namespace here + * + * so if we have it, and we aren't allowed to replace, bail + */ + if (!replaceAllowed && isVelocimacro(name, definingTemplate)) + { + /* + * Concurrency fix: the log entry was changed to debug scope because it + * causes false alarms when several concurrent threads simultaneously (re)parse + * some macro + */ + log.debug("VM addition rejected: {}: inline not allowed to replace existing VM", name); + return false; + } + } + + return true; + } + + /** + * Tells the world if a given directive string is a Velocimacro + * @param vm Name of the Macro. + * @param template Source template from which the macro should be loaded. + * @return True if the given name is a macro. + */ + public boolean isVelocimacro(String vm, Template template) + { + // synchronization removed + return(vmManager.get(vm, null, template) != null); + } + + /** + * actual factory: creates a Directive that will + * behave correctly wrt getting the framework to + * dig out the correct # of args + * @param vmName Name of the Macro. + * @param renderingTemplate destination template + * @param sourceTemplate Source template from which the macro should be loaded. + * @return A directive representing the Macro. + */ + public Directive getVelocimacro(String vmName, Template renderingTemplate, Template sourceTemplate) + { + VelocimacroProxy vp = null; + + vp = vmManager.get(vmName, renderingTemplate, sourceTemplate); + + /* + * if this exists, and autoload is on, we need to check where this VM came from + */ + + if (vp != null && autoReloadLibrary ) + { + synchronized (this) + { + /* + * see if this VM came from a library. Need to pass sourceTemplate in the event + * namespaces are set, as it could be masked by local + */ + + String lib = vmManager.getLibraryName(vmName, sourceTemplate); + + if (lib != null) + { + try + { + /* + * get the template from our map + */ + + Twonk tw = libModMap.get(lib); + + if (tw != null) + { + Template template = tw.template; + + /* + * now, compare the last modified time of the resource with the last + * modified time of the template if the file has changed, then reload. + * Otherwise, we should be ok. + */ + + long tt = tw.modificationTime; + long ft = template.getResourceLoader().getLastModified(template); + + if (ft > tt) + { + log.debug("auto-reloading VMs from VM library: {}", lib); + + /* + * when there are VMs in a library that invoke each other, there are + * calls into getVelocimacro() from the init() process of the VM + * directive. To stop the infinite loop we save the current time + * reported by the resource loader and then be honest when the + * reload is complete + */ + + tw.modificationTime = ft; + + template = rsvc.getTemplate(lib); + + /* + * and now we be honest + */ + + tw.template = template; + tw.modificationTime = template.getLastModified(); + + /* + * note that we don't need to put this twonk + * back into the map, as we can just use the + * same reference and this block is synchronized + */ + } + } + } + catch (Exception e) + { + String msg = "Velocimacro: Error using VM library: " + lib; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + vp = vmManager.get(vmName, sourceTemplate, renderingTemplate); + } + } + } + + return vp; + } + + /** + * sets permission to have VMs local in scope to their declaring template note that this is + * really taken care of in the VMManager class, but we need it here for gating purposes in addVM + * eventually, I will slide this all into the manager, maybe. + */ + private void setTemplateLocalInline(boolean b) + { + templateLocal = b; + } + + private boolean getTemplateLocalInline() + { + return templateLocal; + } + + /** + * sets the permission to add new macros + */ + private boolean setAddMacroPermission(final boolean addNewAllowed) + { + boolean b = this.addNewAllowed; + this.addNewAllowed = addNewAllowed; + return b; + } + + /** + * sets the permission for allowing addMacro() calls to replace existing VM's + */ + private boolean setReplacementPermission(boolean arg) + { + boolean b = replaceAllowed; + replaceAllowed = arg; + vmManager.setInlineReplacesGlobal(arg); + return b; + } + + /** + * set the switch for automatic reloading of + * global library-based VMs + */ + private void setAutoload(boolean b) + { + autoReloadLibrary = b; + } + + /** + * get the switch for automatic reloading of + * global library-based VMs + */ + private boolean getAutoload() + { + return autoReloadLibrary; + } + + /** + * small container class to hold the tuple + * of a template and modification time. + * We keep the modification time so we can + * 'override' it on a reload to prevent + * recursive reload due to inter-calling + * VMs in a library + */ + private static class Twonk + { + /** Template kept in this container. */ + public Template template; + + /** modification time of the template. */ + public long modificationTime; + } +} + + + + + + + diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroManager.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroManager.java new file mode 100644 index 00000000..63dc92b9 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroManager.java @@ -0,0 +1,355 @@ +package org.apache.velocity.runtime; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.directive.Macro; +import org.apache.velocity.runtime.directive.VelocimacroProxy; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages VMs in namespaces. Currently, two namespace modes are + * supported: + * + * <ul> + * <li>flat - all allowable VMs are in the global namespace</li> + * <li>local - inline VMs are added to it's own template namespace</li> + * </ul> + * + * Thanks to <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a> + * for some ideas incorporated here. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:JFernandez@viquity.com">Jose Alberto Fernandez</a> + * @version $Id$ + */ +public class VelocimacroManager +{ + private boolean registerFromLib = false; + + /** reference to global namespace hash */ + private final Map<String, MacroEntry> globalNamespace; + + /** set of names of library templates/namespaces */ + private final Map<String, Template> libraries = new ConcurrentHashMap<>(17, 0.5f, 20); + + private RuntimeServices rsvc = null; + + /* + * big switch for namespaces. If true, then properties control + * usage. If false, no. + */ + private boolean namespacesOn = true; + private boolean inlineLocalMode = false; + private boolean inlineReplacesGlobal = false; + + /** + * Adds the global namespace to the hash. + */ + VelocimacroManager(RuntimeServices rsvc) + { + /* + * add the global namespace to the namespace hash. We always have that. + */ + + globalNamespace = new ConcurrentHashMap<>(101, 0.5f, 20); + this.rsvc = rsvc; + } + + /** + * Adds a VM definition to the cache. + * + * Called by VelocimacroFactory.addVelociMacro (after parsing and discovery in Macro directive) + * + * @param vmName Name of the new VelociMacro. + * @param macroBody String representation of the macro body. + * @param macroArgs Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + * @param definingTemplate The template from which this macro has been loaded. + * @param canReplaceGlobalMacro whether this macro can replace a global macro + * @return Whether everything went okay. + */ + public boolean addVM(final String vmName, final Node macroBody, List<Macro.MacroArg> macroArgs, + final Template definingTemplate, boolean canReplaceGlobalMacro) + { + if (macroBody == null) + { + // happens only if someone uses this class without the Macro directive + // and provides a null value as an argument + throw new VelocityException("Null AST for "+vmName+" in " + definingTemplate.getName()); + } + + MacroEntry me = new MacroEntry(vmName, macroBody, macroArgs, definingTemplate.getName(), rsvc); + + me.setFromLibrary(registerFromLib); + + /* + * the client (VMFactory) will signal to us via + * registerFromLib that we are in startup mode registering + * new VMs from libraries. Therefore, we want to + * addto the library map for subsequent auto reloads + */ + + boolean isLib = true; + + MacroEntry exist = globalNamespace.get(vmName); + + if (registerFromLib) + { + libraries.put(definingTemplate.getName(), definingTemplate); + } + else + { + /* + * now, we first want to check to see if this namespace (template) + * is actually a library - if so, we need to use the global namespace + * we don't have to do this when registering, as namespaces should + * be shut off. If not, the default value is true, so we still go + * global + */ + + isLib = libraries.containsKey(definingTemplate.getName()); + } + + if ( !isLib && usingNamespaces() ) + { + definingTemplate.getMacros().put(vmName, me); + } + else + { + /* + * otherwise, add to global template. First, check if we + * already have it to preserve some of the autoload information + */ + + + if (exist != null) + { + me.setFromLibrary(exist.getFromLibrary()); + } + + /* + * now add it + */ + + globalNamespace.put(vmName, me); + + } + return true; + } + + /** + * Gets a VelocimacroProxy object by the name / source template duple. + * + * @param vmName Name of the VelocityMacro to look up. + * @param renderingTemplate Template we are currently rendering. + * @param template Source Template. + * @return A proxy representing the Macro. + */ + public VelocimacroProxy get(final String vmName, final Template renderingTemplate, final Template template) + { + if( inlineReplacesGlobal && renderingTemplate != null ) + { + /* + * if VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL is true (local macros can + * override global macros) and we know which template we are rendering at the + * moment, check if local namespace contains a macro we are looking for + * if so, return it instead of the global one + */ + + MacroEntry me = (MacroEntry)renderingTemplate.getMacros().get(vmName); + if( me != null ) + { + return me.getProxy(); + } + } + + if( usingNamespaces() && template != null ) + { + MacroEntry me = (MacroEntry)template.getMacros().get(vmName); + if( template.getMacros().size() > 0 && me != null ) + { + return me.getProxy(); + } + } + + MacroEntry me = globalNamespace.get(vmName); + + if (me != null) + { + return me.getProxy(); + } + + return null; + } + + /** + * public switch to let external user of manager to control namespace + * usage indep of properties. That way, for example, at startup the + * library files are loaded into global namespace + * + * @param namespaceOn True if namespaces should be used. + */ + public void setNamespaceUsage(final boolean namespaceOn) + { + this.namespacesOn = namespaceOn; + } + + /** + * Should macros registered from Libraries be marked special? + * @param registerFromLib True if macros from Libs should be marked. + */ + public void setRegisterFromLib(final boolean registerFromLib) + { + this.registerFromLib = registerFromLib; + } + + /** + * Should macros from the same template be inlined? + * + * @param inlineLocalMode True if macros should be inlined on the same template. + */ + public void setTemplateLocalInlineVM(final boolean inlineLocalMode) + { + this.inlineLocalMode = inlineLocalMode; + } + + /** + * determines if currently using namespaces. + * + * @return true if using namespaces, false if not + */ + private boolean usingNamespaces() + { + /* + * if the big switch turns of namespaces, then ignore the rules + */ + + if (!namespacesOn) + { + return false; + } + + /* + * currently, we only support the local template namespace idea + */ + + return inlineLocalMode; + + } + + /** + * Return the library name for a given macro. + * @param vmName Name of the Macro to look up. + * @param template Template + * @return The name of the library which registered this macro in a namespace. + */ + public String getLibraryName(final String vmName, Template template) + { + if (usingNamespaces()) + { + /* + * if we have this macro defined in this namespace, then + * it is masking the global, library-based one, so + * just return null + */ + MacroEntry me = (MacroEntry)template.getMacros().get(vmName); + if( me != null ) + return null; + } + + MacroEntry me = globalNamespace.get(vmName); + + if (me != null) + { + return me.getSourceTemplate(); + } + + return null; + } + + /** + * @param is + * @since 1.6 + */ + public void setInlineReplacesGlobal(boolean is) + { + inlineReplacesGlobal = is; + } + + + /** + * wrapper class for holding VM information + */ + private static class MacroEntry + { + private final String sourceTemplate; + private boolean fromLibrary = false; + private VelocimacroProxy vp; + + private MacroEntry(final String vmName, final Node macro, + List<Macro.MacroArg> macroArgs, final String sourceTemplate, + RuntimeServices rsvc) + { + this.sourceTemplate = sourceTemplate; + + vp = new VelocimacroProxy(); + vp.init(rsvc); + vp.setName(vmName); + vp.setMacroArgs(macroArgs); + vp.setNodeTree((SimpleNode)macro); + vp.setLocation(macro.getLine(), macro.getColumn(), macro.getTemplate()); + } + + /** + * Has the macro been registered from a library. + * @param fromLibrary True if the macro was registered from a Library. + */ + public void setFromLibrary(final boolean fromLibrary) + { + this.fromLibrary = fromLibrary; + } + + /** + * Returns true if the macro was registered from a library. + * @return True if the macro was registered from a library. + */ + public boolean getFromLibrary() + { + return fromLibrary; + } + + public String getSourceTemplate() + { + return sourceTemplate; + } + + VelocimacroProxy getProxy() + { + return vp; + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java new file mode 100755 index 00000000..e2d34a78 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java @@ -0,0 +1,209 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.Renderable; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.util.StringBuilderWriter; +import org.apache.velocity.util.StringUtils; +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.Writer; + +/** + * Directive that puts an unrendered AST block in the context + * under the specified key, postponing rendering until the + * reference is used and rendered. + * + * @author Andrew Tetlaw + * @author Nathan Bubna + * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a> + * @since 1.7 + * @version $Id: Block.java 686842 2008-08-18 18:29:31Z nbubna $ + */ +public abstract class Block extends Directive +{ + protected Node block; + protected Logger log; + protected int maxDepth; + protected String key; + + /** + * Return type of this directive. + * @return type, DirectiveConstants.BLOCK or DirectiveConstants.LINE + */ + @Override + public int getType() + { + return BLOCK; + } + + /** + * simple init - get the key + * @param rs + * @param context + * @param node + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + throws TemplateInitException + { + super.init(rs, context, node); + + log = rsvc.getLog(); + + /* + * No checking is done. We just grab the last child node and assume + * that it's the block! + */ + block = node.jjtGetChild(node.jjtGetNumChildren() - 1); + } + + /** + * renders block directive + * @param context + * @param writer + * @return success status + */ + public boolean render(InternalContextAdapter context, Writer writer) + { + preRender(context); + try + { + return block.render(context, writer); + } + catch (IOException e) + { + String msg = "Failed to render " + id(context) + " to writer at " + + StringUtils.formatFileString(this); + log.error(msg, e); + throw new RuntimeException(msg, e); + } + catch (StopCommand stop) + { + if (!stop.isFor(this)) + { + throw stop; + } + return true; + } + finally + { + postRender(context); + } + } + + /** + * Creates a string identifying the source and location of the block + * definition, and the current template being rendered if that is + * different. + * @param context + * @return id string + */ + protected String id(InternalContextAdapter context) + { + StringBuilder str = new StringBuilder(100) + .append("block $").append(key); + if (!context.getCurrentTemplateName().equals(getTemplateName())) + { + str.append(" used in ").append(context.getCurrentTemplateName()); + } + return str.toString(); + } + + /** + * actual class placed in the context, holds the context + * being used for the render, as well as the parent (which already holds + * everything else we need). + */ + public static class Reference implements Renderable + { + private InternalContextAdapter context; + private Block parent; + private int depth; + + /** + * @param context + * @param parent + */ + public Reference(InternalContextAdapter context, Block parent) + { + this.context = context; + this.parent = parent; + } + + /** + * Render the AST of this block into the writer using the context. + * @param context + * @param writer + * @return success status + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + { + depth++; + if (depth > parent.maxDepth) + { + /* this is only a debug message, as recursion can + * happen in quasi-innocent situations and is relatively + * harmless due to how we handle it here. + * this is more to help anyone nuts enough to intentionally + * use recursive block definitions and having problems + * pulling it off properly. + */ + parent.log.debug("Max recursion depth reached for {} at {}", parent.id(context), StringUtils.formatFileString(parent)); + depth--; + return false; + } + else + { + parent.render(context, writer); + depth--; + return true; + } + } + + /** + * Makes #if( $blockRef ) true without rendering, so long as we aren't beyond max depth. + * @return reference value as boolean + */ + public boolean getAsBoolean() + { + return depth <= parent.maxDepth; + } + + /** + * @return rendered string + */ + public String toString() + { + Writer writer = new StringBuilderWriter(); + if (render(context, writer)) + { + return writer.toString(); + } + return null; + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/BlockMacro.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/BlockMacro.java new file mode 100644 index 00000000..713dc5ce --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/BlockMacro.java @@ -0,0 +1,125 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.node.Node; + +import java.io.IOException; +import java.io.Writer; + +/** + * BlockMacro directive is used to invoke Velocity macros with normal parameters and a macro body. + * <p> + * The macro can then refer to the passed body AST. This directive can be used as a + * "decorator". Body AST can contain any valid Velocity syntax. + * + * An example: + * <pre> + * #set($foobar = "yeah!") + * + * #macro(strong $txt) + * <strong>$bodyContent</strong> $txt + * #end + * + * #@strong($foobar) + * <u>This text is underlined and bold</u> + * #end + * </pre> + * Will print: + * <pre> + * <strong><u>This text is underlined and bold<u></strong> yeah! + * </pre> + * + * bodyContent reference name is configurable (see velocity.properties). + * + * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a> + * @since 1.7 + * @version $Id$ + */ +public class BlockMacro extends Block +{ + private String name; + private RuntimeMacro macro; + + @Override + public String getName() + { + return key; + } + + /** + * Override to use the macro name, since it is within an + * #@myMacro() ... #end block that the scope in question + * would be used. + */ + @Override + public String getScopeName() + { + return name; + } + + /** + * Initializes the directive. + * + * @param rs + * @param macroName + * @param context + * @param node + * @throws TemplateInitException + */ + public void init(RuntimeServices rs, String macroName, InternalContextAdapter context, Node node) + throws TemplateInitException + { + this.name = macroName; + + super.init(rs, context, node); + + // get name of the reference that refers to AST block passed to block macro call + key = rsvc.getString(RuntimeConstants.VM_BODY_REFERENCE, "bodyContent"); + + // use the macro max depth for bodyContent max depth as well + maxDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH); + + macro = new RuntimeMacro(); + macro.setLocation(getLine(), getColumn(), getTemplate()); + macro.init(rsvc, name, context, node); + } + + /** + * Renders content using the selected macro and the passed AST body. + * + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException + { + return macro.render(context, writer, node, new Reference(context, this)); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Break.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Break.java new file mode 100644 index 00000000..053afd9e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Break.java @@ -0,0 +1,126 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.util.StringUtils; + +import java.io.Writer; +import java.util.ArrayList; + +/** + * Break directive used for interrupting scopes. + * + * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a> + * @author Nathan Bubna + * @version $Id$ + */ +public class Break extends Directive +{ + private boolean scoped = false; + + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "break"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + /** + * Since there is no processing of content, + * there is never a need for an internal scope. + */ + @Override + public boolean isScopeProvided() + { + return false; + } + + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + { + super.init(rs, context, node); + + this.scoped = (node.jjtGetNumChildren() == 1); + } + + /** + * This directive throws a StopCommand which signals either + * the nearest Scope or the specified scope to stop rendering + * its content. + * @return never, always throws a StopCommand or Exception + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + { + if (!scoped) + { + throw new StopCommand(); + } + + Object argument = node.jjtGetChild(0).value(context); + if (argument instanceof Scope) + { + ((Scope)argument).stop(); + throw new IllegalStateException("Scope.stop() failed to throw a StopCommand"); + } + else + { + throw new VelocityException(node.jjtGetChild(0).literal()+ + " is not a valid " + Scope.class.getName() + " instance at " + + StringUtils.formatFileString(this), + null, + rsvc.getLogContext().getStackTrace()); + } + } + + /** + * Called by the parser to validate the argument types + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + if (argtypes.size() > 1) + { + throw new MacroParseException("The #break directive takes only a single, optional Scope argument", + templateName, t); + } + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Define.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Define.java new file mode 100755 index 00000000..f1e90f6a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Define.java @@ -0,0 +1,121 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; +import org.apache.velocity.util.StringUtils; + +import java.io.Writer; +import java.util.ArrayList; + +/** + * Directive that puts an unrendered AST block in the context + * under the specified key, postponing rendering until the + * reference is used and rendered. + * + * @author Andrew Tetlaw + * @author Nathan Bubna + * @version $Id: Define.java 686842 2008-08-18 18:29:31Z nbubna $ + */ +public class Define extends Block +{ + /** + * Return name of this directive. + */ + @Override + public String getName() + { + return "define"; + } + + /** + * simple init - get the key + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + throws TemplateInitException + { + super.init(rs, context, node); + + // the first child is the block name (key), the second child is the block AST body + if ( node.jjtGetNumChildren() != 2 ) + { + throw new VelocityException("parameter missing: block name at " + + StringUtils.formatFileString(this), + null, + rsvc.getLogContext().getStackTrace()); + } + + /* + * first token is the name of the block. We don't even check the format, + * just assume it looks like this: $block_name. Should we check if it has + * a '$' or not? + */ + key = node.jjtGetChild(0).getFirstTokenImage().substring(1); + + /* + * default max depth of two is used because intentional recursion is + * unlikely and discouraged, so make unintentional ones end fast + */ + maxDepth = rsvc.getInt(RuntimeConstants.DEFINE_DIRECTIVE_MAXDEPTH, 2); + } + + /** + * directive.render() simply makes an instance of the Block inner class + * and places it into the context as indicated. + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + { + /* put a Block.Reference instance into the context, + * using the user-defined key, for later inline rendering. + */ + context.put(key, new Reference(context, this)); + return true; + } + + /** + * Called by the parser to validate the argument types + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + if (argtypes.size() != 1) + { + throw new MacroParseException("The #define directive requires one argument", + templateName, t); + } + + if (argtypes.get(0) == ParserTreeConstants.JJTWORD) + { + throw new MacroParseException("The argument to #define is of the wrong type", + templateName, t); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Directive.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Directive.java new file mode 100644 index 00000000..9d938f78 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Directive.java @@ -0,0 +1,264 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.Node; + +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; + +/** + * Base class for all directives used in Velocity. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author Nathan Bubna + * @version $Id$ + */ +public abstract class Directive implements DirectiveConstants, Cloneable +{ + private int line = 0; + private int column = 0; + private boolean provideScope = false; + private Template template; + + protected Logger log = null; + + /** + * + */ + protected RuntimeServices rsvc = null; + + /** + * Return the name of this directive. + * @return The name of this directive. + */ + public abstract String getName(); + + /** + * Get the directive type BLOCK/LINE. + * @return The directive type BLOCK/LINE. + */ + public abstract int getType(); + + /** + * Allows the template location to be set. + * @param line + * @param column + */ + public void setLocation( int line, int column ) + { + this.line = line; + this.column = column; + } + + /** + * Allows the template location to be set. + * @param line + * @param column + * @param template + */ + public void setLocation(int line, int column, Template template) + { + setLocation(line, column); + this.template = template; + } + + /** + * returns the template in which this directive appears + * @return template + */ + public Template getTemplate() + { + return template; + } + + /** + * for log msg purposes + * @return The current line for log msg purposes. + */ + public int getLine() + { + return line; + } + + /** + * for log msg purposes + * @return The current column for log msg purposes. + */ + public int getColumn() + { + return column; + } + + /** + * @return The template file name this directive was defined in, or null if not + * defined in a file. + */ + public String getTemplateName() + { + return template.getName(); + } + + /** + * @return the name to be used when a scope control is provided for this + * directive. + */ + public String getScopeName() + { + return getName(); + } + + /** + * @return true if there will be a scope control injected into the context + * when rendering this directive. + */ + public boolean isScopeProvided() + { + return provideScope; + } + + /** + * How this directive is to be initialized. + * @param rs + * @param context + * @param node + * @throws TemplateInitException + */ + public void init( RuntimeServices rs, InternalContextAdapter context, + Node node) + throws TemplateInitException + { + rsvc = rs; + log = rsvc.getLog("directive." + getName()); + + provideScope = rsvc.isScopeControlEnabled(getScopeName()); + } + + /** + * The Parser calls this method during template parsing to check the arguments + * types. Be aware that this method is called pre init, so not all data + * is available in this method. The default implementation does not peform any + * checking. We do this so that Custom directives do not trigger any parse + * errors in IDEs. + * @param argtypes type, Array of argument types of each argument to the directive + * for example ParserTreeConstants.JJTWORD + * @param t token of directive + * @param templateName the name of the template this directive is referenced in. + * @throws ParseException + */ + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + } + + /** + * How this directive is to be rendered + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + * @throws ResourceNotFoundException + * @throws ParseErrorException + * @throws MethodInvocationException + */ + public abstract boolean render( InternalContextAdapter context, + Writer writer, Node node ) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException; + + + /** + * This creates and places the scope control for this directive + * into the context (if scope provision is turned on). + * @param context + */ + protected void preRender(InternalContextAdapter context) + { + if (isScopeProvided()) + { + String name = getScopeName(); + Object previous = context.get(name); + context.put(name, makeScope(previous)); + } + } + + /** + * @param prev + * @return scope + */ + protected Scope makeScope(Object prev) + { + return new Scope(this, prev); + } + + /** + * This cleans up any scope control for this directive after rendering, + * assuming the scope control was turned on. + * @param context + */ + protected void postRender(InternalContextAdapter context) + { + if (isScopeProvided()) + { + String name = getScopeName(); + Object obj = context.get(name); + + try + { + Scope scope = (Scope)obj; + if (scope.getParent() != null) + { + context.put(name, scope.getParent()); + } + else if (scope.getReplaced() != null) + { + context.put(name, scope.getReplaced()); + } + else + { + context.remove(name); + } + } + catch (ClassCastException cce) + { + // the user can override the scope with a #set, + // since that means they don't care about a replaced value + // and obviously aren't too keen on their scope control, + // and especially since #set is meant to be handled globally, + // we'll assume they know what they're doing and not worry + // about replacing anything superseded by this directive's scope + } + } + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/DirectiveConstants.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/DirectiveConstants.java new file mode 100644 index 00000000..0836e752 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/DirectiveConstants.java @@ -0,0 +1,35 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +/** + * Base class for all directives used in Velocity. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public interface DirectiveConstants +{ + /** Block directive indicator */ + int BLOCK = 1; + + /** Line directive indicator */ + int LINE = 2; +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Evaluate.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Evaluate.java new file mode 100644 index 00000000..89ccdb6b --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Evaluate.java @@ -0,0 +1,240 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.util.introspection.Info; + +import java.io.IOException; +import java.io.StringReader; +import java.io.Writer; + +/** + * Evaluates the directive argument as a VTL string, using the existing + * context. + * + * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a> + * @version $Id$ + * @since 1.6 + */ +public class Evaluate extends Directive +{ + + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "evaluate"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + /** + * Initialize and check arguments. + * @param rs + * @param context + * @param node + * @throws TemplateInitException + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, + Node node) + throws TemplateInitException + { + super.init( rs, context, node ); + + /* + * Check that there is exactly one argument and it is a string or reference. + */ + + int argCount = node.jjtGetNumChildren(); + if (argCount == 0) + { + throw new TemplateInitException( + "#" + getName() + "() requires exactly one argument", + null, + rsvc.getLogContext().getStackTrace(), + context.getCurrentTemplateName(), + node.getColumn(), + node.getLine()); + } + if (argCount > 1) + { + /* + * use line/col of second argument + */ + + throw new TemplateInitException( + "#" + getName() + "() requires exactly one argument", + null, + rsvc.getLogContext().getStackTrace(), + context.getCurrentTemplateName(), + node.jjtGetChild(1).getColumn(), + node.jjtGetChild(1).getLine()); + } + + Node childNode = node.jjtGetChild(0); + if ( childNode.getType() != ParserTreeConstants.JJTSTRINGLITERAL && + childNode.getType() != ParserTreeConstants.JJTREFERENCE ) + { + throw new TemplateInitException( + "#" + getName() + "() argument must be a string literal or reference", + null, + rsvc.getLogContext().getStackTrace(), + context.getCurrentTemplateName(), + childNode.getColumn(), + childNode.getLine()); + } + } + + /** + * Evaluate the argument, convert to a String, and evaluate again + * (with the same context). + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + * @throws ResourceNotFoundException + * @throws ParseErrorException + * @throws MethodInvocationException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, + Node node) throws IOException, ResourceNotFoundException, + ParseErrorException, MethodInvocationException + { + + /* + * Evaluate the string with the current context. We know there is + * exactly one argument and it is a string or reference. + */ + + Object value = node.jjtGetChild(0).value( context ); + String sourceText; + if ( value != null ) + { + sourceText = value.toString(); + } + else + { + sourceText = ""; + } + + /* + * The new string needs to be parsed since the text has been dynamically generated. + */ + String templateName = context.getCurrentTemplateName(); + Template template = (Template)context.getCurrentResource(); + if (template == null) + { + template = new Template(); + template.setName(templateName); + } + SimpleNode nodeTree = null; + + try + { + nodeTree = rsvc.parse(new StringReader(sourceText), template); + } + catch (ParseException | TemplateInitException pex) + { + // use the line/column from the template + Info info = new Info( templateName, node.getLine(), node.getColumn() ); + throw new ParseErrorException( pex.getMessage(), info, rsvc.getLogContext().getStackTrace() ); + } + + /* + * now we want to init and render. Chain the context + * to prevent any changes to the current context. + */ + + if (nodeTree != null) + { + context.pushCurrentTemplateName(templateName); + + try + { + try + { + nodeTree.init(context, rsvc); + } + catch (TemplateInitException pex) + { + Info info = new Info( templateName, node.getLine(), node.getColumn() ); + throw new ParseErrorException( pex.getMessage(), info, rsvc.getLogContext().getStackTrace() ); + } + + try + { + preRender(context); + + /* + * now render, and let any exceptions fly + */ + nodeTree.render(context, writer); + } + catch (StopCommand stop) + { + if (!stop.isFor(this)) + { + throw stop; + } + } + catch (ParseErrorException pex) + { + // convert any parsing errors to the correct line/col + Info info = new Info( templateName, node.getLine(), node.getColumn() ); + throw new ParseErrorException( pex.getMessage(), info, rsvc.getLogContext().getStackTrace() ); + } + } + finally + { + context.popCurrentTemplateName(); + postRender(context); + } + return true; + } + + return false; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Foreach.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Foreach.java new file mode 100644 index 00000000..991e097a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Foreach.java @@ -0,0 +1,360 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.*; +import org.apache.velocity.util.StringUtils; +import org.apache.velocity.util.introspection.Info; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Foreach directive used for moving through arrays, + * or objects that provide an Iterator. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author Daniel Rall + * @version $Id$ + */ +public class Foreach extends Directive +{ + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "foreach"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return BLOCK; + } + + /** + * The maximum number of times we're allowed to loop. + */ + private int maxNbrLoops; + + /** + * Whether or not to throw an Exception if the iterator is null. + */ + private boolean skipInvalidIterator; + + /** + * The reference name used to access each + * of the elements in the list object. It + * is the $item in the following: + * + * #foreach ($item in $list) + * + * This can be used class wide because + * it is immutable. + */ + private String elementKey; + + /** + * immutable, so create in init + */ + protected Info uberInfo; + + /** + * simple init - init the tree and get the elementKey from + * the AST + * @param rs + * @param context + * @param node + * @throws TemplateInitException + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + throws TemplateInitException + { + super.init(rs, context, node); + + maxNbrLoops = rsvc.getInt(RuntimeConstants.MAX_NUMBER_LOOPS, + Integer.MAX_VALUE); + if (maxNbrLoops < 1) + { + maxNbrLoops = Integer.MAX_VALUE; + } + skipInvalidIterator = + rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, true); + + if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false)) + { + // If we are in strict mode then the default for skipInvalidItarator + // is true. However, if the property is explicitly set, then honor the setting. + skipInvalidIterator = rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, false); + } + + /* + * this is really the only thing we can do here as everything + * else is context sensitive + */ + SimpleNode sn = (SimpleNode) node.jjtGetChild(0); + + if (sn instanceof ASTReference) + { + elementKey = ((ASTReference) sn).getRootString(); + } + else + { + /* + * the default, error-prone way which we'll remove + */ + elementKey = sn.getFirstTokenImage().substring(1); + } + + /* + * make an uberinfo - saves new's later on + */ + + uberInfo = new Info(this.getTemplateName(), + getLine(),getColumn()); + } + + /** + * Extension hook to allow subclasses to control whether loop vars + * are set locally or not. So, those in favor of VELOCITY-285, can + * make that happen easily by overriding this and having it use + * context.localPut(k,v). See VELOCITY-630 for more on this. + * @param context + * @param key + * @param value + */ + protected void put(InternalContextAdapter context, String key, Object value) + { + context.put(key, value); + } + + /** + * Retrieve the contextual iterator. + * @param iterable + * @param node + * @return iterator + */ + protected Iterator getIterator(Object iterable, Node node) + { + Iterator i = null; + /* + * do our introspection to see what our collection is + */ + if (iterable != null) + { + try + { + i = rsvc.getUberspect().getIterator(iterable, uberInfo); + } + /* + * pass through application level runtime exceptions + */ + catch (RuntimeException e) + { + throw e; + } + catch (Exception ee) + { + String msg = "Error getting iterator for #foreach parameter " + + node.literal() + " at " + StringUtils.formatFileString(node); + log.error(msg, ee); + throw new VelocityException(msg, ee, rsvc.getLogContext().getStackTrace()); + } + + if (i == null && !skipInvalidIterator) + { + String msg = "#foreach parameter " + node.literal() + " at " + + StringUtils.formatFileString(node) + " is of type " + iterable.getClass().getName() + + " and cannot be iterated by " + rsvc.getUberspect().getClass().getName(); + log.error(msg); + throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); + } + } + return i; + } + + /** + * renders the #foreach() block + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException + { + // Get the block ast tree which is always the last child ... + Node block = node.jjtGetChild(node.jjtGetNumChildren()-1); + + // ... except if there is an #else clause + Node elseBlock = null; + Node previous = node.jjtGetChild(node.jjtGetNumChildren()-2); + if (previous instanceof ASTBlock) + { + elseBlock = block; + block = previous; + } + + Node iterableNode = node.jjtGetChild(2); + Object iterable = iterableNode.value(context); + Iterator i = getIterator(iterable, iterableNode); + if (i == null || !i.hasNext()) + { + if (elseBlock != null) + { + renderBlock(context, writer, elseBlock); + } + return false; + } + + /* + * save the element key if there is one + */ + Object o = context.get(elementKey); + + /* + * roll our own scope class instead of using preRender(ctx)'s + */ + ForeachScope foreach = null; + if (isScopeProvided()) + { + String name = getScopeName(); + foreach = new ForeachScope(this, context.get(name)); + context.put(name, foreach); + } + + int count = 1; + while (count <= maxNbrLoops && i.hasNext()) + { + count++; + + put(context, elementKey, i.next()); + if (isScopeProvided()) + { + // update the scope control + foreach.index++; + foreach.hasNext = i.hasNext(); + } + + try + { + renderBlock(context, writer, block); + } + catch (StopCommand stop) + { + if (stop.isFor(this)) + { + break; + } + else + { + // clean up first + clean(context, o); + throw stop; + } + } + } + clean(context, o); + /* + * closes the iterator if it implements the Closeable interface + */ + if (i instanceof Closeable && i != iterable) /* except if the iterable is the iterator itself */ + { + ((Closeable)i).close(); + } + return true; + } + + protected void renderBlock(InternalContextAdapter context, Writer writer, Node block) + throws IOException + { + block.render(context, writer); + } + + protected void clean(InternalContextAdapter context, Object o) + { + /* + * restores element key if exists + * otherwise just removes + */ + if (o != null) + { + context.put(elementKey, o); + } + else + { + context.remove(elementKey); + } + + // clean up after the ForeachScope + postRender(context); + } + + /** + * We do not allow a word token in any other arg position except for the 2nd since + * we are looking for the pattern #foreach($foo in $bar). + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + if (argtypes.size() < 3) + { + throw new MacroParseException("Too few arguments to the #foreach directive", + templateName, t); + } + else if (argtypes.get(0) != ParserTreeConstants.JJTREFERENCE) + { + throw new MacroParseException("Expected argument 1 of #foreach to be a reference", + templateName, t); + } + else if (argtypes.get(1) != ParserTreeConstants.JJTWORD) + { + throw new MacroParseException("Expected word 'in' at argument position 2 in #foreach", + templateName, t); + } + else if (argtypes.get(2) == ParserTreeConstants.JJTWORD) + { + throw new MacroParseException("Argument 3 of #foreach is of the wrong type", + templateName, t); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/ForeachScope.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/ForeachScope.java new file mode 100755 index 00000000..d1267552 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/ForeachScope.java @@ -0,0 +1,79 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +/** + * This represents scoping and metadata for #foreach, + * adding index, count, hasNext, isFirst and isLast info. + * + * @author Nathan Bubna + * @version $Id$ + */ +public class ForeachScope extends Scope +{ + protected int index = -1; + protected boolean hasNext = false; + + public ForeachScope(Object owner, Object replaces) + { + super(owner, replaces); + } + + public int getIndex() + { + return index; + } + + public int getCount() + { + return index + 1; + } + + public boolean hasNext() + { + return getHasNext(); + } + + public boolean getHasNext() + { + return hasNext; + } + + public boolean isFirst() + { + return index < 1; + } + + public boolean getFirst() + { + return isFirst(); + } + + public boolean isLast() + { + return !hasNext; + } + + public boolean getLast() + { + return isLast(); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Include.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Include.java new file mode 100644 index 00000000..1e1abd7b --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Include.java @@ -0,0 +1,319 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.StringUtils; + +import java.io.IOException; +import java.io.Writer; + +/** + * <p>Pluggable directive that handles the #include() statement in VTL. + * This #include() can take multiple arguments of either + * StringLiteral or Reference.</p> + * + * <p>Notes:</p> + * <ol> + * <li>For security reasons, the included source material can only come + * from somewhere within the template root tree. If you want to include + * content from elsewhere on your disk, add extra template roots, or use + * a link from somwehere under template root to that content.</li> + * + * <li>By default, there is no output to the render stream in the event of + * a problem. You can override this behavior with two property values : + * include.output.errormsg.start + * include.output.errormsg.end + * If both are defined in velocity.properties, they will be used to + * in the render output to bracket the arg string that caused the + * problem. + * Ex. : if you are working in html then + * include.output.errormsg.start=<!-- #include error : + * include.output.errormsg.end= --> + * might be an excellent way to start...</li> + * + * <li>As noted above, #include() can take multiple arguments. + * Ex : #include('foo.vm' 'bar.vm' $foo) + * will include all three if valid to output without any + * special separator.</li> + * </ol> + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:kav@kav.dk">Kasper Nielsen</a> + * @version $Id$ + */ +public class Include extends InputBase +{ + private String outputMsgStart = ""; + private String outputMsgEnd = ""; + + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "include"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + /** + * Since there is no processing of content, + * there is never a need for an internal scope. + */ + @Override + public boolean isScopeProvided() + { + return false; + } + + /** + * simple init - init the tree and get the elementKey from + * the AST + * @param rs + * @param context + * @param node + * @throws TemplateInitException + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, + Node node) + throws TemplateInitException + { + super.init( rs, context, node ); + + /* + * get the msg, and add the space so we don't have to + * do it each time + */ + outputMsgStart = rsvc.getString(RuntimeConstants.ERRORMSG_START); + outputMsgStart = outputMsgStart + " "; + + outputMsgEnd = rsvc.getString(RuntimeConstants.ERRORMSG_END ); + outputMsgEnd = " " + outputMsgEnd; + } + + /** + * iterates through the argument list and renders every + * argument that is appropriate. Any non appropriate + * arguments are logged, but render() continues. + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + * @throws MethodInvocationException + * @throws ResourceNotFoundException + */ + @Override + public boolean render(InternalContextAdapter context, + Writer writer, Node node) + throws IOException, MethodInvocationException, + ResourceNotFoundException + { + /* + * get our arguments and check them + */ + + int argCount = node.jjtGetNumChildren(); + + for( int i = 0; i < argCount; i++) + { + /* + * we only handle StringLiterals and References right now + */ + + Node n = node.jjtGetChild(i); + + if ( n.getType() == ParserTreeConstants.JJTSTRINGLITERAL || + n.getType() == ParserTreeConstants.JJTREFERENCE ) + { + if (!renderOutput( n, context, writer )) + outputErrorToStream( writer, "error with arg " + i + + " please see log."); + } + else + { + String msg = "invalid #include() argument '" + + n.toString() + "' at " + StringUtils.formatFileString(this); + log.error(msg); + outputErrorToStream( writer, "error with arg " + i + + " please see log."); + throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); + } + } + + return true; + } + + /** + * does the actual rendering of the included file + * + * @param node AST argument of type StringLiteral or Reference + * @param context valid context so we can render References + * @param writer output Writer + * @return boolean success or failure. failures are logged + * @exception IOException + * @exception MethodInvocationException + * @exception ResourceNotFoundException + */ + private boolean renderOutput( Node node, InternalContextAdapter context, + Writer writer ) + throws IOException, MethodInvocationException, + ResourceNotFoundException + { + if ( node == null ) + { + log.error("#include() null argument"); + return false; + } + + /* + * does it have a value? If you have a null reference, then no. + */ + Object value = node.value( context ); + if ( value == null) + { + log.error("#include() null argument"); + return false; + } + + /* + * get the path + */ + String sourcearg = value.toString(); + + /* + * check to see if the argument will be changed by the event handler + */ + + String arg = EventHandlerUtil.includeEvent( rsvc, context, sourcearg, context.getCurrentTemplateName(), getName() ); + + /* + * a null return value from the event cartridge indicates we should not + * input a resource. + */ + boolean blockinput = false; + if (arg == null) + blockinput = true; + + Resource resource = null; + + try + { + if (!blockinput) + resource = getResource(arg, getInputEncoding(context)); + } + catch ( ResourceNotFoundException rnfe ) + { + /* + * the arg wasn't found. Note it and throw + */ + log.error("#" + getName() + "(): cannot find resource '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw rnfe; + } + + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + log.error("#" + getName() + "(): arg = '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw e; + } + catch (Exception e) + { + String msg = "#" + getName() + "(): arg = '" + arg + + "', called at " + StringUtils.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + + /* + * note - a blocked input is still a successful operation as this is + * expected behavior. + */ + + if ( blockinput ) + return true; + + else if ( resource == null ) + return false; + + writer.write((String)resource.getData()); + return true; + } + + /** + * Puts a message to the render output stream if ERRORMSG_START / END + * are valid property strings. Mainly used for end-user template + * debugging. + * @param writer + * @param msg + * @throws IOException + * @deprecated if/how errors are displayed is not the concern of the engine, which should throw in all cases + */ + private void outputErrorToStream( Writer writer, String msg ) + throws IOException + { + if ( outputMsgStart != null && outputMsgEnd != null) + { + writer.write(outputMsgStart); + writer.write(msg); + writer.write(outputMsgEnd); + } + } + + /** + * Find the resource to include + * @param path resource path + * @param encoding resource encoding + * @return found resource + * @throws ResourceNotFoundException if resource was not found + */ + protected Resource getResource(String path, String encoding) throws ResourceNotFoundException + { + return rsvc.getContent(path, encoding); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/InputBase.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/InputBase.java new file mode 100644 index 00000000..c12626a4 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/InputBase.java @@ -0,0 +1,59 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.Resource; + +/** + * Base class for directives which do input operations + * (e.g. <code>#include()</code>, <code>#parse()</code>, etc.). + * + * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> + * @since 1.4 + */ +public abstract class InputBase extends Directive +{ + /** + * Decides the encoding used during input processing of this + * directive. + * + * Get the resource, and assume that we use the encoding of the + * current template the 'current resource' can be + * <code>null</code> if we are processing a stream.... + * + * @param context The context to derive the default input encoding + * from. + * @return The encoding to use when processing this directive. + */ + protected String getInputEncoding(InternalContextAdapter context) + { + Resource current = context.getCurrentResource(); + if (current != null) + { + return current.getEncoding(); + } + else + { + return (String) rsvc.getProperty(RuntimeConstants.INPUT_ENCODING); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Macro.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Macro.java new file mode 100644 index 00000000..973c3358 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Macro.java @@ -0,0 +1,294 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +/** + * Macro implements the macro definition directive of VTL. + * + * example: + * + * #macro( isnull $i ) + * #if( $i ) + * $i + * #end + * #end + * + * This object is used at parse time to mainly process and register the + * macro. It is used inline in the parser when processing a directive. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a> + * @version $Id$ + */ +public class Macro extends Directive +{ + private static boolean debugMode = false; + + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "macro"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return BLOCK; + } + + /** + * Since this class does no processing of content, + * there is never a need for an internal scope. + */ + @Override + public boolean isScopeProvided() + { + return false; + } + + /** + * render() doesn't do anything in the final output rendering. + * There is no output from a #macro() directive. + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + */ + @Override + public boolean render(InternalContextAdapter context, + Writer writer, Node node) + throws IOException + { + /* + * do nothing: We never render. The VelocimacroProxy object does that + */ + + return true; + } + + /** + * @see org.apache.velocity.runtime.directive.Directive#init(org.apache.velocity.runtime.RuntimeServices, org.apache.velocity.context.InternalContextAdapter, org.apache.velocity.runtime.parser.node.Node) + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, + Node node) + throws TemplateInitException + { + super.init(rs, context, node); + + + // Add this macro to the VelocimacroManager now that it has been initialized. + List<MacroArg> macroArgs = getArgArray(node, rsvc); + int numArgs = node.jjtGetNumChildren(); + rsvc.addVelocimacro(macroArgs.get(0).name, node.jjtGetChild(numArgs - 1), + macroArgs, node.getTemplate()); + } + + /** + * Check the argument types of a macro call, called by the parser to do validation + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + if (argtypes.size() < 1) + { + throw new MacroParseException("A macro definition requires at least a macro name" + , templateName, t); + } + + /* + * lets make sure that the first arg is an ASTWord + */ + if(argtypes.get(0) != ParserTreeConstants.JJTWORD) + { + throw new MacroParseException("Macro argument 1" + + " must be a token without surrounding \' or \"" + , templateName, t); + } + + + // We use this to flag if the default arguments are out of order. such as + // #macro($a $b=1 $c). We enforce that all default parameters must be + // specified consecutively, and at the end of the argument list. + boolean consecutive = false; + + // All arguments other then the first must be either a reference + // or a directiveassign followed by a reference in the case a default + // value is specified. + for (int argPos = 1; argPos < argtypes.size(); argPos++) + { + if (argtypes.get(argPos) == ParserTreeConstants.JJTDIRECTIVEASSIGN) + { + // Absorb next argument type since parser enforces that these are in + // pairs, and we don't need to check the type of the second + // arg because it is done by the parser. + argPos++; + consecutive = true; + } + else if (argtypes.get(argPos) != ParserTreeConstants.JJTREFERENCE) + { + throw new MacroParseException("Macro argument " + (argPos + 1) + + " must be a reference", templateName, t); + } + else if (consecutive) + { + // We have already found a default parameter e.g.; $x = 2, but + // the next parameter was not a reference. + throw new MacroParseException("Macro non-default argument follows a default argument at " + , templateName, t); + } + } + } + + /** + * Creates an array containing the literal text from the macro + * argument(s) (including the macro's name as the first arg). + * + * @param node The parse node from which to grok the argument + * list. It's expected to include the block node tree (for the + * macro body). + * @param rsvc For debugging purposes only. + * @return array of arguments + */ + private static List<MacroArg> getArgArray(Node node, RuntimeServices rsvc) + { + /* + * Get the number of arguments for the macro, excluding the + * last child node which is the block tree containing the + * macro body. + */ + int numArgs = node.jjtGetNumChildren(); + numArgs--; // avoid the block tree... + + ArrayList<MacroArg> macroArgs = new ArrayList<>(); + + for (int i = 0; i < numArgs; i++) + { + Node curnode = node.jjtGetChild(i); + MacroArg macroArg = new MacroArg(); + if (curnode.getType() == ParserTreeConstants.JJTDIRECTIVEASSIGN) + { + // This is an argument with a default value + macroArg.name = curnode.getFirstTokenImage(); + + // Inforced by the parser there will be an argument here. + i++; + curnode = node.jjtGetChild(i); + macroArg.defaultVal = curnode; + } + else + { + // An argument without a default value + macroArg.name = curnode.getFirstTokenImage(); + } + + // trim off the leading $ for the args after the macro name. + // saves everyone else from having to do it + if (i > 0 && macroArg.name.startsWith(String.valueOf(rsvc.getParserConfiguration().getDollarChar()))) + { + macroArg.name = macroArg.name.substring(1); + } + + macroArgs.add(macroArg); + } + + if (debugMode) + { + StringBuilder msg = new StringBuilder("Macro.getArgArray(): nbrArgs="); + msg.append(numArgs).append(": "); + macroToString(msg, macroArgs, rsvc); + rsvc.getLog("macro").debug(msg.toString()); + } + + return macroArgs; + } + + /** + * MacroArgs holds the information for a single argument in a + * macro definition. The arguments for a macro are passed around as a + * list of these objects. + */ + public static class MacroArg + { + /** + * Name of the argument with '$' stripped off + */ + public String name = null; + + /** + * If the argument was given a default value, then this contains + * the base of the AST tree of the value. Otherwise it is null. + */ + public Node defaultVal = null; + } + + /** + * For debugging purposes. Formats the arguments from + * <code>argArray</code> and appends them to <code>buf</code>. + * + * @param buf A StringBuilder. If null, a new StringBuilder is allocated. + * @param macroArgs Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + * @return A StringBuilder containing the formatted arguments. If a StringBuilder + * has passed in as buf, this method returns it. + * @since 1.5 + */ + public static StringBuilder macroToString(final StringBuilder buf, List<MacroArg> macroArgs, RuntimeServices rsvc) + { + StringBuilder ret = (buf == null) ? new StringBuilder() : buf; + + ret.append(rsvc.getParserConfiguration().getHashChar()).append(macroArgs.get(0).name).append("( "); + for (MacroArg marg : macroArgs) + { + ret.append(rsvc.getParserConfiguration().getDollarChar()).append(marg.name); + if (marg.defaultVal != null) + { + ret.append("=").append(marg.defaultVal); + } + ret.append(' '); + } + ret.append(" )"); + return ret; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/MacroParseException.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/MacroParseException.java new file mode 100644 index 00000000..0ce9a5f8 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/MacroParseException.java @@ -0,0 +1,206 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.exception.ExtendedParseException; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.StringUtils; + +/** + * Exception to indicate problem happened while constructing #macro() + * + * For internal use in parser - not to be passed to app level + * + * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> + * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a> + * @version $Id$ + */ +public class MacroParseException + extends ParseException + implements ExtendedParseException +{ + private final String templateName; + + /** + * Version Id for serializable + */ + private static final long serialVersionUID = -4985224672336070689L; + + /** + * @param msg + * @param templateName + * @param currentToken + */ + public MacroParseException(final String msg, final String templateName, final Token currentToken) + { + super(msg + " at "); + this.currentToken = currentToken; + this.templateName = templateName; + } + + /** + * returns the Template name where this exception occured. + * @return The Template name where this exception occured. + * @since 1.5 + */ + @Override + public String getTemplateName() + { + return templateName; + } + + /** + * returns the line number where this exception occured. + * @return The line number where this exception occured. + * @since 1.5 + */ + @Override + public int getLineNumber() + { + if ((currentToken != null) && (currentToken.next != null)) + { + return currentToken.next.beginLine; + } + else if (currentToken != null) + { + return currentToken.beginLine; + } + else + { + return -1; + } + } + + /** + * returns the column number where this exception occured. + * @return The column number where this exception occured. + * @since 1.5 + */ + @Override + public int getColumnNumber() + { + if ((currentToken != null) && (currentToken.next != null)) + { + return currentToken.next.beginColumn; + } + else if (currentToken != null) + { + return currentToken.beginColumn; + } + else + { + return -1; + } + } + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + * @return the current message. + * @since 1.5 + */ + @Override + public String getMessage() + { + if (!specialConstructor) + { + StringBuilder sb = new StringBuilder(super.getMessage()); + appendTemplateInfo(sb); + return sb.toString(); + } + + int maxSize = 0; + + StringBuilder expected = new StringBuilder(); + + for (int[] expectedTokenSequence : expectedTokenSequences) + { + if (maxSize < expectedTokenSequence.length) + { + maxSize = expectedTokenSequence.length; + } + + for (int i : expectedTokenSequence) + { + expected.append(tokenImage[i]).append(" "); + } + + if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) + { + expected.append("..."); + } + + expected.append(eol).append(" "); + } + + StringBuilder retval = new StringBuilder("Encountered \""); + Token tok = currentToken.next; + + for (int i = 0; i < maxSize; i++) + { + if (i != 0) + { + retval.append(" "); + } + + if (tok.kind == 0) + { + retval.append(tokenImage[0]); + break; + } + + retval.append(add_escapes(tok.image)); + tok = tok.next; + } + + retval.append("\""); + appendTemplateInfo(retval); + + if (expectedTokenSequences.length == 1) + { + retval.append("Was expecting:").append(eol).append(" "); + } + else + { + retval.append("Was expecting one of:").append(eol).append(" "); + } + + // avoid JDK 1.3 StringBuffer.append(Object o) vs 1.4 StringBuffer.append(StringBuffer sb) gotcha. + retval.append(expected.toString()); + return retval.toString(); + } + + /** + * @param sb + * @since 1.5 + */ + protected void appendTemplateInfo(final StringBuilder sb) + { + sb.append(StringUtils.formatFileString(getTemplateName(), getLineNumber(), getColumnNumber())); + sb.append(eol); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Parse.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Parse.java new file mode 100644 index 00000000..06dbf13b --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Parse.java @@ -0,0 +1,358 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.util.StringUtils; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; + +/** + * Pluggable directive that handles the <code>#parse()</code> + * statement in VTL. + * + * <pre> + * Notes: + * ----- + * 1) The parsed source material can only come from somewhere in + * the TemplateRoot tree for security reasons. There is no way + * around this. If you want to include content from elsewhere on + * your disk, use a link from somewhere under Template Root to that + * content. + * + * 2) There is a limited parse depth. It is set as a property + * "directive.parse.max_depth = 10" by default. This 10 deep + * limit is a safety feature to prevent infinite loops. + * </pre> + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a> + * @version $Id$ + */ +public class Parse extends InputBase +{ + private int maxDepth; + + /** + * Indicates if we are running in strict reference mode. + */ + public boolean strictRef = false; + + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "parse"; + } + + /** + * Overrides the default to use "template", so that all templates + * can use the same scope reference, whether rendered via #parse + * or direct merge. + */ + @Override + public String getScopeName() + { + return "template"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + /** + * Init's the #parse directive. + * @param rs + * @param context + * @param node + * @throws TemplateInitException + */ + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + throws TemplateInitException + { + super.init(rs, context, node); + + this.maxDepth = rsvc.getInt(RuntimeConstants.PARSE_DIRECTIVE_MAXDEPTH, 10); + + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + } + + /** + * iterates through the argument list and renders every + * argument that is appropriate. Any non appropriate + * arguments are logged, but render() continues. + * @param context + * @param writer + * @param node + * @return True if the directive rendered successfully. + * @throws IOException + * @throws ResourceNotFoundException + * @throws ParseErrorException + * @throws MethodInvocationException + */ + @Override + public boolean render(InternalContextAdapter context, + Writer writer, Node node) + throws IOException, ResourceNotFoundException, ParseErrorException, + MethodInvocationException + { + /* + * did we get an argument? + */ + if ( node.jjtGetNumChildren() == 0 ) + { + throw new VelocityException("#" + getName() + "(): argument missing at " + + StringUtils.formatFileString(this), null, rsvc.getLogContext().getStackTrace()); + } + + /* + * does it have a value? If you have a null reference, then no. + */ + Object value = node.jjtGetChild(0).value( context ); + if (value == null) + { + log.debug("#" + getName() + "(): null argument at {}", StringUtils.formatFileString(this)); + } + + /* + * get the path + */ + String sourcearg = value == null ? null : value.toString(); + + /* + * check to see if the argument will be changed by the event cartridge + */ + String arg = EventHandlerUtil.includeEvent( rsvc, context, sourcearg, context.getCurrentTemplateName(), getName()); + + /* + * if strict mode and arg was not fixed by event handler, then complain + */ + if (strictRef && value == null && arg == null) + { + throw new VelocityException("The argument to #" + getName() + " returned null at " + + StringUtils.formatFileString(this), null, rsvc.getLogContext().getStackTrace()); + } + + /* + * a null return value from the event cartridge indicates we should not + * input a resource. + */ + if (arg == null) + { + // abort early, but still consider it a successful rendering + return true; + } + + + if (maxDepth > 0) + { + /* + * see if we have exceeded the configured depth. + */ + String[] templateStack = context.getTemplateNameStack(); + if (templateStack.length >= maxDepth) + { + StringBuilder path = new StringBuilder(); + for (String aTemplateStack : templateStack) + { + path.append(" > ").append(aTemplateStack); + } + log.error("Max recursion depth reached ({}). File stack: {}", + templateStack.length, path); + + return false; + } + } + + /* + * now use the Runtime resource loader to get the template + */ + + Template t = null; + + try + { + t = getTemplate(arg, getInputEncoding(context)); + } + catch ( ResourceNotFoundException rnfe ) + { + /* + * the arg wasn't found. Note it and throw + */ + log.error("#" + getName() + "(): cannot find template '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw rnfe; + } + catch ( ParseErrorException pee ) + { + /* + * the arg was found, but didn't parse - syntax error + * note it and throw + */ + log.error("#" + getName() + "(): syntax error in #" + getName() + "()-ed template '{}', called at {}", + arg, StringUtils.formatFileString(this)); + throw pee; + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + log.error("Exception rendering #" + getName() + "({}) at {}", + arg, StringUtils.formatFileString(this)); + throw e; + } + catch ( Exception e ) + { + String msg = "Exception rendering #" + getName() + "(" + arg + ") at " + + StringUtils.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + /* + * Add the template name to the macro libraries list + */ + List<Template> macroLibraries = context.getMacroLibraries(); + + /* + * if macroLibraries are not set create a new one + */ + if (macroLibraries == null) + { + macroLibraries = new ArrayList<>(); + } + + context.setMacroLibraries(macroLibraries); + + /* instead of adding the name of the template, add the Template reference */ + macroLibraries.add(t); + + /* + * and render it + */ + try + { + preRender(context); + context.pushCurrentTemplateName(arg); + + ((SimpleNode) t.getData()).render(context, writer); + } + catch( StopCommand stop ) + { + if (!stop.isFor(this)) + { + throw stop; + } + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + /* + * Log #parse errors so the user can track which file called which. + */ + log.error("Exception rendering #" + getName() + "({}) at {}", + arg, StringUtils.formatFileString(this)); + throw e; + } + catch ( Exception e ) + { + String msg = "Exception rendering #" + getName() + "(" + arg + ") at " + + StringUtils.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + finally + { + context.popCurrentTemplateName(); + postRender(context); + } + + /* + * note - a blocked input is still a successful operation as this is + * expected behavior. + */ + + return true; + } + + /** + * Called by the parser to validate the argument types + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + if (argtypes.size() != 1) + { + throw new MacroParseException("The #" + getName() + " directive requires one argument", + templateName, t); + } + + if (argtypes.get(0) == ParserTreeConstants.JJTWORD) + { + throw new MacroParseException("The argument to #" + getName() + " is of the wrong type", + templateName, t); + } + } + + /** + * Find the template to render in the appropriate encoding + * @param path template path + * @param encoding template encoding + * @return found template + * @throws ResourceNotFoundException if template was not found + * @since 2.4 + */ + protected Template getTemplate(String path, String encoding) throws ResourceNotFoundException + { + return rsvc.getTemplate(path, encoding); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/RuntimeMacro.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/RuntimeMacro.java new file mode 100644 index 00000000..95ddb165 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/RuntimeMacro.java @@ -0,0 +1,371 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.Renderable; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.ASTDirective; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; +import org.apache.velocity.util.StringUtils; + +import org.apache.commons.lang3.Validate; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +/** + * This class acts as a proxy for potential macros. When the AST is built + * this class is inserted as a placeholder for the macro (whether or not + * the macro is actually defined). At render time we check whether there is + * a implementation for the macro call. If an implementation cannot be + * found the literal text is rendered. + * @since 1.6 + */ +public class RuntimeMacro extends Directive +{ + /** + * Name of the macro + */ + private String macroName; + + /** + * Literal text of the macro + */ + private String literal = null; + + /** + * Node of the macro call + */ + private Node node = null; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + /** + * badArgsErrorMsg will be non null if the arguments to this macro + * are deamed bad at init time, see the init method. If his is non null, then this macro + * cannot be rendered, and if there is an attempt to render we throw an exception + * with this as the message. + */ + private String badArgsErrorMsg = null; + + /** + * Return name of this Velocimacro. + * + * @return The name of this Velocimacro. + */ + @Override + public String getName() + { + return macroName; + } + + /** + * Override to always return "macro". We don't want to use + * the macro name here, since when writing VTL that uses the + * scope, we are within a #macro call. The macro name will instead + * be used as the scope name when defining the body of a BlockMacro. + */ + @Override + public String getScopeName() + { + return "macro"; + } + + /** + * Velocimacros are always LINE + * type directives. + * + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + + /** + * Initialize the Runtime macro. At the init time no implementation so we + * just save the values to use at the render time. + * + * @param rs runtime services + * @param name macro name + * @param context InternalContextAdapter + * @param node node containing the macro call + */ + public void init(RuntimeServices rs, String name, InternalContextAdapter context, + Node node) + { + super.init(rs, context, node); + + macroName = Validate.notNull(name); + macroName = rsvc.useStringInterning() ? macroName.intern() : macroName; + this.node = node; + + /* + * Apply strictRef setting only if this really looks like a macro, + * so strict mode doesn't balk at things like #E0E0E0 in a template. + * compare with ")" is a simple #foo() style macro, comparing to + * "#end" is a block style macro. We use starts with because the token + * may end with '\n' + */ + // Tokens can be used here since we are in init() and Tokens have not been dropped yet + Token t = node.getLastToken(); + if (t.image.startsWith(")") || t.image.startsWith(rsvc.getParserConfiguration().getHashChar() + "end")) + { + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + } + + // Validate that none of the arguments are plain words, (VELOCITY-614) + // they should be string literals, references, inline maps, or inline lists + for (int n=0; n < node.jjtGetNumChildren(); n++) + { + Node child = node.jjtGetChild(n); + if (child.getType() == ParserTreeConstants.JJTWORD) + { + badArgsErrorMsg = "Invalid arg '" + child.getFirstTokenImage() + + "' in macro #" + macroName + " at " + StringUtils.formatFileString(child); + + if (strictRef) // If strict, throw now + { + /* indicate col/line assuming it starts at 0 + * this will be corrected one call up */ + throw new TemplateInitException(badArgsErrorMsg, + null, + rsvc.getLogContext().getStackTrace(), + context.getCurrentTemplateName(), 0, 0); + } + } + } + // TODO: Improve this + // this is only needed if the macro does not exist during runtime + // since tokens are eliminated after this init call, we have to create a cached version of the + // literal which is in 99.9% cases waste. However, for regular macro calls (non Block macros) + // this doesn't create very long Strings so it's probably acceptable + getLiteral(); + } + + /** + * It is probably quite rare that we need to render the macro literal + * but since we won't keep the tokens in memory, we need to calculate it + * at parsing time. + */ + private String getLiteral() + { + SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling(); + ASTDirective directive = (ASTDirective)node; + + String morePrefix = directive.getMorePrefix(); + + if (literal == null) + { + StringBuilder buffer = new StringBuilder(); + Token t = node.getFirstToken(); + + /* avoid outputting twice the prefix and the 'MORE' prefix, + * but still display the prefix in the cases where the ASTDirective would hide it */ + int pos = -1; + while (t != null && t != node.getLastToken()) + { + if (pos == -1) pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar()); + if (pos != -1) + { + buffer.append(t.image.substring(pos)); + pos = 0; + } + else if (morePrefix.length() == 0 && spaceGobbling.compareTo(SpaceGobbling.LINES) >= 0) + { + buffer.append(t.image); + } + t = t.next; + } + + if (t != null) + { + if (pos == -1) pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar()); + if (pos != -1) + { + buffer.append(t.image.substring(pos)); + } + } + + literal = buffer.toString(); + /* avoid outputting twice the postfix, but still display it in the cases + * where the ASTDirective would hide it */ + String postfix = directive.getPostfix(); + if ((morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE) && literal.endsWith(postfix)) + { + literal = literal.substring(0, literal.length() - postfix.length()); + } + } + return literal; + } + + + /** + * Velocimacro implementation is not known at the init time. So look for + * a implementation in the macro libraries and if finds one renders it. The + * actual rendering is delegated to the VelocimacroProxy object. When + * looking for a macro we first loot at the template with has the + * macro call then we look at the macro libraries in the order they appear + * in the list. If a macro has many definitions above look up will + * determine the precedence. + * + * @param context + * @param writer + * @param node + * @return true if the rendering is successful + * @throws IOException + * @throws ResourceNotFoundException + * @throws ParseErrorException + * @throws MethodInvocationException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, + Node node) + throws IOException, ResourceNotFoundException, + ParseErrorException, MethodInvocationException + { + return render(context, writer, node, null); + } + + /** + * This method is used with BlockMacro when we want to render a macro with a body AST. + * + * @param context + * @param writer + * @param node + * @param body AST block that was enclosed in the macro body. + * @return true if the rendering is successful + * @throws IOException + * @throws ResourceNotFoundException + * @throws ParseErrorException + * @throws MethodInvocationException + */ + public boolean render(InternalContextAdapter context, Writer writer, + Node node, Renderable body) + throws IOException, ResourceNotFoundException, + ParseErrorException, MethodInvocationException + { + VelocimacroProxy vmProxy = null; + Template renderingTemplate = (Template)context.getCurrentResource(); + + /* + * first look in the source template + */ + Object o = rsvc.getVelocimacro(macroName, renderingTemplate, getTemplate()); + + if( o != null ) + { + // getVelocimacro can only return a VelocimacroProxy so we don't need the + // costly instanceof check + vmProxy = (VelocimacroProxy)o; + } + + /* + * if not found, look in the macro libraries. + */ + if (vmProxy == null) + { + List<Template> macroLibraries = context.getMacroLibraries(); + if (macroLibraries != null) + { + for (int i = macroLibraries.size() - 1; i >= 0; i--) + { + o = rsvc.getVelocimacro(macroName, renderingTemplate, macroLibraries.get(i)); + + // get the first matching macro + if (o != null) + { + vmProxy = (VelocimacroProxy) o; + break; + } + } + } + } + + if (vmProxy != null) + { + if (badArgsErrorMsg != null) + { + throw new TemplateInitException(badArgsErrorMsg, + null, rsvc.getLogContext().getStackTrace(), + context.getCurrentTemplateName(), node.getColumn(), node.getLine()); + } + + try + { + preRender(context); + return vmProxy.render(context, writer, node, body); + } + catch (StopCommand stop) + { + if (!stop.isFor(this)) + { + throw stop; + } + return true; + } + catch (RuntimeException | IOException e) + { + /* + * We catch, the exception here so that we can record in + * the logs the template and line number of the macro call + * which generate the exception. This information is + * especially important for multiple macro call levels. + * this is also true for the following catch blocks. + */ + log.error("Exception in macro #{} called at {}", + macroName, StringUtils.formatFileString(node)); + throw e; + } + finally + { + postRender(context); + } + } + else if (strictRef) + { + throw new VelocityException("Macro '#" + macroName + "' is not defined at " + + StringUtils.formatFileString(node), null, rsvc.getLogContext().getStackTrace()); + } + + /* + * If we cannot find an implementation write the literal text + */ + writer.write(getLiteral()); + return true; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Scope.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Scope.java new file mode 100755 index 00000000..7e7d226e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Scope.java @@ -0,0 +1,359 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.Template; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This handles context scoping and metadata for directives. + * + * @author Nathan Bubna + * @version $Id$ + */ +public class Scope extends AbstractMap<Object, Object> +{ + private static final String setReturnValue = ""; + private Map<Object, Object> storage; + private Object replaced; + private Scope parent; + private Info info; + protected final Object owner; + + /** + * @param owner + * @param previous + */ + public Scope(Object owner, Object previous) + { + this.owner = owner; + if (previous != null) + { + try + { + this.parent = (Scope)previous; + } + catch (ClassCastException cce) + { + this.replaced = previous; + } + } + } + + private Map<Object, Object> getStorage() + { + if (storage == null) + { + storage = new HashMap<>(); + } + return storage; + } + + /** + * @return entry set + */ + @Override + public Set<Map.Entry<Object, Object>> entrySet() + { + return getStorage().entrySet(); + } + + /** + * getter + * @param key + * @return found value + */ + @Override + public Object get(Object key) + { + Object o = super.get(key); + if (o == null && parent != null && !containsKey(key)) + { + return parent.get(key); + } + return o; + } + + /** + * setter + * @param key + * @param value + * @return previous value + */ + @Override + public Object put(Object key, Object value) + { + return getStorage().put(key, value); + } + + /** + * Convenience method to call put(key,val) in a template + * without worrying about what is returned/rendered by the call. + * This should ALWAYS return an empty string. + * @param key + * @param value + * @return empty string + */ + public String set(Object key, Object value) + { + put(key, value); + return setReturnValue; + } + + /** + * Allows #stop to easily trigger the proper StopCommand for this scope. + */ + protected void stop() + { + throw new StopCommand(owner); + } + + /** + * Returns the number of control arguments of this type + * that are stacked up. This is the distance between this + * instance and the topmost instance, plus one. This value + * will never be negative or zero. + * @return depth + */ + protected int getDepth() + { + if (parent == null) + { + return 1; + } + return parent.getDepth() + 1; + } + + /** + * Returns the topmost parent control reference, retrieved + * by simple recursion on {@link #getParent}. + * @return top-most scope + */ + public Scope getTopmost() + { + if (parent == null) + { + return this; + } + return parent.getTopmost(); + } + + /** + * Returns the parent control reference overridden by the placement + * of this instance in the context. + * @return parent scope + */ + public Scope getParent() + { + return parent; + } + + /** + * Returns the user's context reference overridden by the placement + * of this instance in the context. If there was none (as is hoped), + * then this will return null. This never returns parent controls; + * those are returned by {@link #getParent}. + * @return replaced reference value, or null + */ + public Object getReplaced() + { + if (replaced == null && parent != null) + { + return parent.getReplaced(); + } + return replaced; + } + + /** + * Returns info about the current scope for debugging purposes. + * @return template debugging infos + */ + public Info getInfo() + { + if (info == null) + { + info = new Info(this, owner); + } + return info; + } + + /** + * Class to encapsulate and provide access to info about + * the current scope for debugging. + */ + public static class Info + { + private Scope scope; + private Directive directive; + private Template template; + + /** + * c'tor + * @param scope + * @param owner + */ + public Info(Scope scope, Object owner) + { + if (owner instanceof Directive) + { + directive = (Directive)owner; + } + if (owner instanceof Template) + { + template = (Template)owner; + } + this.scope = scope; + } + + /** + * name getter + * @return name + */ + public String getName() + { + if (directive != null) + { + return directive.getName(); + } + if (template != null) + { + return template.getName(); + } + return null; + } + + /** + * type getter + * @return scope type + */ + public String getType() + { + if (directive != null) + { + switch (directive.getType()) + { + case Directive.BLOCK: + return "block"; + case Directive.LINE: + return "line"; + } + } + if (template != null) + { + return template.getEncoding(); + } + return null; + } + + /** + * current depth + * @return depth + */ + public int getDepth() + { + return scope.getDepth(); + } + + /** + * template name getter + * @return template name + */ + public String getTemplate() + { + if (directive != null) + { + return directive.getTemplateName(); + } + if (template != null) + { + return template.getName(); + } + return null; + } + + /** + * line getter + * @return line number + */ + public int getLine() + { + if (directive != null) + { + return directive.getLine(); + } + return 0; + } + + /** + * column getter + * @return column number + */ + public int getColumn() + { + if (directive != null) + { + return directive.getColumn(); + } + return 0; + } + + /** + * string representation getter + * @return string representation + */ + public String toString() + { + StringBuilder sb = new StringBuilder(); + if (directive != null) + { + sb.append('#'); // parser characters substitution is not handled here + } + sb.append(getName()); + sb.append("[type:").append(getType()); + int depth = getDepth(); + if (depth > 1) + { + sb.append(" depth:").append(depth); + } + if (template == null) + { + String vtl = getTemplate(); + sb.append(" template:"); + if (!vtl.contains(" ")) + { + sb.append(vtl); + } + else + { + sb.append('"').append(vtl).append('"'); + } + sb.append(" line:").append(getLine()); + sb.append(" column:").append(getColumn()); + } + sb.append(']'); + return sb.toString(); + } + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Stop.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Stop.java new file mode 100644 index 00000000..c2caf388 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Stop.java @@ -0,0 +1,115 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.Node; + +import java.io.Writer; +import java.util.ArrayList; + +/** + * This class implements the #stop directive which allows + * a user to stop the merging and rendering process. The #stop directive + * will accept a single message argument with info about the reason for + * stopping. + */ +public class Stop extends Directive +{ + private static final StopCommand STOP_ALL = new StopCommand("StopCommand to exit merging") { + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } + }; + + private boolean hasMessage = false; + + /** + * Return name of this directive. + * @return The name of this directive. + */ + @Override + public String getName() + { + return "stop"; + } + + /** + * Return type of this directive. + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + /** + * Since there is no processing of content, + * there is never a need for an internal scope. + */ + @Override + public boolean isScopeProvided() + { + return false; + } + + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + { + super.init(rs, context, node); + + hasMessage = (node.jjtGetNumChildren() == 1); + } + + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + { + if (!hasMessage) + { + throw STOP_ALL; + } + + Object argument = node.jjtGetChild(0).value(context); + + // stop all and use specified message + throw new StopCommand(String.valueOf(argument)); + } + + /** + * Called by the parser to check the argument types + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) + throws ParseException + { + int kids = argtypes.size(); + if (kids > 1) + { + throw new MacroParseException("The #stop directive only accepts a single message parameter", + templateName, t); + } + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/StopCommand.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/StopCommand.java new file mode 100755 index 00000000..37768a57 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/StopCommand.java @@ -0,0 +1,89 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.runtime.RuntimeInstance; + +/** + * Stop command for directive Control objects. In an ideal JDK, + * this would be able to extend a RuntimeThrowable class, but we + * don't have that. So to avoid the interface changes needed by + * extending Throwable and the potential errant catches were we + * to extend RuntimeException, we'll have to extend Error, + * despite the fact that this is never an error. + * + * @author Nathan Bubna + * @version $Id$ + */ +public class StopCommand extends Error +{ + private static final long serialVersionUID = 2577683435802825964L; + private Object stopMe; + private boolean nearest = false; + + public StopCommand() + { + this.nearest = true; + } + + public StopCommand(String message) + { + super(message); + } + + public StopCommand(Object stopMe) + { + this.stopMe = stopMe; + } + + @Override + public String getMessage() + { + if (stopMe != null) + { + // only create a useful message if requested (which is unlikely) + return "StopCommand: "+stopMe; + } + else + { + return "StopCommand: "+super.getMessage(); + } + } + + public boolean isFor(Object that) + { + if (nearest) // if we're stopping at the first chance + { + // save that for message + stopMe = that; + return true; + } + else if (stopMe != null) // if we have a specified stopping point + { + return (that == stopMe); + } + else // only stop for the top :) + { + return (that instanceof Template || + that instanceof RuntimeInstance); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java new file mode 100644 index 00000000..d3b8ff5c --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java @@ -0,0 +1,459 @@ +package org.apache.velocity.runtime.directive; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MacroOverflowException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.Renderable; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.directive.Macro.MacroArg; +import org.apache.velocity.runtime.parser.node.ASTReference; +import org.apache.velocity.runtime.parser.node.ASTStringLiteral; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.util.StringUtils; + +import java.io.IOException; +import java.io.Writer; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +/** + * VelocimacroProxy.java + * + * a proxy Directive-derived object to fit with the current directive system + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class VelocimacroProxy extends Directive +{ + private String macroName; + private List<MacroArg> macroArgs = null; + private String[] literalArgArray = null; + private SimpleNode nodeTree = null; + private int numMacroArgs = 0; + private boolean strictArguments; + private int maxCallDepth; + private String bodyReference; + private boolean enableBCmode; + + private static final Object NULL_VALUE_MARKER = new Object(); + + /** + * Return name of this Velocimacro. + * @return The name of this Velocimacro. + */ + @Override + public String getName() + { + return macroName; + } + + /** + * Velocimacros are always LINE type directives. + * @return The type of this directive. + */ + @Override + public int getType() + { + return LINE; + } + + /** + * sets the directive name of this VM + * + * @param name + */ + public void setName(String name) + { + macroName = name; + } + + /** + * sets the array of arguments specified in the macro definition + * @param args Array of macro arguments, containing the + * #macro() arguments and default values. the 0th is the name. + */ + public void setMacroArgs(List<Macro.MacroArg> args) + { + macroArgs = args; + + // for performance reasons we precache these strings - they are needed in + // "render literal if null" functionality + if (enableBCmode) + { + literalArgArray = new String[macroArgs.size()]; + for (int i = 0; i < macroArgs.size(); i++) + { + literalArgArray[i] = ".literal.$" + macroArgs.get(i).name; + } + } + + /* + * get the arg count from the arg array. remember that the arg array has the macro name as + * it's 0th element + */ + + numMacroArgs = macroArgs.size() - 1; + } + + /** + * Return the list of macro arguments associated with this macro + * @return macro arguments + */ + public List<Macro.MacroArg> getMacroArgs() + { + return macroArgs; + } + + /** + * @param tree + */ + public void setNodeTree(SimpleNode tree) + { + nodeTree = tree; + } + + /** + * returns the number of ars needed for this VM + * @return The number of ars needed for this VM + */ + public int getNumArgs() + { + return numMacroArgs; + } + + /** + * Initialize members of VelocimacroProxy. called from MacroEntry + * @param rs runtime services + */ + public void init(RuntimeServices rs) + { + rsvc = rs; + log = rs.getLog("macro"); + + strictArguments = rsvc.getBoolean( + RuntimeConstants.VM_ARGUMENTS_STRICT, false); + + // get the macro call depth limit + maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH); + + // get name of the reference that refers to AST block passed to block macro call + bodyReference = rsvc.getString(RuntimeConstants.VM_BODY_REFERENCE, "bodyContent"); + + enableBCmode = rsvc.getBoolean(RuntimeConstants.VM_ENABLE_BC_MODE, false); + } + + /** + * Render the macro AST node + * @param context + * @param writer + * @param node + * @return success status + * @throws IOException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException + { + return render(context, writer, node, null); + } + + /** + * Renders the macro using the context. + * + * @param context Current rendering context + * @param writer Writer for output + * @param node AST that calls the macro + * @param body the macro body + * @return true if the directive rendered successfully. + * @throws IOException + */ + public boolean render(InternalContextAdapter context, Writer writer, + Node node, Renderable body) + throws IOException + { + int callArgNum = node.jjtGetNumChildren(); + + // if this macro was invoked by a call directive, we might have a body AST here. + Object oldBodyRef = null; + if (body != null) + { + oldBodyRef = context.get(bodyReference); + context.put(bodyReference, body); + callArgNum--; // Remove the body AST from the arg count + } + + // is everything copacetic? + checkArgumentCount(node, callArgNum); + checkDepth(context); + + // put macro arg values and save the returned old/new value pairs + Object[] values = handleArgValues(context, node, callArgNum); + try + { + // render the velocity macro + context.pushCurrentMacroName(macroName); + nodeTree.render(context, writer); + } + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()"; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + finally + { + // if MacroOverflowException was thrown then it already empties the stack + // for everything else - e.g. other exceptions - we clean up after ourself + if (context.getCurrentMacroCallDepth() > 0) + context.popCurrentMacroName(); + + // clean up after the args and bodyRef + // but only if they weren't overridden inside + Object current = context.get(bodyReference); + if (current == body) + { + if (oldBodyRef != null) + { + context.put(bodyReference, oldBodyRef); + } + else + { + context.remove(bodyReference); + } + } + + for (int i = 1; i < macroArgs.size(); i++) + { + MacroArg macroArg = macroArgs.get(i); + current = context.get(macroArg.name); + Object given = values[(i-1) * 2 + 1]; + Object old = values[(i-1) * 2]; + if (current == given || current == null && given == NULL_VALUE_MARKER) + { + if (old == null) + { + context.remove(macroArg.name); + } + else if (old == NULL_VALUE_MARKER) + { + context.put(macroArg.name, null); + } + else + { + context.put(macroArg.name, old); + } + } + + if (enableBCmode) + { + /* allow for nested calls */ + Deque<String> literalsStack = (Deque<String>)context.get(literalArgArray[i]); + if (literalsStack != null) /* shouldn't be null */ + { + literalsStack.removeFirst(); + if (literalsStack.size() == 0) + { + context.remove(literalArgArray[i]); + } + } + } + } + } + + return true; + } + + /** + * Check whether the number of arguments given matches the number defined. + * @param node + * @param callArgNum + */ + protected void checkArgumentCount(Node node, int callArgNum) + { + // Check if we have more calling arguments then the macro accepts + if (callArgNum > macroArgs.size() - 1) + { + if (strictArguments) + { + throw new VelocityException("Provided " + callArgNum + " arguments but macro #" + + macroArgs.get(0).name + " accepts at most " + (macroArgs.size()-1) + + " at " + StringUtils.formatFileString(node), null, rsvc.getLogContext().getStackTrace()); + } + // Backward compatibility logging, Mainly for MacroForwardDefinedTestCase + log.debug("VM #{}: too many arguments to macro. Wanted {} got {}", + macroArgs.get(0).name, macroArgs.size() - 1, callArgNum); + } + } + + /** + * check that we aren't already at the max call depth and throws + * a MacroOverflowException if we are there. + * @param context + */ + protected void checkDepth(InternalContextAdapter context) + { + if (maxCallDepth > 0 && maxCallDepth == context.getCurrentMacroCallDepth()) + { + String[] stack = context.getMacroNameStack(); + + StringBuilder out = new StringBuilder(100) + .append("Max calling depth of ").append(maxCallDepth) + .append(" was exceeded in macro '").append(macroName) + .append("' with Call Stack:"); + for (int i = 0; i < stack.length; i++) + { + if (i != 0) + { + out.append("->"); + } + out.append(stack[i]); + } + out.append(" at ").append(StringUtils.formatFileString(this)); + log.error(out.toString()); + + // clean out the macro stack, since we just broke it + while (context.getCurrentMacroCallDepth() > 0) + { + context.popCurrentMacroName(); + } + throw new MacroOverflowException(out.toString(), null, rsvc.getLogContext().getStackTrace()); + } + } + + /** + * Gets the macro argument values and puts them in the context under + * the argument names. Store and return an array of old and new values + * paired for each argument name, for later cleanup. Also, put literal + * representations of arguments which evaluate to null in the context. + * @param context + * @param node + * @param callArgNum + * @return macro arguments values + */ + protected Object[] handleArgValues(InternalContextAdapter context, + Node node, int callArgNum) + { + // Changed two dimensional array to single dimensional to optimize memory lookups + Object[] values = new Object[macroArgs.size() * 2]; + + boolean warnedMissingArguments = false; + + // Move arguments into the macro's context. Start at one to skip macro name + for (int i = 1; i < macroArgs.size(); i++) + { + MacroArg macroArg = macroArgs.get(i); + Object oldVal = context.get(macroArg.name); + values[(i-1) * 2] = + oldVal == null + ? context.containsKey(macroArg.name) ? NULL_VALUE_MARKER : null + : oldVal; + + // put the new value in + Object newVal = null; + Node argNode = null; + if (i - 1 < callArgNum) + { + // There's a calling value. + argNode = node.jjtGetChild(i - 1); + newVal = argNode.value(context); + } + else if (macroArg.defaultVal != null) + { + // We don't have a calling value, but the macro defines a default value + newVal = macroArg.defaultVal.value(context); + } + else if (strictArguments) + { + // We come to this point if we don't have a calling value, and + // there is no default value. Not enough arguments defined. + int minArgNum = -1; //start at -1 to skip the macro name + // Calculate minimum number of args required for macro + for (MacroArg marg : macroArgs) + { + if (marg.defaultVal == null) minArgNum++; + } + throw new VelocityException("Need at least " + minArgNum + " argument for macro #" + + macroArgs.get(0).name + " but only " + callArgNum + " where provided at " + + StringUtils.formatFileString(node), null, rsvc.getLogContext().getStackTrace()); + } + else + { + if (!warnedMissingArguments) + { + // Backward compatibility logging, Mainly for MacroForwardDefinedTestCase + log.debug("VM #{}: too few arguments to macro. Wanted {} got {}", + macroArgs.get(0).name, macroArgs.size() - 1, callArgNum); + warnedMissingArguments = true; + } + if (enableBCmode) + { + // use the global context value as default + newVal = oldVal; + } + } + + values[(i-1) * 2 + 1] = newVal; + + /* when enableBCmode is true, we still store the actual reference passed to the macro + even if the value is not null, because *if* the argument is set to null *during* the macro rendering + we still expect the passed argument literal to be displayed to be fully backward compatible. */ + if (enableBCmode && /* newVal == null && */ argNode != null) + { + /* allow nested macro calls for B.C. */ + Deque<String> literalsStack = (Deque<String>)context.get(literalArgArray[i]); + if (literalsStack == null) + { + literalsStack = new LinkedList<>(); + context.put(literalArgArray[i], literalsStack); + } + /* Reflects the strange 1.7 behavor... */ + if (argNode != null && (argNode instanceof ASTReference || argNode instanceof ASTStringLiteral)) + { + literalsStack.addFirst(argNode.literal()); + } + else + { + literalsStack.addFirst('$' + macroArg.name); + } + } + } + + // Now really put the values in the context + for (int i = 1; i < macroArgs.size(); i++) + { + MacroArg macroArg = macroArgs.get(i); + Object value = values[(i-1) * 2 + 1]; + context.put(macroArg.name, value); + } + + // return the array of replaced and new values + return values; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/contrib/For.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/contrib/For.java new file mode 100644 index 00000000..aaefae9b --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/contrib/For.java @@ -0,0 +1,141 @@ +package org.apache.velocity.runtime.directive.contrib; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.directive.Foreach; +import org.apache.velocity.runtime.directive.MacroParseException; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.ASTReference; +import org.apache.velocity.runtime.parser.node.Node; +import org.apache.velocity.runtime.parser.node.ParserTreeConstants; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; + +/** + * The #for directive provides the behavior of the #foreach directive but also + * provides an 'index' keyword that allows the user to define an optional index variable + * that tracks the loop iterations. e.g.; #for($user in $users index $i). + * As $user iterates through $users the index reference $i will be equal to + * 0, 1, 2, etc.. + * @see org.apache.velocity.runtime.directive.Foreach + */ +public class For extends Foreach +{ + protected String counterName; + protected int counterInitialValue; + + @Override + public String getName() + { + return "for"; + } + + @Override + public int getType() + { + return BLOCK; + } + + @Override + public void init(RuntimeServices rs, InternalContextAdapter context, Node node) + throws TemplateInitException + { + super.init(rs, context, node); + // If we have more then 3 argument then the user has specified an + // index value, i.e.; #foreach($a in $b index $c) + if (node.jjtGetNumChildren() > 4) + { + // The index variable name is at position 4 + counterName = ((ASTReference) node.jjtGetChild(4)).getRootString(); + // The count value always starts at 0 when using an index. + counterInitialValue = 0; + } + } + + @Override + public boolean render(InternalContextAdapter context, Writer writer, Node node) + throws IOException + { + Object c = context.get(counterName); + context.put(counterName, counterInitialValue); + try + { + return super.render(context, writer, node); + } + finally + { + if (c != null) + { + context.put(counterName, c); + } + else + { + context.remove(counterName); + } + } + } + + @Override + protected void renderBlock(InternalContextAdapter context, Writer writer, Node node) + throws IOException + { + Object count = context.get(counterName); + if (count instanceof Number) + { + context.put(counterName, ((Number)count).intValue() + 1); + } + super.renderBlock(context, writer, node); + } + + /** + * We do not allow a word token in any other arg position except for the 2nd + * since we are looking for the pattern #foreach($foo in $bar). + */ + @Override + public void checkArgs(ArrayList<Integer> argtypes, Token t, + String templateName) throws ParseException + { + super.checkArgs(argtypes, t, templateName); + + // If #foreach is defining an index variable make sure it has the 'index + // $var' combo. + if (argtypes.size() > 3) + { + if (argtypes.get(3) != ParserTreeConstants.JJTWORD) + { + throw new MacroParseException( + "Expected word 'index' at argument position 4 in #foreach", + templateName, t); + } + else if (argtypes.size() == 4 + || argtypes.get(4) != ParserTreeConstants.JJTREFERENCE) + { + throw new MacroParseException( + "Expected a reference after 'index' in #foreach", templateName, t); + } + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java new file mode 100644 index 00000000..9e7ee389 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java @@ -0,0 +1,128 @@ +package org.apache.velocity.runtime.parser; + +/* + * 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. + */ +/* Generated By:JavaCC: Do not edit this line. CharStream.java Version 2.1 */ + +/** + * This interface describes a character stream that maintains line and + * column number positions of the characters. It also has the capability + * to backup the stream to some extent. An implementation of this + * interface is used in the TokenManager implementation generated by + * JavaCCParser. + * + * All the methods except backup can be implemented in any fashion. backup + * needs to be implemented correctly for the correct operation of the lexer. + * Rest of the methods are all used to get information like line number, + * column number and the String that constitutes a token and are not used + * by the lexer. Hence their implementation won't affect the generated lexer's + * operation. + */ + +public interface CharStream +{ + /** + * Returns the next character from the selected input. The method + * of selecting the input is the responsibility of the class + * implementing this interface. Can throw any java.io.IOException. + * @return read char + * @throws java.io.IOException + */ + char readChar() throws java.io.IOException; + + /** + * Returns the column number of the last character for current token (being + * matched after the last call to BeginTOken). + * @return ending column number + */ + int getEndColumn(); + + /** + * Returns the line number of the last character for current token (being + * matched after the last call to BeginTOken). + * @return ending line number + */ + int getEndLine(); + + /** + * Returns the column number of the first character for current token (being + * matched after the last call to BeginTOken). + * @return starting column number + */ + int getBeginColumn(); + + /** + * Returns the line number of the first character for current token (being + * matched after the last call to BeginTOken). + * @return starting line number + */ + int getBeginLine(); + + /** + * Backs up the input stream by amount steps. Lexer calls this method if it + * had already read some characters, but could not use them to match a + * (longer) token. So, they will be used again as the prefix of the next + * token and it is the implemetation's responsibility to do this right. + * @param amount + */ + void backup(int amount); + + /** + * Returns the next character that marks the beginning of the next token. + * All characters must remain in the buffer between two successive calls + * to this method to implement backup correctly. + * @return next token start char + * @throws java.io.IOException + */ + char BeginToken() throws java.io.IOException; + + /** + * Returns a string made up of characters from the marked token beginning + * to the current buffer position. Implementations have the choice of returning + * anything that they want to. For example, for efficiency, one might decide + * to just return null, which is a valid implementation. + * @return token image + */ + String GetImage(); + + /** + * <p>Returns an array of characters that make up the suffix of length 'len' for + * the currently matched token. This is used to build up the matched string + * for use in actions in the case of MORE. A simple and inefficient + * implementation of this is as follows :</p> + * <pre><code> + * { + * String t = GetImage(); + * return t.substring(t.length() - len, t.length()).toCharArray(); + * } + * </code></pre> + * @param len suffix len + * @return suffix + */ + char[] GetSuffix(int len); + + /** + * The lexer calls this function to indicate that it is done with the stream + * and hence implementations can free any resources held by this class. + * Again, the body of this function can be just empty and it will not + * affect the lexer's operation. + */ + void Done(); + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java new file mode 100644 index 00000000..c6738cd5 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java @@ -0,0 +1,165 @@ +package org.apache.velocity.runtime.parser; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.node.SimpleNode; +import org.apache.velocity.util.introspection.Info; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; + +/** + * <p>Track location in template files during rendering by populating the slf4j MDC tags <code>file</code>, <code>line</code> and <code>column</code>.</p> + * <p>An MDC-aware logger can then use this info to display the template location in the message</p> + * <p>For instance with webapp-slf4j-logger, it's enough to use <code>%file</code>, <code>%line</code> and <code>%column</code> in the logger format string.</p> + * <p>Since this feature can have a performance impact, it has to be enabled in <code>velocity.properties</code> using:</p> + * <pre><code>runtime.log.track_location = true</code></pre> + * <p>(typically in a development environment)</p> + * + * @author Claude Brisson + * @version $Id:$ + * @since 2.2 + */ + +public class LogContext +{ + protected static Logger logger = LoggerFactory.getLogger("rendering"); + + public static final String MDC_FILE = "file"; + public static final String MDC_LINE = "line"; + public static final String MDC_COLUMN = "column"; + + private boolean trackLocation; + + public LogContext(boolean trackLocation) + { + this.trackLocation = trackLocation; + } + + private static ThreadLocal<Deque<StackElement>> contextStack = new ThreadLocal<Deque<StackElement>>() + { + @Override + public Deque<StackElement> initialValue() + { + return new ArrayDeque<>(); + } + }; + + private static class StackElement + { + protected StackElement(SimpleNode src, Info info) + { + this.src = src; + this.info = info; + } + + protected SimpleNode src; + protected int count = 1; + protected Info info; + } + + public void pushLogContext(SimpleNode src, Info info) + { + if (!trackLocation) + { + return; + } + Deque<StackElement> stack = contextStack.get(); + StackElement last = stack.peek(); + if (last != null && last.src == src) + { + ++last.count; + } + else + { + stack.push(new StackElement(src, info)); + setLogContext(info); + } + } + + public void popLogContext() + { + if (!trackLocation) + { + return; + } + Deque<StackElement> stack = contextStack.get(); + StackElement last = stack.peek(); + if (last == null) + { + logger.error("log context is already empty"); + return; + } + if (--last.count == 0) + { + stack.pop(); + last = stack.peek(); + if (last == null) + { + clearLogContext(); + } + else + { + setLogContext(last.info); + } + } + } + + private void setLogContext(Info info) + { + MDC.put(MDC_FILE, info.getTemplateName()); + MDC.put(MDC_LINE, String.valueOf(info.getLine())); + MDC.put(MDC_COLUMN, String.valueOf(info.getColumn())); + } + + private void clearLogContext() + { + MDC.remove(MDC_FILE); + MDC.remove(MDC_LINE); + MDC.remove(MDC_COLUMN); + } + + private static final String STACKTRACE_LINE = " %s at %s[line %d, column %d]"; + + public String[] getStackTrace() + { + if (!trackLocation) + { + return null; + } + Deque<StackElement> stack = contextStack.get(); + List<String> levels = new ArrayList<>(); + for (StackElement level : stack) + { + String line = String.format(STACKTRACE_LINE, + level.src.literal(), + level.info.getTemplateName(), + level.info.getLine(), + level.info.getColumn()); + levels.add(line); + } + return levels.size() > 0 ? levels.toArray(new String[levels.size()]) : null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java new file mode 100644 index 00000000..4a8d5769 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java @@ -0,0 +1,237 @@ +package org.apache.velocity.runtime.parser; + +/* + * 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. + */ +/* Generated By:JavaCC: Do not edit this line. ParseException.java Version 3.0 */ + +/** + * This exception is thrown when parse errors are encountered. + * It is intended to be caught and typically will be rethrown + * as a ParseErrorException. + * + * <p>You can explicitly create objects of this exception type by + * calling the method generateParseException in the generated + * parser. + * + * You can modify this class to customize your error reporting + * mechanisms so long as you retain the public fields. + */ +public class ParseException extends Exception { + + private static final long serialVersionUID = -309603325673449381L; + + /** + * <p>This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form:</p> + * <pre> + * ParseException: <result of getMessage> + * </pre> + * @param currentTokenVal + * @param expectedTokenSequencesVal + * @param tokenImageVal + */ + public ParseException(Token currentTokenVal, + int[][] expectedTokenSequencesVal, + String[] tokenImageVal + ) + { + super(""); + specialConstructor = true; + currentToken = currentTokenVal; + expectedTokenSequences = expectedTokenSequencesVal; + tokenImage = tokenImageVal; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + public ParseException() { + super(); + specialConstructor = false; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + * @param message + */ + public ParseException(String message) { + super(message); + specialConstructor = false; + } + + /** + * This variable determines which constructor was used to create + * this object and thereby affects the semantics of the + * "getMessage" method (see below). + */ + protected boolean specialConstructor; + + /** + * This is the last token that has been consumed successfully. If + * this object has been created due to a parse error, the token + * followng this token will (therefore) be the first error token. + */ + public Token currentToken; + + /** + * Each entry in this array is an array of integers. Each array + * of integers represents a sequence of tokens (by their ordinal + * values) that is expected at this point of the parse. + */ + public int[][] expectedTokenSequences; + + /** + * This is a reference to the "tokenImage" array of the generated + * parser within which the parse error occurred. This array is + * defined in the generated ...Constants interface. + */ + public String[] tokenImage; + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + * @return message + */ + @Override + public String getMessage() { + if (!specialConstructor) { + return super.getMessage(); + } + String expected = ""; + int maxSize = 0; + for (int[] expectedTokenSequence : expectedTokenSequences) + { + if (maxSize < expectedTokenSequence.length) + { + maxSize = expectedTokenSequence.length; + } + for (int i : expectedTokenSequence) + { + expected += tokenImage[i] + " "; + } + if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) + { + expected += "..."; + } + expected += eol + " "; + } + String retval = "Encountered \""; + Token tok = currentToken.next; + for (int i = 0; i < maxSize; i++) { + if (i != 0) retval += " "; + if (tok.kind == 0) { + retval += tokenImage[0]; + break; + } + retval += add_escapes(tok.image); + tok = tok.next; + } + retval += "\" at line " + currentToken.next.beginLine + ", column " + currentToken.next.beginColumn; + retval += "." + eol; + if (expectedTokenSequences.length == 1) { + retval += "Was expecting:" + eol + " "; + } else { + retval += "Was expecting one of:" + eol + " "; + } + retval += expected; + return retval; + } + + /** + * The end of line string for this machine. + */ + protected String eol = System.lineSeparator(); + + /** + * Used to convert raw characters to their escaped version + * when these raw version cannot be used as part of an ASCII + * string literal. + * @param str raw characters + * @return escaped string + */ + protected String add_escapes(String str) { + StringBuilder retval = new StringBuilder(); + char ch; + for (int i = 0; i < str.length(); i++) { + switch (str.charAt(i)) + { + case 0 : + continue; + case '\b': + retval.append("\\b"); + continue; + case '\t': + retval.append("\\t"); + continue; + case '\n': + retval.append("\\n"); + continue; + case '\f': + retval.append("\\f"); + continue; + case '\r': + retval.append("\\r"); + continue; + case '\"': + retval.append("\\\""); + continue; + case '\'': + retval.append("\\\'"); + continue; + case '\\': + retval.append("\\\\"); + continue; + default: + if ((ch = str.charAt(i)) < 0x20 || ch > 0x7e) { + String s = "0000" + Integer.toString(ch, 16); + retval.append("\\u").append(s.substring(s.length() - 4, s.length())); + } else { + retval.append(ch); + } + } + } + return retval.toString(); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java new file mode 100644 index 00000000..73bc0e9b --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java @@ -0,0 +1,54 @@ +package org.apache.velocity.runtime.parser; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.parser.node.SimpleNode; + +import java.io.Reader; + +public interface Parser +{ + RuntimeServices getRuntimeServices(); + SimpleNode parse(Reader reader, Template template) throws ParseException; + void resetCurrentTemplate(); + Template getCurrentTemplate(); + Token getToken(int index); + boolean isDirective(String macro); + Directive getDirective(String directive); + void ReInit(CharStream stream); + + char dollar(); + char hash(); + char at(); + char asterisk(); + + default String lineComment() + { + return String.valueOf(hash()) + hash(); + } + + default String blockComment() + { + return String.valueOf(hash()) + asterisk(); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java new file mode 100644 index 00000000..acb20a97 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java @@ -0,0 +1,245 @@ +package org.apache.velocity.runtime.parser; + +/* + * 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. + */ + +import org.apache.velocity.exception.ExtendedParseException; +import org.apache.velocity.util.StringUtils; + +/** + * This is an extension of the ParseException, which also takes a + * template name. + * + * @see org.apache.velocity.runtime.parser.ParseException + * + * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class TemplateParseException + extends ParseException + implements ExtendedParseException +{ + private static final long serialVersionUID = -3146323135623083918L; + + /** + * This is the name of the template which contains the parsing error, or + * null if not defined. + */ + private final String templateName; + + /** + * This constructor is used to add a template name + * to info cribbed from a ParseException generated in the parser. + * @param currentTokenVal + * @param expectedTokenSequencesVal + * @param tokenImageVal + * @param templateNameVal + */ + public TemplateParseException(Token currentTokenVal, int [][] expectedTokenSequencesVal, String [] tokenImageVal, + String templateNameVal) + { + super(currentTokenVal, expectedTokenSequencesVal, tokenImageVal); + this.templateName = templateNameVal; + } + + /** + * <p>This constructor is used by the method "generateParseException" + * in the generated parser. Calling this constructor generates + * a new object of this type with the fields "currentToken", + * "expectedTokenSequences", and "tokenImage" set. The boolean + * flag "specialConstructor" is also set to true to indicate that + * this constructor was used to create this object. + * This constructor calls its super class with the empty string + * to force the "toString" method of parent class "Throwable" to + * print the error message in the form:</p> + * <pre> + * ParseException: <result of getMessage> + * </pre> + * @param currentTokenVal + * @param expectedTokenSequencesVal + * @param tokenImageVal + */ + public TemplateParseException(Token currentTokenVal, int [][] expectedTokenSequencesVal, String [] tokenImageVal) + { + super(currentTokenVal, expectedTokenSequencesVal, tokenImageVal); + templateName = "*unset*"; + } + + /** + * The following constructors are for use by you for whatever + * purpose you can think of. Constructing the exception in this + * manner makes the exception behave in the normal way - i.e., as + * documented in the class "Throwable". The fields "errorToken", + * "expectedTokenSequences", and "tokenImage" do not contain + * relevant information. The JavaCC generated code does not use + * these constructors. + */ + public TemplateParseException() + { + super(); + templateName = "*unset*"; + } + + /** + * Creates a new TemplateParseException object. + * + * @param message TODO: DOCUMENT ME! + */ + public TemplateParseException(String message) + { + super(message); + templateName = "*unset*"; + } + + /** + * returns the Template name where this exception occurred. + * @return The Template name where this exception occurred. + */ + @Override + public String getTemplateName() + { + return templateName; + } + + /** + * returns the line number where this exception occurred. + * @return The line number where this exception occurred. + */ + @Override + public int getLineNumber() + { + if ((currentToken != null) && (currentToken.next != null)) + { + return currentToken.next.beginLine; + } + else + { + return -1; + } + } + + /** + * returns the column number where this exception occurred. + * @return The column number where this exception occurred. + */ + @Override + public int getColumnNumber() + { + if ((currentToken != null) && (currentToken.next != null)) + { + return currentToken.next.beginColumn; + } + else + { + return -1; + } + } + + /** + * This method has the standard behavior when this object has been + * created using the standard constructors. Otherwise, it uses + * "currentToken" and "expectedTokenSequences" to generate a parse + * error message and returns it. If this object has been created + * due to a parse error, and you do not catch it (it gets thrown + * from the parser), then this method is called during the printing + * of the final stack trace, and hence the correct error message + * gets displayed. + * @return The error message. + */ + @Override + public String getMessage() + { + if (!specialConstructor) + { + StringBuilder sb = new StringBuilder(super.getMessage()); + appendTemplateInfo(sb); + return sb.toString(); + } + + int maxSize = 0; + + StringBuilder expected = new StringBuilder(); + + for (int[] expectedTokenSequence : expectedTokenSequences) + { + if (maxSize < expectedTokenSequence.length) + { + maxSize = expectedTokenSequence.length; + } + + for (int i : expectedTokenSequence) + { + expected.append(tokenImage[i]).append(" "); + } + + if (expectedTokenSequence[expectedTokenSequence.length - 1] != 0) + { + expected.append("..."); + } + + expected.append(eol).append(" "); + } + + StringBuilder retval = new StringBuilder("Encountered \""); + Token tok = currentToken.next; + + for (int i = 0; i < maxSize; i++) + { + if (i != 0) + { + retval.append(" "); + } + + if (tok.kind == 0) + { + retval.append(tokenImage[0]); + break; + } + + retval.append(add_escapes(tok.image)); + tok = tok.next; + } + + retval.append("\" at "); + appendTemplateInfo(retval); + + if (expectedTokenSequences.length == 1) + { + retval.append("Was expecting:").append(eol).append(" "); + } + else + { + retval.append("Was expecting one of:").append(eol).append(" "); + } + + // avoid JDK 1.3 StringBuffer.append(Object o) vs 1.4 StringBuffer.append(StringBuffer sb) gotcha. + retval.append(expected.toString()); + return retval.toString(); + } + + /** + * @param sb + */ + protected void appendTemplateInfo(final StringBuilder sb) + { + sb.append(StringUtils.formatFileString(getTemplateName(), getLineNumber(), getColumnNumber())); + sb.append(eol); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java new file mode 100644 index 00000000..c5133749 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java @@ -0,0 +1,526 @@ +package org.apache.velocity.runtime.parser; + +/* + * 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. + */ + +/** + * <p>An implementation of interface CharStream, where the stream is assumed to + * contain only ASCII characters (without unicode processing).</p> + * + * <p>NOTE : This class was originally an ASCII_CharStream autogenerated + * by Javacc. It was then modified via changing class name with appropriate + * fixes for CTORS, and mods to readChar().</p> + * + * <p>This is safe because we *always* use Reader with this class, and never a + * InputStream. This guarantees that we have a correct stream of 16-bit + * chars - all encoding transformations have been done elsewhere, so we + * believe that there is no risk in doing this. Time will tell :)</p> + * + */ +public final class VelocityCharStream +implements CharStream +{ + public static final boolean staticFlag = false; + int bufsize; + private int nextBufExpand; + int available; + int tokenBegin; + + public int bufpos = -1; + private int bufline[]; + private int bufcolumn[]; + + private int column = 0; + private int line = 1; + + private boolean prevCharIsCR = false; + private boolean prevCharIsLF = false; + + private java.io.Reader inputStream; + + private char[] buffer; + private int maxNextCharInd = 0; + private int inBuf = 0; + + /* CB - to properly handle EOF *inside* javacc lexer, + * we send a 'file separator' ascii char *just before* EOF + * (see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text) + */ + private boolean beforeEOF = false; + private static char END_OF_FILE = '\u001C'; + + private void ExpandBuff(boolean wrapAround) + { + char[] newbuffer = new char[bufsize + nextBufExpand]; + int newbufline[] = new int[bufsize + nextBufExpand]; + int newbufcolumn[] = new int[bufsize + nextBufExpand]; + + try + { + if (wrapAround) + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, + bufsize - tokenBegin, bufpos); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos += (bufsize - tokenBegin)); + } + else + { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; + + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; + + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; + + maxNextCharInd = (bufpos -= tokenBegin); + } + } + catch (Throwable t) + { + throw new Error(t.getMessage()); + } + + + bufsize += nextBufExpand; + nextBufExpand = bufsize; + available = bufsize; + tokenBegin = 0; + } + + private void FillBuff() throws java.io.IOException + { + if (maxNextCharInd == available) + { + if (available == bufsize) + { + if (tokenBegin > nextBufExpand) + { + bufpos = maxNextCharInd = 0; + available = tokenBegin; + } + else if (tokenBegin < 0) + { + bufpos = maxNextCharInd = 0; + } + else + { + ExpandBuff(false); + } + } + else if (available > tokenBegin) + { + available = bufsize; + } + else if ((tokenBegin - available) < nextBufExpand) + { + ExpandBuff(true); + } + else + { + available = tokenBegin; + } + } + + int i; + try + { + if ((i = inputStream.read(buffer, maxNextCharInd, + available - maxNextCharInd)) == -1) + { + if (beforeEOF) + { + inputStream.close(); + throw new java.io.IOException(); + } + buffer[maxNextCharInd++] = END_OF_FILE; + beforeEOF = true; + } + else + { + maxNextCharInd += i; + } + } + catch(java.io.IOException e) + { + --bufpos; + backup(0); + if (tokenBegin == -1) + { + tokenBegin = bufpos; + } + throw e; + } + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#BeginToken() + */ + @Override + public final char BeginToken() throws java.io.IOException + { + tokenBegin = -1; + char c = readChar(); + tokenBegin = bufpos; + + return c; + } + + private void UpdateLineColumn(char c) + { + column++; + + if (prevCharIsLF) + { + prevCharIsLF = false; + line += (column = 1); + } + else if (prevCharIsCR) + { + prevCharIsCR = false; + if (c == '\n') + { + prevCharIsLF = true; + } + else + { + line += (column = 1); + } + } + + switch (c) + { + case '\r' : + prevCharIsCR = true; + break; + case '\n' : + prevCharIsLF = true; + break; + case '\t' : + column--; + column += (8 - (column & 07)); + break; + default : + break; + } + + bufline[bufpos] = line; + bufcolumn[bufpos] = column; + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#readChar() + */ + @Override + public final char readChar() throws java.io.IOException + { + if (inBuf > 0) + { + --inBuf; + + /* + * was : return (char)((char)0xff & buffer[(bufpos == bufsize - 1) ? (bufpos = 0) : ++bufpos]); + */ + return buffer[(bufpos == bufsize - 1) ? (bufpos = 0) : ++bufpos]; + } + + if (++bufpos >= maxNextCharInd) + { + FillBuff(); + } + + /* + * was : char c = (char)((char)0xff & buffer[bufpos]); + */ + char c = buffer[bufpos]; + + UpdateLineColumn(c); + return (c); + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#getEndColumn() + */ + @Override + public final int getEndColumn() + { + return bufcolumn[bufpos]; + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#getEndLine() + */ + @Override + public final int getEndLine() + { + return bufline[bufpos]; + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#getBeginColumn() + */ + @Override + public final int getBeginColumn() + { + return bufcolumn[tokenBegin]; + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#getBeginLine() + */ + @Override + public final int getBeginLine() + { + return bufline[tokenBegin]; + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#backup(int) + */ + @Override + public final void backup(int amount) + { + + inBuf += amount; + if ((bufpos -= amount) < 0) + bufpos += bufsize; + } + + /** + * @param dstream + * @param startline + * @param startcolumn + * @param buffersize + */ + public VelocityCharStream(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + available = bufsize = nextBufExpand = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + + /** + * @param dstream + * @param startline + * @param startcolumn + */ + public VelocityCharStream(java.io.Reader dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + /** + * @param dstream + * @param startline + * @param startcolumn + * @param buffersize + */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn, int buffersize) + { + inputStream = dstream; + line = startline; + column = startcolumn - 1; + + if (buffer == null || buffersize != buffer.length) + { + available = bufsize = nextBufExpand = buffersize; + buffer = new char[buffersize]; + bufline = new int[buffersize]; + bufcolumn = new int[buffersize]; + } + prevCharIsLF = prevCharIsCR = false; + tokenBegin = inBuf = maxNextCharInd = 0; + bufpos = -1; + beforeEOF = false; + } + + /** + * @param dstream + * @param startline + * @param startcolumn + */ + public void ReInit(java.io.Reader dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + /** + * @param dstream + * @param startline + * @param startcolumn + * @param buffersize + */ + public VelocityCharStream(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + this(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + + /** + * @param dstream + * @param startline + * @param startcolumn + */ + public VelocityCharStream(java.io.InputStream dstream, int startline, + int startcolumn) + { + this(dstream, startline, startcolumn, 4096); + } + + /** + * @param dstream + * @param startline + * @param startcolumn + * @param buffersize + */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn, int buffersize) + { + ReInit(new java.io.InputStreamReader(dstream), startline, startcolumn, buffersize); + } + /** + * @param dstream + * @param startline + * @param startcolumn + */ + public void ReInit(java.io.InputStream dstream, int startline, + int startcolumn) + { + ReInit(dstream, startline, startcolumn, 4096); + } + /** + * @see org.apache.velocity.runtime.parser.CharStream#GetImage() + */ + @Override + public final String GetImage() + { + if (bufpos >= tokenBegin) + { + return new String(buffer, tokenBegin, bufpos - tokenBegin + 1); + } + else + { + return new String(buffer, tokenBegin, bufsize - tokenBegin) + + new String(buffer, 0, bufpos + 1); + } + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#GetSuffix(int) + */ + @Override + public final char[] GetSuffix(int len) + { + char[] ret = new char[len]; + + if ((bufpos + 1) >= len) + { + System.arraycopy(buffer, bufpos - len + 1, ret, 0, len); + } + else + { + System.arraycopy(buffer, bufsize - (len - bufpos - 1), ret, 0, + len - bufpos - 1); + System.arraycopy(buffer, 0, ret, len - bufpos - 1, bufpos + 1); + } + + return ret; + } + + /** + * @see org.apache.velocity.runtime.parser.CharStream#Done() + */ + @Override + public void Done() + { + buffer = null; + bufline = null; + bufcolumn = null; + } + + /** + * Method to adjust line and column numbers for the start of a token.<BR> + * @param newLine + * @param newCol + */ + public void adjustBeginLineColumn(int newLine, int newCol) + { + int start = tokenBegin; + int len; + + if (bufpos >= tokenBegin) + { + len = bufpos - tokenBegin + inBuf + 1; + } + else + { + len = bufsize - tokenBegin + bufpos + 1 + inBuf; + } + + int i = 0, j = 0, k = 0; + int nextColDiff = 0, columnDiff = 0; + + while (i < len && + bufline[j = start % bufsize] == bufline[k = ++start % bufsize]) + { + bufline[j] = newLine; + nextColDiff = columnDiff + bufcolumn[k] - bufcolumn[j]; + bufcolumn[j] = newCol + columnDiff; + columnDiff = nextColDiff; + i++; + } + + if (i < len) + { + bufline[j] = newLine++; + bufcolumn[j] = newCol + columnDiff; + + while (i++ < len) + { + if (bufline[j = start % bufsize] != bufline[++start % bufsize]) + bufline[j] = newLine++; + else + bufline[j] = newLine; + } + } + + line = bufline[j]; + column = bufcolumn[j]; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java new file mode 100644 index 00000000..119c4865 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java @@ -0,0 +1,90 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.DuckType; + +/** + * Handles number addition of nodes.<br><br> + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTAddNode extends ASTMathNode +{ + /** + * @param id + */ + public ASTAddNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTAddNode(Parser p, int id) + { + super(p, id); + } + + @Override + protected Object handleSpecial(Object left, Object right, InternalContextAdapter context) + { + // check for strings, but don't coerce + String lstr = DuckType.asString(left, false); + String rstr = DuckType.asString(right, false); + if (lstr != null || rstr != null) + { + if (lstr == null) + { + lstr = left != null ? left.toString() : jjtGetChild(0).literal(); + } + else if (rstr == null) + { + rstr = right != null ? right.toString() : jjtGetChild(1).literal(); + } + return lstr.concat(rstr); + } + return null; + } + + @Override + public String getLiteralOperator() + { + return "+"; + } + + @Override + public Number perform(Number left, Number right, InternalContextAdapter context) + { + return MathUtils.add(left, right); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java new file mode 100644 index 00000000..2338d40f --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java @@ -0,0 +1,123 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTAndNode extends ASTLogicalOperator +{ + /** + * @param id + */ + public ASTAndNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTAndNode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return "&&"; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * Returns the value of the expression. + * Since the value of the expression is simply the boolean + * result of evaluate(), lets return that. + * @param context + * @return The value of the expression. + * @throws MethodInvocationException + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + return evaluate(context) ? Boolean.TRUE : Boolean.FALSE; + } + + /** + * logical and : + * <pre> + * null && right = false + * left && null = false + * null && null = false + * </pre> + * @param context + * @return True if both sides are true. + * @throws MethodInvocationException + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + Node left = jjtGetChild(0); + Node right = jjtGetChild(1); + + /* + * null == false + */ + if (left == null || right == null) + { + return false; + } + + /* + * short circuit the test. Don't eval the RHS if the LHS is false + */ + + if( left.evaluate( context ) ) + { + if ( right.evaluate( context ) ) + { + return true; + } + } + + return false; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java new file mode 100644 index 00000000..2cc2973d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java @@ -0,0 +1,69 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTAssignment extends SimpleNode +{ + /** + * @param id + */ + public ASTAssignment(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTAssignment(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java new file mode 100644 index 00000000..8b776eaf --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java @@ -0,0 +1,47 @@ +package org.apache.velocity.runtime.parser.node; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +public abstract class ASTBinaryOperator extends SimpleNode +{ + public ASTBinaryOperator(int id) + { + super(id); + } + + public ASTBinaryOperator(Parser p, int id) + { + super(p, id); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + /** + * get the string representing the mathematical operator + * @return operator string + */ + public abstract String getLiteralOperator(); + + @Override + public String literal() + { + StringBuilder builder = new StringBuilder(); + builder.append(jjtGetChild(0).literal()); + builder.append(' '); + builder.append(getLiteralOperator()); + builder.append(' '); + return builder.toString(); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java new file mode 100644 index 00000000..c8358ed5 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java @@ -0,0 +1,158 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; +import org.apache.velocity.runtime.parser.Parser; + +import java.io.IOException; +import java.io.Writer; + + +/** + * + */ +public class ASTBlock extends SimpleNode +{ + private String prefix = ""; + private String postfix = ""; + + // used during parsing + public boolean endsWithNewline = false; + + /* + * '#' and '$' prefix characters eaten by javacc MORE mode, prefixing the '#' ending the block + */ + private String morePostfix = ""; + + /** + * @param id + */ + public ASTBlock(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTBlock(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + /** + * set indentation prefix + * @param prefix + */ + public void setPrefix(String prefix) + { + this.prefix = prefix; + } + + /** + * get indentation prefix + * @return indentation prefix + */ + public String getPrefix() + { + return prefix; + } + + public void setMorePostfix(String morePosffix) + { + this.morePostfix = morePosffix; + } + + /** + * set indentation postfix + * @param postfix + */ + public void setPostfix(String postfix) + { + this.postfix = postfix; + } + + /** + * get indentation postfix + * @return indentation prefix + */ + public String getPostfix() + { + return postfix; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException, MethodInvocationException, + ResourceNotFoundException, ParseErrorException + { + SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling(); + + if (spaceGobbling == SpaceGobbling.NONE) + { + writer.write(prefix); + } + + int i, k = jjtGetNumChildren(); + + for (i = 0; i < k; i++) + jjtGetChild(i).render(context, writer); + + if (morePostfix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0) + { + writer.write(postfix); + } + + writer.write(morePostfix); + + return true; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java new file mode 100644 index 00000000..b2d3ba2a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java @@ -0,0 +1,109 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; + +import java.io.IOException; +import java.io.Writer; + +/** + * Represents all comments... + * + * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTComment extends SimpleNode +{ + private static final char[] ZILCH = "".toCharArray(); + + private char[] carr; + + /** + * @param id + */ + public ASTComment(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTComment(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * We need to make sure we catch any of the dreaded MORE tokens. + * @param context + * @param data + * @return The data object. + */ + @Override + public Object init(InternalContextAdapter context, Object data) + { + Token t = getFirstToken(); + + int loc1 = t.image.indexOf(parser.lineComment()); + int loc2 = t.image.indexOf(parser.blockComment()); + + if (loc1 == -1 && loc2 == -1) + { + carr = ZILCH; + } + else + { + carr = t.image.substring(0, (loc1 == -1) ? loc2 : loc1).toCharArray(); + } + + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException, MethodInvocationException, ParseErrorException, ResourceNotFoundException + { + writer.write(carr); + return true; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java new file mode 100644 index 00000000..721aa23d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java @@ -0,0 +1,177 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.DuckType; +import org.apache.velocity.util.StringUtils; + +/** + * Numeric comparison support<br><br> + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author Nathan Bubna + */ +public abstract class ASTComparisonNode extends ASTBinaryOperator +{ + /** + * @param id + */ + public ASTComparisonNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTComparisonNode(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException + { + Object left = jjtGetChild(0).value(context); + Object right = jjtGetChild(1).value(context); + + if (left == null || right == null) + { + return compareNull(left, right); + } + Boolean result = compareNumbers(left, right); + if (result == null) + { + result = compareNonNumber(left, right); + } + return result; + } + + /** + * Always false by default, != and == subclasses must override this. + * @param left + * @param right + * @return comparison result + */ + public boolean compareNull(Object left, Object right) + { + // if either side is null, log and bail + String msg = (left == null ? "Left" : "Right") + + " side (" + + jjtGetChild( (left == null? 0 : 1) ).literal() + + ") of comparison operation has null value at " + + StringUtils.formatFileString(this); + if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false)) + { + throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); + } + log.error(msg); + return false; + } + + /** + * compare numbers + * @param left + * @param right + * @return comparison result + */ + public Boolean compareNumbers(Object left, Object right) + { + try + { + left = DuckType.asNumber(left); + } + catch (NumberFormatException nfe) {} + try + { + right = DuckType.asNumber(right); + } + catch (NumberFormatException nfe) {} + + // only compare Numbers + if (left instanceof Number && right instanceof Number) + { + return numberTest(MathUtils.compare((Number)left, (Number)right)); + } + return null; + } + + /** + * get the string representing the mathematical operator + * @return operator string + */ + @Override + public abstract String getLiteralOperator(); + + /** + * performs the actual comparison + * @param compareResult + * @return comparison result + */ + public abstract boolean numberTest(int compareResult); + + public boolean compareNonNumber(Object left, Object right) + { + // by default, log and bail + String msg = (right instanceof Number ? "Left" : "Right") + + " side of comparison operation is not a number at " + + StringUtils.formatFileString(this); + if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false)) + { + throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); + } + log.error(msg); + return false; + } + + private String getLiteral(boolean left) + { + return jjtGetChild(left ? 0 : 1).literal(); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) throws MethodInvocationException + { + return evaluate(context); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java new file mode 100644 index 00000000..925edf45 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java @@ -0,0 +1,372 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; +import org.apache.velocity.runtime.directive.BlockMacro; +import org.apache.velocity.runtime.directive.Directive; +import org.apache.velocity.runtime.directive.RuntimeMacro; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.StandardParserConstants; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.introspection.Info; + +import java.io.IOException; +import java.io.Writer; + +/** + * This class is responsible for handling the pluggable + * directives in VTL. + * + * For example : #foreach() + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:kav@kav.dk">Kasper Nielsen</a> + * @version $Id$ + */ +public class ASTDirective extends SimpleNode +{ + private Directive directive = null; + private String directiveName = ""; + private boolean isDirective; + private boolean isInitialized; + + private String prefix = ""; + private String postfix = ""; + + /* + * '#' and '$' prefix characters eaten by javacc MORE mode + */ + private String morePrefix = ""; + + /** + * @param id + */ + public ASTDirective(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTDirective(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public synchronized Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + Token t; + + /* method is synchronized to avoid concurrent directive initialization **/ + + if (!isInitialized) + { + super.init( context, data ); + + /* + * handle '$' and '#' chars prefix + */ + t = getFirstToken(); + int pos = -1; + while (t != null && (pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar())) == -1) + { + t = t.next; + } + if (t != null && pos > 0) + { + morePrefix = t.image.substring(0, pos); + } + + /* + * only do things that are not context dependent + */ + + if (parser.isDirective( directiveName )) + { + isDirective = true; + + try + { + directive = parser.getDirective( directiveName ) + .getClass().newInstance(); + } + catch (InstantiationException | IllegalAccessException e) + { + throw new VelocityException( + "Couldn't initialize directive of class " + + parser.getDirective(directiveName).getClass().getName(), + e, rsvc.getLogContext().getStackTrace()); + } + + t = getFirstToken(); + if (t.kind == StandardParserConstants.WHITESPACE) t = t.next; + directive.setLocation(t.beginLine, t.beginColumn, getTemplate()); + directive.init(rsvc, context, this); + } + else if( directiveName.startsWith(String.valueOf(rsvc.getParserConfiguration().getAtChar())) ) + { + if( this.jjtGetNumChildren() > 0 ) + { + // block macro call (normal macro call but has AST body) + directiveName = directiveName.substring(1); + + directive = new BlockMacro(); + directive.setLocation(getLine(), getColumn(), getTemplate()); + + try + { + ((BlockMacro)directive).init( rsvc, directiveName, context, this ); + } + catch (TemplateInitException die) + { + throw new TemplateInitException(die.getMessage(), + (ParseException) die.getCause(), + rsvc.getLogContext().getStackTrace(), + die.getTemplateName(), + die.getColumnNumber() + getColumn(), + die.getLineNumber() + getLine()); + } + isDirective = true; + } + else + { + // this is a fake block macro call without a body. e.g. #@foo + // just render as it is + isDirective = false; + } + } + else + { + /* + Create a new RuntimeMacro + */ + directive = new RuntimeMacro(); + directive.setLocation(getLine(), getColumn(), getTemplate()); + + /* + Initialize it + */ + try + { + ((RuntimeMacro)directive).init( rsvc, directiveName, context, this ); + } + + /* + correct the line/column number if an exception is caught + */ + catch (TemplateInitException die) + { + throw new TemplateInitException(die.getMessage(), + (ParseException) die.getCause(), + rsvc.getLogContext().getStackTrace(), + die.getTemplateName(), + die.getColumnNumber() + getColumn(), + die.getLineNumber() + getLine()); + } + isDirective = true; + + } + + isInitialized = true; + + saveTokenImages(); + cleanupParserAndTokens(); + } + + if (morePrefix.length() == 0 && rsvc.getSpaceGobbling() == SpaceGobbling.STRUCTURED && isInitialized && isDirective && directive.getType() == Directive.BLOCK) + { + NodeUtils.fixIndentation(this, prefix); + } + + return data; + } + + /** + * set indentation prefix + * @param prefix + */ + public void setPrefix(String prefix) + { + this.prefix = prefix; + } + + /** + * get indentation prefix + * @return indentation prefix + */ + public String getPrefix() + { + return prefix; + } + + /** + * set indentation postfix + * @param postfix + */ + public void setPostfix(String postfix) + { + this.postfix = postfix; + } + + /** + * get indentation postfix + * @return indentation prefix + */ + public String getPostfix() + { + return postfix; + } + + /** + * more prefix getter + * @return more prefix + */ + public String getMorePrefix() + { + return morePrefix; + } + + public int getDirectiveType() + { + return directive.getType(); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException,MethodInvocationException, ResourceNotFoundException, ParseErrorException + { + SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling(); + /* + * normal processing + */ + + if (isDirective) + { + if (morePrefix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0) + { + writer.write(prefix); + } + + writer.write(morePrefix); + + try + { + rsvc.getLogContext().pushLogContext(this, new Info(getTemplateName(), getLine(), getColumn())); + directive.render(context, writer, this); + } + finally + { + rsvc.getLogContext().popLogContext(); + } + + if (morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE) + { + writer.write(postfix); + } + } + else + { + writer.write(prefix); + writer.write(morePrefix); + writer.write(rsvc.getParserConfiguration().getHashChar()); + writer.write(directiveName); + writer.write(postfix); + } + + return true; + } + + /** + * Sets the directive name. Used by the parser. This keeps us from having to + * dig it out of the token stream and gives the parse the change to override. + * @param str + */ + public void setDirectiveName( String str ) + { + directiveName = str; + } + + /** + * Gets the name of this directive. + * @return The name of this directive. + */ + public String getDirectiveName() + { + return directiveName; + } + + @Override + public String toString() + { + return "ASTDirective [" + super.toString() + ", directiveName=" + + directiveName + "]"; + } + + /** + * Returns the string "#<i>directive_name</i>(...)". Arguments literals are not rendered. This method is only + * used for displaying the VTL stacktrace when a rendering error is encountered when runtime.log.track_location is true. + * @return + */ + @Override + public String literal() + { + if (literal != null) + { + return literal; + } + StringBuilder builder = new StringBuilder(); + builder.append('#').append(getDirectiveName()).append("(...)"); + + return literal = builder.toString(); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java new file mode 100644 index 00000000..66f74266 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java @@ -0,0 +1,53 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +public class ASTDirectiveAssign extends SimpleNode +{ + + public ASTDirectiveAssign(int i) + { + super(i); + } + + public ASTDirectiveAssign(Parser p, int i) + { + super(p, i); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + saveTokenImages(); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java new file mode 100644 index 00000000..e070a78a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java @@ -0,0 +1,86 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MathException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles number division of nodes<br><br> + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTDivNode extends ASTMathNode +{ + /** + * @param id + */ + public ASTDivNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTDivNode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return "/"; + } + + @Override + public Number perform(Number left, Number right, InternalContextAdapter context) + { + /* + * check for divide by 0 + */ + if (MathUtils.isZero(right)) + { + String msg = "Right side of division operation is zero. Must be non-zero. " + + getLocation(context); + if (strictMode) + { + log.error(msg); + throw new MathException(msg, rsvc.getLogContext().getStackTrace()); + } + else + { + log.debug(msg); + return null; + } + } + return MathUtils.divide(left, right); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java new file mode 100644 index 00000000..5a2411bb --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java @@ -0,0 +1,96 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.DuckType; + +/** + * Handles <code>arg1 == arg2</code> + * + * This operator requires that the LHS and RHS are both of the + * same Class, both numbers or both coerce-able to strings. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author Nathan Bubna + */ +public class ASTEQNode extends ASTComparisonNode +{ + public ASTEQNode(int id) + { + super(id); + } + + public ASTEQNode(Parser p, int id) + { + super(p, id); + } + + @Override + public boolean compareNull(Object left, Object right) + { + // at least one is null, see if other is null or acts as a null + return left == right || DuckType.asNull(left == null ? right : left); + } + + @Override + public String getLiteralOperator() + { + return "=="; + } + + @Override + public boolean numberTest(int compareResult) + { + return compareResult == 0; + } + + @Override + public boolean compareNonNumber(Object left, Object right) + { + /* + * if both are not null, then assume that if one class + * is a subclass of the other that we should use the equals operator + */ + if (left.getClass().isAssignableFrom(right.getClass()) || + right.getClass().isAssignableFrom(left.getClass())) + { + return left.equals(right); + } + + // coerce to string, remember getAsString() methods may return null + left = DuckType.asString(left); + right = DuckType.asString(right); + if (left == right) + { + return true; + } + else if (left == null || right == null) + { + return false; + } + else + { + return left.equals(right); + } + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java new file mode 100644 index 00000000..78334c33 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java @@ -0,0 +1,110 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +import java.io.IOException; +import java.io.Writer; + +/** + * This class is responsible for handling the ElseIf VTL control statement. + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ +*/ +public class ASTElseIfStatement extends SimpleNode +{ + /** + * @param id + */ + public ASTElseIfStatement(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTElseIfStatement(Parser p, int id) + { + super(p, id); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * An ASTElseStatement is true if the expression + * it contains evaluates to true. Expressions know + * how to evaluate themselves, so we do that + * here and return the value back to ASTIfStatement + * where this node was originally asked to evaluate + * itself. + * @param context + * @return True if all children are true. + * @throws MethodInvocationException + */ + @Override + public boolean evaluate (InternalContextAdapter context) + throws MethodInvocationException + { + return jjtGetChild(0).evaluate(context); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException,MethodInvocationException, + ResourceNotFoundException, ParseErrorException + { + return jjtGetChild(1).render( context, writer ); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java new file mode 100644 index 00000000..378501cd --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java @@ -0,0 +1,87 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * This class is responsible for handling the Else VTL control statement. + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTElseStatement extends SimpleNode +{ + /** + * @param id + */ + public ASTElseStatement(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTElseStatement(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * An ASTElseStatement always evaluates to + * true. Basically behaves like an #if(true). + * @param context + * @return Always true. + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return true; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java new file mode 100644 index 00000000..67539ec0 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java @@ -0,0 +1,90 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.runtime.parser.Parser; + +import java.io.IOException; +import java.io.Writer; + +/** + * This class is responsible for handling Escapes + * in VTL. + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTEscape extends SimpleNode +{ + /** Used by the parser */ + public String val; + private char[] ctext; + + /** + * @param id + */ + public ASTEscape(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTEscape(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) + { + ctext = val.toCharArray(); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException + { + writer.write(ctext); + return true; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java new file mode 100644 index 00000000..3a7ba3a7 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java @@ -0,0 +1,91 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +import java.io.IOException; +import java.io.Writer; + +/** + * This class is responsible for handling EscapedDirectives + * in VTL. + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTEscapedDirective extends SimpleNode +{ + /** + * @param id + */ + public ASTEscapedDirective(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTEscapedDirective(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException + { + writer.write(firstImage); + return true; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + saveTokenImages(); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java new file mode 100644 index 00000000..6b11698a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java @@ -0,0 +1,96 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTExpression extends SimpleNode +{ + /** + * @param id + */ + public ASTExpression(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTExpression(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + return jjtGetChild(0).evaluate(context); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + return jjtGetChild(0).value(context); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + saveTokenImages(); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + @Override + public String literal() + { + return jjtGetChild(0).literal(); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java new file mode 100644 index 00000000..aed9d6f5 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java @@ -0,0 +1,88 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTFalse extends SimpleNode +{ + private static Boolean value = Boolean.FALSE; + + /** + * @param id + */ + public ASTFalse(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTFalse(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return false; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + { + return value; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java new file mode 100644 index 00000000..1f13c30a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java @@ -0,0 +1,126 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; + +import java.math.BigDecimal; + + +/** + * Handles floating point numbers. The value will be either a Double + * or a BigDecimal. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @since 1.5 + */ +public class ASTFloatingPointLiteral extends SimpleNode +{ + + // This may be of type Double or BigDecimal + private Number value = null; + + /** + * @param id + */ + public ASTFloatingPointLiteral(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTFloatingPointLiteral(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * Initialization method - doesn't do much but do the object + * creation. We only need to do it once. + * @param context + * @param data + * @return The data object. + * @throws TemplateInitException + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + /* + * init the tree correctly + */ + + super.init( context, data ); + + /* + * Determine the size of the item and make it a Double or BigDecimal as appropriate. + */ + String str = getFirstToken().image; + try + { + value = Double.valueOf( str ); + + } catch ( NumberFormatException E1 ) + { + + // if there's still an Exception it will propqgate out + value = new BigDecimal( str ); + + } + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + { + return value; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || !MathUtils.isZero(value); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java new file mode 100644 index 00000000..62be6c76 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java @@ -0,0 +1,51 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles arg1 >= arg2<br><br> + */ +public class ASTGENode extends ASTComparisonNode +{ + public ASTGENode(int id) + { + super(id); + } + + public ASTGENode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return ">="; + } + + @Override + public boolean numberTest(int compareResult) + { + return compareResult >= 0; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java new file mode 100644 index 00000000..2eddc6f7 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java @@ -0,0 +1,51 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles arg1 > arg2<br><br> + */ +public class ASTGTNode extends ASTComparisonNode +{ + public ASTGTNode(int id) + { + super(id); + } + + public ASTGTNode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return ">"; + } + + @Override + public boolean numberTest(int compareResult) + { + return compareResult == 1; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java new file mode 100644 index 00000000..3cb762a4 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java @@ -0,0 +1,305 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.IntrospectionCacheData; +import org.apache.velocity.util.introspection.VelPropertyGet; + +import java.lang.reflect.InvocationTargetException; + +/** + * ASTIdentifier.java + * + * Method support for identifiers : $foo + * + * mainly used by ASTReference + * + * Introspection is now moved to 'just in time' or at render / execution + * time. There are many reasons why this has to be done, but the + * primary two are thread safety, to remove any context-derived + * information from class member variables. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTIdentifier extends SimpleNode +{ + private String identifier = ""; + + /** + * This is really immutable after the init, so keep one for this node + */ + protected Info uberInfo; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + /** + * @param id + */ + public ASTIdentifier(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTIdentifier(Parser p, int id) + { + super(p, id); + } + + /** + * Identifier getter + * @return identifier + */ + public String getIdentifier() + { + return identifier; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * simple init - don't do anything that is context specific. + * just get what we need from the AST, which is static. + * @param context + * @param data + * @return The data object. + * @throws TemplateInitException + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + super.init(context, data); + + identifier = rsvc.useStringInterning() ? getFirstToken().image.intern() : getFirstToken().image; + + uberInfo = new Info(getTemplateName(), getLine(), getColumn()); + + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + + saveTokenImages(); + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + VelPropertyGet vg = null; + + try + { + /* + * first, see if we have this information cached. + */ + + IntrospectionCacheData icd = context.icacheGet(this); + Class<?> clazz = o instanceof Class<?> ? (Class<?>)o : o.getClass(); + + /* + * if we have the cache data and the class of the object we are + * invoked with is the same as that in the cache, then we must + * be all right. The last 'variable' is the method name, and + * that is fixed in the template :) + */ + + if ( icd != null && (icd.contextData == clazz) ) + { + vg = (VelPropertyGet) icd.thingy; + } + else + { + /* + * otherwise, do the introspection, and cache it. Use the + * uberspector + */ + + vg = rsvc.getUberspect().getPropertyGet(o, identifier, uberInfo); + + if (vg != null && vg.isCacheable()) + { + icd = new IntrospectionCacheData(); + icd.contextData = clazz; + icd.thingy = vg; + context.icachePut(this,icd); + } + } + } + + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "ASTIdentifier.execute() : identifier = "+identifier; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + /* + * we have no getter... punt... + */ + + if (vg == null) + { + if (strictRef) + { + throw new MethodInvocationException("Object '" + o.getClass().getName() + + "' does not contain property '" + identifier + "'", + null, rsvc.getLogContext().getStackTrace(), identifier, + uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn()); + } + else + { + return null; + } + } + + /* + * now try and execute. If we get a MIE, throw that + * as the app wants to get these. If not, log and punt. + */ + try + { + return vg.invoke(o); + } + catch(InvocationTargetException ite) + { + /* + * if we have an event cartridge, see if it wants to veto + * also, let non-Exception Throwables go... + */ + + Throwable t = ite.getTargetException(); + if (t instanceof Exception) + { + try + { + return EventHandlerUtil.methodException(rsvc, context, o.getClass(), vg.getMethodName(), + (Exception) t, uberInfo); + } + + /* + * If the event handler throws an exception, then wrap it + * in a MethodInvocationException. + */ + catch( Exception e ) + { + throw new MethodInvocationException( + "Invocation of method '" + vg.getMethodName() + "'" + + " in " + o.getClass() + + " threw exception " + + ite.getTargetException().toString(), + ite.getTargetException(), rsvc.getLogContext().getStackTrace(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn()); + } + } + else + { + /* + * no event cartridge to override. Just throw + */ + + throw new MethodInvocationException( + "Invocation of method '" + vg.getMethodName() + "'" + + " in " + o.getClass() + + " threw exception " + + ite.getTargetException().toString(), + ite.getTargetException(), rsvc.getLogContext().getStackTrace(), vg.getMethodName(), getTemplateName(), this.getLine(), this.getColumn()); + + + } + } + catch(IllegalArgumentException iae) + { + return null; + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "ASTIdentifier() : exception invoking method " + + "for identifier '" + identifier + "' in " + + o.getClass(); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + /** + * Returns the string ".<i>identifier</i>". This method is only used for displaying the VTL stacktrace + * when a rendering error is encountered when runtime.log.track_location is true. + * @return + */ + @Override + public String literal() + { + if (literal != null) + { + return literal; + } + return literal = '.' + getIdentifier(); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java new file mode 100644 index 00000000..b1b1bb66 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java @@ -0,0 +1,216 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; + +import java.io.IOException; +import java.io.Writer; + + +/** + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTIfStatement extends SimpleNode +{ + private String prefix = ""; + private String postfix = ""; + + /* + * '#' and '$' prefix characters eaten by javacc MORE mode + */ + private String morePrefix = ""; + + /** + * @param id + */ + public ASTIfStatement(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTIfStatement(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + + /* + * handle '$' and '#' chars prefix + */ + Token t = getFirstToken(); + int pos = -1; + while (t != null && (pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar())) == -1) + { + t = t.next; + } + if (t != null && pos > 0) + { + morePrefix = t.image.substring(0, pos); + } + + /* handle structured space gobbling */ + if (rsvc.getSpaceGobbling() == SpaceGobbling.STRUCTURED && postfix.length() > 0) + { + NodeUtils.fixIndentation(this, prefix); + } + + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + /** + * set indentation prefix + * @param prefix + */ + public void setPrefix(String prefix) + { + this.prefix = prefix; + } + + /** + * get indentation prefix + * @return prefix + */ + public String getPrefix() + { + return prefix; + } + + /** + * set indentation postfix + * @param postfix + */ + public void setPostfix(String postfix) + { + this.postfix = postfix; + } + + /** + * get indentation postfix + * @return postfix + */ + public String getPostfix() + { + return postfix; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException,MethodInvocationException, + ResourceNotFoundException, ParseErrorException + { + SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling(); + + if (morePrefix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0) + { + writer.write(prefix); + } + + writer.write(morePrefix); + + /* + * Check if the #if(expression) construct evaluates to true: + */ + if (jjtGetChild(0).evaluate(context)) + { + jjtGetChild(1).render(context, writer); + } + else + { + int totalNodes = jjtGetNumChildren(); + + /* + * Now check the remaining nodes left in the + * if construct. The nodes are either elseif + * nodes or else nodes. Each of these node + * types knows how to evaluate themselves. If + * a node evaluates to true then the node will + * render itself and this method will return + * as there is nothing left to do. + */ + for (int i = 2; i < totalNodes; i++) + { + if (jjtGetChild(i).evaluate(context)) + { + jjtGetChild(i).render(context, writer); + break; + } + } + } + + if (morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE) + { + writer.write(postfix); + } + + /* + * This is reached without rendering anything (other than potential suffix/prefix) when an ASTIfStatement + * consists of an if/elseif sequence where none of the nodes evaluate to true. + */ + + return true; + } + + /** + * @param context + * @param visitor + */ + public void process( InternalContextAdapter context, ParserVisitor visitor) + { + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java new file mode 100644 index 00000000..37f88b2b --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java @@ -0,0 +1,70 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTIncludeStatement extends SimpleNode +{ + /** + * @param id + */ + public ASTIncludeStatement(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTIncludeStatement(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java new file mode 100644 index 00000000..ff8576fe --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java @@ -0,0 +1,224 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.StringUtils; +import org.apache.velocity.util.introspection.VelMethod; + +/* + * 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. + */ + +/** + * This node is responsible for the bracket notation at the end of + * a reference, e.g., $foo[1] + */ + +public class ASTIndex extends SimpleNode +{ + private static final String methodName = "get"; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + /** + * @param i + */ + public ASTIndex(int i) + { + super(i); + } + + /** + * @param p + * @param i + */ + public ASTIndex(Parser p, int i) + { + super(p, i); + } + + /** + * @param context + * @param data + * @return data + * @throws TemplateInitException + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + super.init(context, data); + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + cleanupParserAndTokens(); + return data; + } + + private final static Object[] noParams = {}; + private final static Class<?>[] noTypes = {}; + + /** + * If argument is an Integer and negative, then return (o.size() - argument). + * Otherwise return the original argument. We use this to calculate the true + * index of a negative index e.g., $foo[-1]. If no size() method is found on the + * 'o' object, then we throw an VelocityException. + * @param argument + * @param o + * @param context Used to access the method cache. + * @param node ASTNode used for error reporting. + * @return found object + */ + public static Object adjMinusIndexArg(Object argument, Object o, + InternalContextAdapter context, SimpleNode node) + { + if (argument instanceof Integer && (Integer) argument < 0) + { + // The index value is a negative number, $foo[-1], so we want to actually + // Index [size - value], so try and call the size method. + VelMethod method = ClassUtils.getMethod("size", noParams, noTypes, + o, context, node, false); + if (method == null) + { + // The object doesn't have a size method, so there is no notion of "at the end" + throw new VelocityException( + "A 'size()' method required for negative value " + + (Integer) argument + " does not exist for class '" + + o.getClass().getName() + "' at " + StringUtils.formatFileString(node), + null, node.getRuntimeServices().getLogContext().getStackTrace()); + } + + Object size = null; + try + { + size = method.invoke(o, noParams); + } + catch (Exception e) + { + throw new VelocityException("Error trying to calls the 'size()' method on '" + + o.getClass().getName() + "' at " + StringUtils.formatFileString(node), e, + node.getRuntimeServices().getLogContext().getStackTrace()); + } + + int sizeint = 0; + try + { + sizeint = (Integer) size; + } + catch (ClassCastException e) + { + // If size() doesn't return an Integer we want to report a pretty error + throw new VelocityException("Method 'size()' on class '" + + o.getClass().getName() + "' returned '" + size.getClass().getName() + + "' when Integer was expected at " + StringUtils.formatFileString(node), + null, node.getRuntimeServices().getLogContext().getStackTrace()); + } + + argument = sizeint + (Integer) argument; + } + + // Nothing to do, return the original argument + return argument; + } + + /** + * @param o + * @param context + * @return object value + * @throws MethodInvocationException + */ + @Override + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + Object argument = jjtGetChild(0).value(context); + // If negative, turn -1 into size - 1 + argument = adjMinusIndexArg(argument, o, context, this); + Object [] params = {argument}; + Class<?>[] paramClasses = {argument == null ? null : argument.getClass()}; + + VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, + o, context, this, strictRef); + + if (method == null) return null; + + try + { + /* + * get the returned object. It may be null, and that is + * valid for something declared with a void return type. + * Since the caller is expecting something to be returned, + * as long as things are peachy, we can return an empty + * String so ASTReference() correctly figures out that + * all is well. + */ + Object obj = method.invoke(o, params); + + if (obj == null) + { + if( method.getReturnType() == Void.TYPE) + { + return ""; + } + } + + return obj; + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch( Exception e ) + { + String msg = "Error invoking method 'get(" + + (argument == null ? "null" : argument.getClass().getName()) + + ")' in " + o.getClass().getName() + + " at " + StringUtils.formatFileString(this); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java new file mode 100644 index 00000000..56e2c3ba --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java @@ -0,0 +1,122 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; + +import java.math.BigInteger; + +/** + * Handles integer numbers. The value will be either an Integer, a Long, or a BigInteger. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @since 1.5 + */ +public class ASTIntegerLiteral extends SimpleNode +{ + + // This may be of type Integer, Long or BigInteger + private Number value = null; + + /** + * @param id + */ + public ASTIntegerLiteral(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTIntegerLiteral(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + /* + * init the tree correctly + */ + + super.init( context, data ); + + /* + * Determine the size of the item and make it an Integer, Long, or BigInteger as appropriate. + */ + String str = getFirstToken().image; + try + { + value = Integer.valueOf( str ); + } + catch ( NumberFormatException E1 ) + { + try + { + value = Long.valueOf( str ); + } + catch ( NumberFormatException E2 ) + { + // if there's still an Exception it will propagate out + value = new BigInteger( str ); + } + } + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + { + return value; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || !MathUtils.isZero(value); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java new file mode 100644 index 00000000..5135ff92 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java @@ -0,0 +1,296 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.DuckType; +import org.apache.velocity.util.StringUtils; + +import java.util.AbstractList; +import java.util.Iterator; +import java.util.ListIterator; + +/** + * handles the range 'operator' [ n .. m ] + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + */ +public class ASTIntegerRange extends SimpleNode +{ + /** + * @param id + */ + public ASTIntegerRange(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTIntegerRange(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + public static class IntegerRange extends AbstractList<Integer> + { + public class RangeIterator implements ListIterator<Integer> + { + private int value; + + public RangeIterator() + { + value = left - delta; + } + + public RangeIterator(int startIndex) + { + value = left + (startIndex - 1) * delta; + } + + @Override + public Integer next() + { + value += delta; + return value; + } + + @Override + public boolean hasPrevious() + { + return value != left - delta; + } + + @Override + public Integer previous() + { + value -= delta; + return value; + } + + @Override + public int nextIndex() + { + return (value + delta - left) * delta; + } + + @Override + public int previousIndex() + { + return (value - delta - left) * delta; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException("integer range is read only"); + } + + @Override + public void set(Integer integer) + { + throw new UnsupportedOperationException("integer range is read only"); + } + + @Override + public void add(Integer integer) + { + throw new UnsupportedOperationException("integer range is read only"); + } + + @Override + public boolean hasNext() + { + return value != right; + } + } + + private int left; + private int right; + private int delta; + + public IntegerRange(int left, int right, int delta) + { + this.left = left; + this.right = right; + this.delta = delta; + } + + @Override + public Iterator<Integer> iterator() + { + return new RangeIterator(); + } + + @Override + public Integer get(int index) + { + int ret = left + delta * index; + if (delta > 0 && ret > right || delta < 0 && ret < right) + { + throw new IndexOutOfBoundsException(); + } + return ret; + } + + @Override + public int indexOf(Object o) + { + int v = DuckType.asNumber(o).intValue(); + v -= left; + v *= delta; + return v >= 0 && v < size() ? v : -1; + } + + @Override + public int lastIndexOf(Object o) + { + return indexOf(o); + } + + @Override + public ListIterator<Integer> listIterator() + { + return new RangeIterator(); + } + + @Override + public ListIterator<Integer> listIterator(int index) + { + return new RangeIterator(index); + } + + @Override + public int size() + { + return Math.abs(right - left) + 1; + } + } + + /** + * does the real work. Creates an Vector of Integers with the + * right value range + * + * @param context app context used if Left or Right of .. is a ref + * @return Object array of Integers + * @throws MethodInvocationException + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + /* + * get the two range ends + */ + + Object left = jjtGetChild(0).value( context ); + Object right = jjtGetChild(1).value( context ); + + /* + * if either is null, lets log and bail + */ + + if (left == null || right == null) + { + log.error((left == null ? "Left" : "Right") + + " side of range operator [n..m] has null value." + + " Operation not possible. " + + StringUtils.formatFileString(this)); + return null; + } + + /* + * if not a Number, try to convert + */ + + try + { + left = DuckType.asNumber(left); + } + catch (NumberFormatException nfe) {} + + try + { + right = DuckType.asNumber(right); + } + catch (NumberFormatException nfe) {} + + /* + * if still not a Number, nothing we can do + */ + + if ( !( left instanceof Number ) || !( right instanceof Number )) + { + log.error((!(left instanceof Number) ? "Left" : "Right") + + " side of range operator is not convertible to a Number. " + + StringUtils.formatFileString(this)); + return null; + } + + /* + * get the two integer values of the ends of the range + */ + + int l = ((Number) left).intValue() ; + int r = ((Number) right).intValue(); + + /* + * Determine whether the increment is positive or negative. + */ + + int delta = ( l >= r ) ? -1 : 1; + + /* + * Return the corresponding integer range + */ + + return new IntegerRange(l, r, delta); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java new file mode 100644 index 00000000..ff3c2e8e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java @@ -0,0 +1,51 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles arg1 <= arg2<br><br> + */ +public class ASTLENode extends ASTComparisonNode +{ + public ASTLENode(int id) + { + super(id); + } + + public ASTLENode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return "<="; + } + + @Override + public boolean numberTest(int compareResult) + { + return compareResult <= 0; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java new file mode 100644 index 00000000..a4a43465 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java @@ -0,0 +1,51 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles arg1 < arg2<br><br> + */ +public class ASTLTNode extends ASTComparisonNode +{ + public ASTLTNode(int id) + { + super(id); + } + + public ASTLTNode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return "<"; + } + + @Override + public boolean numberTest(int compareResult) + { + return compareResult == -1; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java new file mode 100644 index 00000000..60180460 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java @@ -0,0 +1,30 @@ +package org.apache.velocity.runtime.parser.node; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +public abstract class ASTLogicalOperator extends ASTBinaryOperator +{ + public ASTLogicalOperator(int id) + { + super(id); + } + + public ASTLogicalOperator(Parser p, int id) + { + super(p, id); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java new file mode 100644 index 00000000..c259b2fb --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java @@ -0,0 +1,112 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * AST Node for creating a map / dictionary. + * + * This class was originally generated from Parser.jjt. + * + * @version $Id$ + * @since 1.5 + */ +public class ASTMap extends SimpleNode +{ + /** + * @param id + */ + public ASTMap(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTMap(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + int size = jjtGetNumChildren(); + + Map<Object, Object> objectMap = new LinkedHashMap<>(); + + for (int i = 0; i < size; i += 2) + { + SimpleNode keyNode = (SimpleNode) jjtGetChild(i); + SimpleNode valueNode = (SimpleNode) jjtGetChild(i+1); + + Object key = (keyNode == null ? null : keyNode.value(context)); + Object value = (valueNode == null ? null : valueNode.value(context)); + + objectMap.put(key, value); + } + + return objectMap; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || children != null && children.length > 0; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java new file mode 100755 index 00000000..e298bc0f --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java @@ -0,0 +1,165 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MathException; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.DuckType; + +/** + * Helps handle math<br><br> + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author Nathan Bubna + * @version $Id: ASTMathNode.java 517553 2007-03-13 06:09:58Z wglass $ + */ +public abstract class ASTMathNode extends ASTBinaryOperator +{ + protected boolean strictMode = false; + + public ASTMathNode(int id) + { + super(id); + } + + public ASTMathNode(Parser p, int id) + { + super(p, id); + } + + /** + * {@inheritDoc} + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + super.init(context, data); + strictMode = rsvc.getBoolean(RuntimeConstants.STRICT_MATH, false); + cleanupParserAndTokens(); + return data; + } + + /** + * {@inheritDoc} + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * gets the two args and performs the operation on them + * + * @param context + * @return result or null + * @throws MethodInvocationException + */ + @Override + public Object value(InternalContextAdapter context) throws MethodInvocationException + { + Object left = jjtGetChild(0).value(context); + Object right = jjtGetChild(1).value(context); + + /* + * should we do anything special here? + */ + Object special = handleSpecial(left, right, context); + if (special != null) + { + return special; + } + + // coerce to Number type, if possible + try + { + left = DuckType.asNumber(left); + } + catch (NumberFormatException nfe) {} + try + { + right = DuckType.asNumber(right); + } + catch (NumberFormatException nfe) {} + + /* + * if not a Number, not much we can do + */ + if (!(left instanceof Number) || !(right instanceof Number)) + { + boolean wrongright = (left instanceof Number); + boolean wrongtype = wrongright ? right != null : left != null; + String msg = (wrongright ? "Right" : "Left") + + " side of math operation (" + + jjtGetChild(wrongright ? 1 : 0).literal() + ") " + + (wrongtype ? "is not a Number. " : "has a null value. ") + + getLocation(context); + if (strictMode) + { + log.error(msg); + throw new MathException(msg, rsvc.getLogContext().getStackTrace()); + } + else + { + log.debug(msg); + return null; + } + } + + return perform((Number)left, (Number)right, context); + } + + /** + * Extension hook to allow special behavior by subclasses + * If this method returns a non-null value, that is returned, + * rather than the result of the math operation. + * @param left + * @param right + * @param context + * @return special value + * @see ASTAddNode#handleSpecial + */ + protected Object handleSpecial(Object left, Object right, InternalContextAdapter context) + { + // do nothing, this is an extension hook + return null; + } + + /** + * Performs the math operation represented by this node. + * @param left + * @param right + * @param context + * @return computed value + * @see ASTAddNode#perform(Number, Number, InternalContextAdapter) + */ + public abstract Number perform(Number left, Number right, InternalContextAdapter context); + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java new file mode 100644 index 00000000..5696b6bb --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java @@ -0,0 +1,459 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.directive.StopCommand; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.IntrospectionCacheData; +import org.apache.velocity.util.introspection.VelMethod; + +import java.lang.reflect.InvocationTargetException; + +/** + * ASTMethod.java + * + * Method support for references : $foo.method() + * + * NOTE : + * + * introspection is now done at render time. + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTMethod extends SimpleNode +{ + /** + * An empty immutable <code>Class</code> array. + */ + private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0]; + + private String methodName = ""; + private int paramCount = 0; + private boolean logOnInvalid = true; + + protected Info uberInfo; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + /** + * @param id + */ + public ASTMethod(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTMethod(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * simple init - init our subtree and get what we can from + * the AST + * @param context + * @param data + * @return The init result + * @throws TemplateInitException + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + super.init( context, data ); + + /* + * make an uberinfo - saves new's later on + */ + + uberInfo = new Info(getTemplateName(), + getLine(),getColumn()); + /* + * this is about all we can do + */ + + methodName = getFirstToken().image; + paramCount = jjtGetNumChildren() - 1; + + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + logOnInvalid = rsvc.getBoolean(RuntimeConstants.RUNTIME_LOG_METHOD_CALL_LOG_INVALID, true); + + cleanupParserAndTokens(); + + return data; + } + + /** + * invokes the method. Returns null if a problem, the + * actual return if the method returns something, or + * an empty string "" if the method returns void + * @param o + * @param context + * @return Result or null. + * @throws MethodInvocationException + */ + @Override + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + /* + * new strategy (strategery!) for introspection. Since we want + * to be thread- as well as context-safe, we *must* do it now, + * at execution time. There can be no in-node caching, + * but if we are careful, we can do it in the context. + */ + Object [] params = new Object[paramCount]; + + /* + * sadly, we do need recalc the values of the args, as this can + * change from visit to visit + */ + final Class<?>[] paramClasses = + paramCount > 0 ? new Class[paramCount] : EMPTY_CLASS_ARRAY; + + for (int j = 0; j < paramCount; j++) + { + params[j] = jjtGetChild(j + 1).value(context); + if (params[j] != null) + { + paramClasses[j] = params[j].getClass(); + } + } + + VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, + o, context, this, strictRef); + + // warn if method wasn't found (if strictRef is true, then ClassUtils did throw an exception) + if (o != null && method == null && logOnInvalid) + { + StringBuilder plist = new StringBuilder(); + for (int i = 0; i < params.length; i++) + { + Class<?> param = paramClasses[i]; + plist.append(param == null ? "null" : param.getName()); + if (i < params.length - 1) + plist.append(", "); + } + log.debug("Object '{}' does not contain method {}({}) (or several ambiguous methods) at {}[line {}, column {}]", o.getClass().getName(), methodName, plist, getTemplateName(), getLine(), getColumn()); + } + + /* + * The parent class (typically ASTReference) uses the icache entry + * under 'this' key to distinguish a valid null result from a non-existent method. + * So update this dummy cache value if necessary. + */ + IntrospectionCacheData prevICD = context.icacheGet(this); + if (method == null) + { + if (prevICD != null) + { + context.icachePut(this, null); + } + return null; + } + else if (prevICD == null) + { + context.icachePut(this, new IntrospectionCacheData()); // no need to fill in its members + } + + try + { + /* + * get the returned object. It may be null, and that is + * valid for something declared with a void return type. + * Since the caller is expecting something to be returned, + * as long as things are peachy, we can return an empty + * String so ASTReference() correctly figures out that + * all is well. + */ + + Object obj = method.invoke(o, params); + + if (obj == null) + { + if( method.getReturnType() == Void.TYPE) + { + return ""; + } + } + + return obj; + } + catch( InvocationTargetException ite ) + { + return handleInvocationException(o, context, ite.getTargetException()); + } + + /* Can also be thrown by method invocation */ + catch( IllegalArgumentException t ) + { + return handleInvocationException(o, context, t); + } + + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch( Exception e ) + { + String msg = "ASTMethod.execute() : exception invoking method '" + + methodName + "' in " + o.getClass(); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + private Object handleInvocationException(Object o, InternalContextAdapter context, Throwable t) + { + /* + * Errors should not be wrapped + */ + if (t instanceof Error) + { + throw (Error)t; + } + /* + * We let StopCommands go up to the directive they are for/from + */ + else if (t instanceof StopCommand) + { + throw (StopCommand)t; + } + + /* + * In the event that the invocation of the method + * itself throws an exception, we want to catch that + * wrap it, and throw. We don't log here as we want to figure + * out which reference threw the exception, so do that + * above + */ + else if (t instanceof Exception) + { + try + { + return EventHandlerUtil.methodException( rsvc, context, o.getClass(), methodName, (Exception) t, uberInfo ); + } + + /* + * If the event handler throws an exception, then wrap it + * in a MethodInvocationException. Don't pass through RuntimeExceptions like other + * similar catchall code blocks. + */ + catch( Exception e ) + { + throw new MethodInvocationException( + "Invocation of method '" + + methodName + "' in " + o.getClass() + + " threw exception " + + e.toString(), + e, rsvc.getLogContext().getStackTrace(), methodName, getTemplateName(), this.getLine(), this.getColumn()); + } + } + + /* + * let non-Exception Throwables go... + */ + else + { + /* + * no event cartridge to override. Just throw + */ + + throw new MethodInvocationException( + "Invocation of method '" + + methodName + "' in " + o.getClass() + + " threw exception " + + t.toString(), + t, rsvc.getLogContext().getStackTrace(), methodName, getTemplateName(), this.getLine(), this.getColumn()); + } + } + + /** + * Internal class used as key for method cache. Combines + * ASTMethod fields with array of parameter classes. Has + * public access (and complete constructor) for unit test + * purposes. + * @since 1.5 + */ + public static class MethodCacheKey + { + /** + * method name + */ + private final String methodName; + + /** + * parameters classes + */ + private final Class<?>[] params; + + /** + * whether the target object is of Class type + * (meaning we're searching either for methods + * of Class, or for static methods of the class + * this Class objects refers to) + * @since 2.2 + */ + private boolean classObject; + + public MethodCacheKey(String methodName, Class<?>[] params, boolean classObject) + { + /* + * Should never be initialized with nulls, but to be safe we refuse + * to accept them. + */ + this.methodName = (methodName != null) ? methodName : StringUtils.EMPTY; + this.params = (params != null) ? params : EMPTY_CLASS_ARRAY; + this.classObject = classObject; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) + { + /* + * note we skip the null test for methodName and params + * due to the earlier test in the constructor + */ + if (o instanceof MethodCacheKey) + { + final MethodCacheKey other = (MethodCacheKey) o; + if (params.length == other.params.length && + methodName.equals(other.methodName) && + classObject == other.classObject) + { + for (int i = 0; i < params.length; ++i) + { + if (params[i] == null) + { + if (params[i] != other.params[i]) + { + return false; + } + } + else if (!params[i].equals(other.params[i])) + { + return false; + } + } + return true; + } + } + return false; + } + + + /** + * @see java.lang.Object#hashCode() + */ + public int hashCode() + { + int result = 17; + + /* + * note we skip the null test for methodName and params + * due to the earlier test in the constructor + */ + for (Class<?> param : params) + { + if (param != null) + { + result = result * 37 + param.hashCode(); + } + } + + result = result * 37 + methodName.hashCode(); + + return result; + } + } + + /** + * @return Returns the methodName. + * @since 1.5 + */ + public String getMethodName() + { + return methodName; + } + + /** + * Returns the string ".<i>method_name</i>(...)". Arguments literals are not rendered. This method is only + * used for displaying the VTL stacktrace when a rendering error is encountered when runtime.log.track_location is true. + * @return + */ + @Override + public String literal() + { + if (literal != null) + { + return literal; + } + StringBuilder builder = new StringBuilder(); + builder.append('.').append(getMethodName()).append("(...)"); + + return literal = builder.toString(); + } + + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java new file mode 100644 index 00000000..d9de7911 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java @@ -0,0 +1,99 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MathException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles modulus division<br><br> + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTModNode extends ASTMathNode +{ + /** + * @param id + */ + public ASTModNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTModNode(Parser p, int id) + { + super(p, id); + } + + @Override + public Number perform(Number left, Number right, InternalContextAdapter context) + { + /* + * check for divide / modulo by 0 + */ + if (MathUtils.isZero(right)) + { + String msg = "Right side of modulus operation is zero. Must be non-zero. " + + getLocation(context); + if (strictMode) + { + log.error(msg); + throw new MathException(msg, rsvc.getLogContext().getStackTrace()); + } + else + { + log.debug(msg); + return null; + } + } + return MathUtils.modulo(left, right); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + @Override + public String getLiteralOperator() + { + return "%"; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java new file mode 100644 index 00000000..67bad83e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java @@ -0,0 +1,81 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles multiplication<br><br> + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTMulNode extends ASTMathNode +{ + /** + * @param id + */ + public ASTMulNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTMulNode(Parser p, int id) + { + super(p, id); + } + + @Override + public Number perform(Number left, Number right, InternalContextAdapter context) + { + return MathUtils.multiply(left, right); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + @Override + public String getLiteralOperator() + { + return "*"; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java new file mode 100644 index 00000000..3f78f5ab --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java @@ -0,0 +1,50 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles <code>arg1 != arg2</code> by negating evaluation of ASTEQNode + */ +public class ASTNENode extends ASTEQNode +{ + public ASTNENode(int id) + { + super(id); + } + + public ASTNENode(Parser p, int id) + { + super(p, id); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean evaluate(InternalContextAdapter context) throws MethodInvocationException + { + return !super.evaluate(context); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java new file mode 100644 index 00000000..842a2925 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java @@ -0,0 +1,94 @@ +package org.apache.velocity.runtime.parser.node; + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MathException; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.util.DuckType; + +public class ASTNegateNode extends SimpleNode +{ + protected boolean strictMode = false; + + public ASTNegateNode(int i) + { + super(i); + } + + public ASTNegateNode(Parser p, int i) + { + super(p, i); + } + + /** + * {@inheritDoc} + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + super.init(context, data); + /* save a literal image now (needed in case of error) */ + strictMode = rsvc.getBoolean(RuntimeConstants.STRICT_MATH, false); + cleanupParserAndTokens(); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + return jjtGetChild(0).evaluate(context); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + Object value = jjtGetChild(0).value( context ); + try + { + value = DuckType.asNumber(value); + } + catch (NumberFormatException nfe) {} + if (!(value instanceof Number)) + { + String msg = "Argument of unary negate (" + + jjtGetChild(0).literal() + + ") " + + (value == null ? "has a null value." : "is not a Number."); + if (strictMode) + { + log.error(msg); + throw new MathException(msg, rsvc.getLogContext().getStackTrace()); + } + else + { + log.debug(msg); + return null; + } + } + return MathUtils.negate((Number) value); + } + @Override + public String literal() + { + return "-" + jjtGetChild(0).literal(); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java new file mode 100644 index 00000000..310a3536 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java @@ -0,0 +1,96 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTNotNode extends SimpleNode +{ + /** + * @param id + */ + public ASTNotNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTNotNode(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + return !jjtGetChild(0).evaluate(context); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + return (jjtGetChild(0).evaluate( context ) ? Boolean.FALSE : Boolean.TRUE) ; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + @Override + public String literal() + { + return "!" + jjtGetChild(0).literal(); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java new file mode 100644 index 00000000..16d1b236 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java @@ -0,0 +1,103 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.Parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class ASTObjectArray extends SimpleNode +{ + /** + * @param id + */ + public ASTObjectArray(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTObjectArray(Parser p, int id) + { + super(p, id); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + int size = jjtGetNumChildren(); + + // since we know the amount of elements, initialize arraylist with proper size + List<Object> objectArray = new ArrayList<>(size); + + for (int i = 0; i < size; i++) + { + objectArray.add(jjtGetChild(i).value(context)); + } + + return objectArray; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return !rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || children != null && children.length > 0; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java new file mode 100644 index 00000000..c019e452 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java @@ -0,0 +1,115 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ +*/ +public class ASTOrNode extends ASTLogicalOperator +{ + /** + * @param id + */ + public ASTOrNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTOrNode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return "||"; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * Returns the value of the expression. + * Since the value of the expression is simply the boolean + * result of evaluate(), lets return that. + * @param context + * @return The Expression value. + * @throws MethodInvocationException + */ + @Override + public Object value(InternalContextAdapter context ) + throws MethodInvocationException + { + return evaluate(context) ? Boolean.TRUE : Boolean.FALSE; + } + + /** + * the logical or : + * <pre> + * left || null -> left + * null || right -> right + * null || null -> false + * left || right -> left || right + * </pre> + * @param context + * @return The evaluation result. + * @throws MethodInvocationException + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + Node left = jjtGetChild(0); + Node right = jjtGetChild(1); + + /* + * if the left is not null and true, then true + */ + + if (left != null && left.evaluate( context ) ) + return true; + + /* + * same for right + */ + + return right != null && right.evaluate(context); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java new file mode 100644 index 00000000..1c004286 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java @@ -0,0 +1,55 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; + + +/** + * + */ +public class ASTParameters extends SimpleNode +{ + /** + * @param id + */ + public ASTParameters(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTParameters(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java new file mode 100644 index 00000000..d4ddda9d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java @@ -0,0 +1,1165 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.io.Filter; +import org.apache.velocity.runtime.Renderable; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.directive.Block.Reference; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.DuckType; +import org.apache.velocity.util.StringUtils; +import org.apache.velocity.util.introspection.Info; +import org.apache.velocity.util.introspection.VelMethod; +import org.apache.velocity.util.introspection.VelPropertySet; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.util.Deque; + +/** + * This class is responsible for handling the references in + * VTL ($foo). + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a> + * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a> + * @version $Id$ +*/ +public class ASTReference extends SimpleNode +{ + /* Reference types */ + private static final int NORMAL_REFERENCE = 1; + private static final int FORMAL_REFERENCE = 2; + private static final int QUIET_REFERENCE = 3; + private static final int RUNT = 4; + + private int referenceType; + private String nullString; + private String alternateNullStringKey; + private String rootString; + private boolean escaped = false; + private boolean computableReference = true; + private boolean logOnNull = true; + private boolean lookupAlternateLiteral = false; + private String escPrefix = ""; + private String morePrefix = ""; + private String identifier = ""; + + private boolean checkEmpty; + + /** + * Indicates if we are running in strict reference mode. + */ + public boolean strictRef = false; + + /** + * non null Indicates if we are setting an index reference e.g, $foo[2], which basically + * means that the last syntax of the reference are brackets. + */ + private ASTIndex astIndex = null; + + /** + * non null Indicates that an alternate value has been provided + */ + private ASTExpression astAlternateValue = null; + + /** + * Indicates if we are using modified escape behavior in strict mode. + * mainly we allow \$abc -> to render as $abc + */ + public boolean strictEscape = false; + + private int numChildren = 0; + + /** + * Whether to trigger an event for invalid quiet references + * @since 2.2 + */ + private boolean warnInvalidQuietReferences = false; + + /** + * Whether to trigger an event for invalid null references, that is when a value + * is present in the context or parent object but is null + * @since 2.2 + */ + private boolean warnInvalidNullReferences = false; + + /** + * Whether to trigger an event for invalid tested references - as in #if($foo) + * @since 2.2 + */ + private boolean warnInvalidTestedReferences = false; + + protected Info uberInfo; + + /** + * @param id + */ + public ASTReference(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTReference(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + super.init(context, data); + + strictEscape = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, false); + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + lookupAlternateLiteral = rsvc.getBoolean(RuntimeConstants.VM_ENABLE_BC_MODE, false); + + /* + * the only thing we can do in init() is getRoot() + * as that is template based, not context based, + * so it's thread- and context-safe + */ + + rootString = rsvc.useStringInterning() ? getRoot().intern() : getRoot(); + if (lookupAlternateLiteral) + { + /* cache alternate null tring key */ + alternateNullStringKey = ".literal." + nullString; + } + + numChildren = jjtGetNumChildren(); + + // This is an expensive call, so get it now. + literal(); + + /* + * and if appropriate... + */ + if (numChildren > 0 ) + { + Node lastNode = jjtGetChild(numChildren-1); + if (lastNode instanceof ASTIndex) + { + /* + * only used in SetValue, where alternate value is forbidden + */ + astIndex = (ASTIndex) lastNode; + } + else if (lastNode instanceof ASTExpression) + { + astAlternateValue = (ASTExpression) lastNode; + --numChildren; + } + else + { + identifier = lastNode.getFirstTokenImage(); + } + } + + + /* + * make an uberinfo - saves new's later on + */ + uberInfo = new Info(getTemplateName(), getLine(),getColumn()); + + /* + * track whether we log invalid references + */ + logOnNull = + rsvc.getBoolean(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, true); + + /* + * whether to check for emptiness when evaluatingnumChildren + */ + checkEmpty = + rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true); + + /* invalid references special cases */ + + warnInvalidQuietReferences = + rsvc.getBoolean(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES_QUIET, false); + warnInvalidNullReferences = + rsvc.getBoolean(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES_NULL, false); + warnInvalidTestedReferences = + rsvc.getBoolean(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES_TESTED, false); + + + /* + * In the case we are referencing a variable with #if($foo) or + * #if( ! $foo) then we allow variables to be undefined and we + * set strictRef to false so that if the variable is undefined + * an exception is not thrown. + */ + if (strictRef && numChildren == 0) + { + logOnNull = false; // Strict mode allows nulls + + Node node = this.jjtGetParent(); + if (node instanceof ASTNotNode // #if( ! $foo) + || node instanceof ASTExpression // #if( $foo ) + || node instanceof ASTOrNode // #if( $foo || ... + || node instanceof ASTAndNode) // #if( $foo && ... + { + // Now scan up tree to see if we are in an If statement + while (node != null) + { + if (node instanceof ASTIfStatement) + { + strictRef = false; + break; + } + node = node.jjtGetParent(); + } + } + } + saveTokenImages(); + cleanupParserAndTokens(); + + return data; + } + + /** + * Returns the 'root string', the reference key + * @return the root string. + */ + public String getRootString() + { + return rootString; + } + + /** + * gets an Object that 'is' the value of the reference + * + * @param o Object parameter, unused per se, but non-null by convention inside an #if/#elseif evaluation + * @param context context used to generate value + * @return The execution result. + * @throws MethodInvocationException + */ + @Override + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + /* + * The only case where 'o' is not null is when this method is called by evaluate(). + * Its value is not used, but it is a convention meant to allow statements like + * #if($invalidReference) *not* to trigger an invalid reference event. + * Statements like #if($invalidReference.prop) should *still* trigger an invalid reference event. + * Statements like #if($validReference.invalidProp) should not. + */ + boolean onlyTestingReference = (o != null); + + if (referenceType == RUNT) + return null; + + /* + * get the root object from the context + */ + + Object result = getRootVariableValue(context); + + if (result == null && !strictRef) + { + /* + * do not trigger an invalid reference if the reference is present, but with a null value + * don't either for a quiet reference or inside an #if/#elseif evaluation context + */ + if ((referenceType != QUIET_REFERENCE || warnInvalidQuietReferences) && + (numChildren > 0 || + (!context.containsKey(rootString) || warnInvalidNullReferences) && + (!onlyTestingReference || warnInvalidTestedReferences))) + { + result = EventHandlerUtil.invalidGetMethod(rsvc, context, + rsvc.getParserConfiguration().getDollarChar() + rootString, null, null, uberInfo); + } + + if (astAlternateValue != null && (!DuckType.asBoolean(result, true))) + { + result = astAlternateValue.value(context); + } + + return result; + } + + /* + * Iteratively work 'down' (it's flat...) the reference + * to get the value, but check to make sure that + * every result along the path is valid. For example: + * + * $hashtable.Customer.Name + * + * The $hashtable may be valid, but there is no key + * 'Customer' in the hashtable so we want to stop + * when we find a null value and return the null + * so the error gets logged. + */ + + try + { + Object previousResult = result; + int failedChild = -1; + + for (int i = 0; i < numChildren; i++) + { + if (strictRef && result == null) + { + /* + * At this point we know that an attempt is about to be made + * to call a method or property on a null value. + */ + String name = jjtGetChild(i).getFirstTokenImage(); + throw new VelocityException("Attempted to access '" + + name + "' on a null value at " + + StringUtils.formatFileString(uberInfo.getTemplateName(), + + jjtGetChild(i).getLine(), jjtGetChild(i).getColumn()), + null, rsvc.getLogContext().getStackTrace()); + } + previousResult = result; + result = jjtGetChild(i).execute(result,context); + if (result == null && !strictRef) // If strict and null then well catch this + // next time through the loop + { + failedChild = i; + break; + } + } + + if (result == null) + { + if (failedChild == -1) + { + /* + * do not trigger an invalid reference if the reference is present, but with a null value + * don't either for a quiet reference, + * or inside an #if/#elseif evaluation context when there's no child + */ + if ((!context.containsKey(rootString) || warnInvalidNullReferences) && + (referenceType != QUIET_REFERENCE || warnInvalidQuietReferences) && + (!onlyTestingReference || warnInvalidTestedReferences || numChildren > 0)) + { + result = EventHandlerUtil.invalidGetMethod(rsvc, context, + rsvc.getParserConfiguration().getDollarChar() + rootString, previousResult, null, uberInfo); + } + } + else + { + Node child = jjtGetChild(failedChild); + // do not call bad reference handler if the getter is present + // (it means the getter has been called and returned null) + // do not either for a quiet reference or if the *last* child failed while testing the reference + Object getter = context.icacheGet(child); + if ((getter == null || warnInvalidNullReferences) && + (referenceType != QUIET_REFERENCE || warnInvalidQuietReferences) && + (!onlyTestingReference || warnInvalidTestedReferences || failedChild < numChildren - 1)) + { + StringBuilder name = new StringBuilder(String.valueOf(rsvc.getParserConfiguration().getDollarChar())).append(rootString); + for (int i = 0; i <= failedChild; i++) + { + Node node = jjtGetChild(i); + if (node instanceof ASTMethod) + { + name.append(".").append(((ASTMethod) node).getMethodName()).append("()"); + } + else + { + name.append(".").append(node.getFirstTokenImage()); + } + } + + if (child instanceof ASTMethod) + { + String methodName = ((ASTMethod) jjtGetChild(failedChild)).getMethodName(); + result = EventHandlerUtil.invalidMethod(rsvc, context, + name.toString(), previousResult, methodName, uberInfo); + } + else + { + String property = jjtGetChild(failedChild).getFirstTokenImage(); + result = EventHandlerUtil.invalidGetMethod(rsvc, context, + name.toString(), previousResult, property, uberInfo); + } + } + } + } + + // Check alternate value at the end of the evaluation + if (astAlternateValue != null && (!DuckType.asBoolean(result, true))) + { + result = astAlternateValue.value(context); + } + + return result; + } + catch(MethodInvocationException mie) + { + mie.setReferenceName(rootString); + throw mie; + } + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + /** + * gets the value of the reference and outputs it to the + * writer. + * + * @param context context of data to use in getting value + * @param writer writer to render to + * @return True if rendering was successful. + * @throws IOException + * @throws MethodInvocationException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) throws IOException, + MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + if (referenceType == RUNT) + { + writer.write(literal); + return true; + } + + Object value = null; + if (escaped && strictEscape) + { + /* + * If we are in strict mode and the variable is escaped, then don't bother to + * retrieve the value since we won't use it. And if the var is not defined + * it will throw an exception. Set value to TRUE to fall through below with + * simply printing $foo, and not \$foo + */ + value = Boolean.TRUE; + } + else + { + value = execute(null, context); + } + + String localNullString = null; + + /* + * if this reference is escaped (\$foo) then we want to do one of two things: 1) if this is + * a reference in the context, then we want to print $foo 2) if not, then \$foo (its + * considered schmoo, not VTL) + */ + + if (escaped) + { + localNullString = getNullString(context); + + if (value == null) + { + writer.write(escPrefix); + writer.write("\\"); + writer.write(localNullString); + } + else + { + writer.write(escPrefix); + writer.write(localNullString); + } + return true; + } + + /* + * the normal processing + * + * if we have an event cartridge, get a new value object + */ + + value = EventHandlerUtil.referenceInsert(rsvc, context, literal, value); + + String toString = null; + if (value != null) + { + if (value instanceof Renderable) + { + Renderable renderable = (Renderable)value; + try + { + writer.write(escPrefix); + writer.write(morePrefix); + if (renderable.render(context,writer)) + { + return true; + } + } + catch(RuntimeException e) + { + // We commonly get here when an error occurs within a block reference. + // We want to log where the reference is at so that a developer can easily + // know where the offending call is located. This can be seen + // as another element of the error stack we report to log. + log.error("Exception rendering " + + ((renderable instanceof Reference)? "block ":"Renderable ") + + rootString + " at " + StringUtils.formatFileString(this)); + throw e; + } + } + + toString = DuckType.asString(value); + } + + if (value == null || toString == null) + { + if (strictRef) + { + if (referenceType != QUIET_REFERENCE) + { + log.error("Prepend the reference with '$!' e.g., $!{}" + + " if you want Velocity to ignore the reference when it evaluates to null", + literal().substring(1)); + if (value == null) + { + throw new VelocityException("Reference " + literal() + + " evaluated to null when attempting to render at " + + StringUtils.formatFileString(this) + , null, rsvc.getLogContext().getStackTrace()); + } + else // toString == null + { + // This will probably rarely happen, but when it does we want to + // inform the user that toString == null so they don't pull there + // hair out wondering why Velocity thinks the value is null. + throw new VelocityException("Reference " + literal() + + " evaluated to object " + value.getClass().getName() + + " whose toString() method returned null at " + + StringUtils.formatFileString(this) + , null, rsvc.getLogContext().getStackTrace()); + } + } + return true; + } + + /* + * write prefix twice, because it's schmoo, so the \ don't escape each + * other... + */ + localNullString = getNullString(context); + if (!strictEscape) + { + // If in strict escape mode then we only print escape once. + // Yea, I know.. brittle stuff + writer.write(escPrefix); + } + writer.write(escPrefix); + writer.write(morePrefix); + writer.write(localNullString); + + if (logOnNull && referenceType != QUIET_REFERENCE) + { + log.debug("Null reference [template '{}', line {}, column {}]: {} cannot be resolved.", + getTemplateName(), this.getLine(), this.getColumn(), this.literal()); + } + } + else + { + /* + * non-null processing + */ + writer.write(escPrefix); + writer.write(morePrefix); + if (writer instanceof Filter) + { + ((Filter)writer).writeReference(toString); + } + else + { + writer.write(toString); + } + + } + return true; + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + /** + * This method helps to implement the "render literal if null" functionality. + * + * VelocimacroProxy saves references to macro arguments (AST nodes) so that if we have a macro + * #foobar($a $b) then there is key "$a.literal" which points to the literal presentation of the + * argument provided to variable $a. If the value of $a is null, we render the string that was + * provided as the argument. + * + * @param context + * @return + */ + private String getNullString(InternalContextAdapter context) + { + String ret = nullString; + + if (lookupAlternateLiteral) + { + Deque<String> alternateLiteralsStack = (Deque<String>)context.get(alternateNullStringKey); + if (alternateLiteralsStack != null && alternateLiteralsStack.size() > 0) + { + ret = alternateLiteralsStack.peekFirst(); + } + } + return ret; + } + + /** + * Computes boolean value of this reference + * Returns the actual value of reference return type + * boolean, and 'true' if value is not null + * + * @param context context to compute value with + * @return True if evaluation was ok. + * @throws MethodInvocationException + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + Object value = execute(this, context); // non-null object as first parameter by convention for 'evaluate' + if (value == null) + { + return false; + } + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + return DuckType.asBoolean(value, checkEmpty); + } + catch(Exception e) + { + throw new VelocityException("Reference evaluation threw an exception at " + + StringUtils.formatFileString(this), e, rsvc.getLogContext().getStackTrace()); + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + return (computableReference ? execute(null, context) : null); + } + + + /** + * Utility class to handle nulls when printing a class type + * @param clazz + * @return class name, or the string "null" + */ + public static String printClass(Class<?> clazz) + { + return clazz == null ? "null" : clazz.getName(); + } + + + /** + * Sets the value of a complex reference (something like $foo.bar) + * Currently used by ASTSetReference() + * + * @see ASTSetDirective + * + * @param context context object containing this reference + * @param value Object to set as value + * @return true if successful, false otherwise + * @throws MethodInvocationException + */ + public boolean setValue(InternalContextAdapter context, Object value) + throws MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + if (astAlternateValue != null) + { + log.error("reference set cannot have a default value {}", + StringUtils.formatFileString(uberInfo)); + return false; + } + + if (numChildren == 0) + { + context.put(rootString, value); + return true; + } + + /* + * The rootOfIntrospection is the object we will + * retrieve from the Context. This is the base + * object we will apply reflection to. + */ + + Object result = getRootVariableValue(context); + + if (result == null) + { + log.error("reference set is not a valid reference at {}", + StringUtils.formatFileString(uberInfo)); + return false; + } + + /* + * How many child nodes do we have? + */ + + for (int i = 0; i < numChildren - 1; i++) + { + result = jjtGetChild(i).execute(result, context); + + if (result == null) + { + if (strictRef) + { + String name = jjtGetChild(i+1).getFirstTokenImage(); + throw new MethodInvocationException("Attempted to access '" + + name + "' on a null value", null, rsvc.getLogContext().getStackTrace(), name, uberInfo.getTemplateName(), + jjtGetChild(i+1).getLine(), jjtGetChild(i+1).getColumn()); + } + + log.error("reference set is not a valid reference at {}", + StringUtils.formatFileString(uberInfo)); + return false; + } + } + + if (astIndex != null) + { + // If astIndex is not null then we are actually setting an index reference, + // something of the form $foo[1] =, or in general any reference that ends with + // the brackets. This means that we need to call a more general method + // of the form set(Integer, <something>), or put(Object, <something), where + // the first parameter is the index value and the second is the LHS of the set. + + Object argument = astIndex.jjtGetChild(0).value(context); + // If negative, turn -1 into (size - 1) + argument = ASTIndex.adjMinusIndexArg(argument, result, context, astIndex); + Object [] params = {argument, value}; + Class<?>[] paramClasses = {params[0] == null ? null : params[0].getClass(), + params[1] == null ? null : params[1].getClass()}; + + String methodName = "set"; + VelMethod method = ClassUtils.getMethod(methodName, params, paramClasses, + result, context, astIndex, false); + + if (method == null) + { + // If we can't find a 'set' method, lets try 'put', This warrents a little + // investigation performance wise... if the user is using the hash + // form $foo["blaa"], then it may be expensive to first try and fail on 'set' + // then go to 'put'? The problem is that getMethod will try the cache, then + // perform introspection on 'result' for 'set' + methodName = "put"; + method = ClassUtils.getMethod(methodName, params, paramClasses, + result, context, astIndex, false); + } + + if (method == null) + { + // couldn't find set or put method, so bail + if (strictRef) + { + throw new VelocityException( + "Found neither a 'set' or 'put' method with param types '(" + + printClass(paramClasses[0]) + "," + printClass(paramClasses[1]) + + ")' on class '" + result.getClass().getName() + + "' at " + StringUtils.formatFileString(astIndex) + , null, rsvc.getLogContext().getStackTrace()); + } + return false; + } + + try + { + method.invoke(result, params); + } + catch(RuntimeException e) + { + // Kludge since invoke throws Exception, pass up Runtimes + throw e; + } + catch(Exception e) + { + throw new MethodInvocationException( + "Exception calling method '" + + methodName + "(" + + printClass(paramClasses[0]) + "," + printClass(paramClasses[1]) + + ")' in " + result.getClass(), + e.getCause(), rsvc.getLogContext().getStackTrace(), identifier, astIndex.getTemplateName(), astIndex.getLine(), + astIndex.getColumn()); + } + + return true; + } + + + /* + * We support two ways of setting the value in a #set($ref.foo = $value ): + * 1) ref.setFoo( value ) + * 2) ref,put("foo", value ) to parallel the get() map introspection + */ + + try + { + VelPropertySet vs = + rsvc.getUberspect().getPropertySet(result, identifier, + value, uberInfo); + + if (vs == null) + { + if (strictRef) + { + throw new MethodInvocationException("Object '" + result.getClass().getName() + + "' does not contain property '" + identifier + "'", null, rsvc.getLogContext().getStackTrace(), identifier, + uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn()); + } + else + { + return false; + } + } + + vs.invoke(result, value); + } + catch(InvocationTargetException ite) + { + /* + * this is possible + */ + + throw new MethodInvocationException( + "ASTReference: Invocation of method '" + + identifier + "' in " + result.getClass() + + " threw exception " + + ite.getTargetException().toString(), + ite.getTargetException(), rsvc.getLogContext().getStackTrace(), identifier, getTemplateName(), this.getLine(), this.getColumn()); + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + /* + * maybe a security exception? + */ + String msg = "ASTReference setValue(): exception: " + e + + " template at " + StringUtils.formatFileString(uberInfo); + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + return true; + } + finally + { + rsvc.getLogContext().popLogContext(); + } + } + + private String getRoot() + { + Token t = getFirstToken(); + + /* + * we have a special case where something like + * $(\\)*!, where the user want's to see something + * like $!blargh in the output, but the ! prevents it from showing. + * I think that at this point, this isn't a reference. + */ + + /* so, see if we have "\\!" */ + + int slashbang = t.image.indexOf("\\!"); + + if (slashbang != -1) + { + if (strictEscape) + { + // If we are in strict escape mode, then we consider this type of + // pattern a non-reference, and we print it out as schmoo... + nullString = literal(); + escaped = true; + return nullString; + } + + /* + * lets do all the work here. I would argue that if this occurs, + * it's not a reference at all, so preceding \ characters in front + * of the $ are just schmoo. So we just do the escape processing + * trick (even | odd) and move on. This kind of breaks the rule + * pattern of $ and # but '!' really tosses a wrench into things. + */ + + /* + * count the escapes: even # -> not escaped, odd -> escaped + */ + + int i = 0; + int len = t.image.length(); + + i = t.image.indexOf(rsvc.getParserConfiguration().getDollarChar()); + + if (i == -1) + { + /* yikes! */ + log.error("ASTReference.getRoot(): internal error: " + + "no $ found for slashbang."); + computableReference = false; + nullString = t.image; + return nullString; + } + + while (i < len && t.image.charAt(i) != '\\') + { + i++; + } + + /* ok, i is the first \ char */ + + int start = i; + int count = 0; + + while (i < len && t.image.charAt(i++) == '\\') + { + count++; + } + + /* + * now construct the output string. We really don't care about + * leading slashes as this is not a reference. It's quasi-schmoo + */ + + nullString = t.image.substring(0,start); // prefix up to the first + nullString += t.image.substring(start, start + count-1 ); // get the slashes + nullString += t.image.substring(start+count); // and the rest, including the + + /* + * this isn't a valid reference, so lets short circuit the value + * and set calcs + */ + + computableReference = false; + + return nullString; + } + + /* + * we need to see if this reference is escaped. if so + * we will clean off the leading \'s and let the + * regular behavior determine if we should output this + * as \$foo or $foo later on in render(). Laziness.. + */ + + escaped = false; + + if (t.image.startsWith("\\")) + { + /* + * count the escapes: even # -> not escaped, odd -> escaped + */ + + int i = 0; + int len = t.image.length(); + + while (i < len && t.image.charAt(i) == '\\') + { + i++; + } + + if ((i % 2) != 0) + escaped = true; + + if (i > 0) + escPrefix = t.image.substring(0, i / 2 ); + + t.image = t.image.substring(i); + } + + /* + * Look for preceding stuff like '#' and '$' + * and snip it off, except for the + * last $ + */ + + int loc1 = t.image.lastIndexOf(rsvc.getParserConfiguration().getDollarChar()); + + /* + * if we have extra stuff, loc > 0 + * ex. '#$foo' so attach that to + * the prefix. + */ + if (loc1 > 0) + { + morePrefix = morePrefix + t.image.substring(0, loc1); + t.image = t.image.substring(loc1); + } + + /* + * Now it should be clean. Get the literal in case this reference + * isn't backed by the context at runtime, and then figure out what + * we are working with. + */ + + // FIXME: this is the key to render nulls as literals, we need to look at context(refname+".literal") + nullString = literal(); + + if (t.image.startsWith("$!")) + { + referenceType = QUIET_REFERENCE; + + /* + * only if we aren't escaped do we want to null the output + */ + + if (!escaped) + nullString = ""; + + if (t.image.startsWith("$!{")) + { + /* + * ex: $!{provider.Title} + */ + + return t.next.image; + } + else + { + /* + * ex: $!provider.Title + */ + + return t.image.substring(2); + } + } + else if (t.image.equals("${")) + { + /* + * ex: ${provider.Title} + */ + + referenceType = FORMAL_REFERENCE; + return t.next.image; + } + else if (t.image.startsWith("$")) + { + /* + * just nip off the '$' so we have + * the root + */ + + referenceType = NORMAL_REFERENCE; + return t.image.substring(1); + } + else + { + /* + * this is a 'RUNT', which can happen in certain circumstances where + * the parser is fooled into believing that an IDENTIFIER is a real + * reference. Another 'dreaded' MORE hack :). + */ + referenceType = RUNT; + return t.image; + } + + } + + /** + * @param context + * @return The evaluated value of the variable. + * @throws MethodInvocationException + */ + public Object getRootVariableValue(InternalContextAdapter context) + { + Object obj = null; + try + { + obj = context.get(rootString); + } + catch(RuntimeException e) + { + log.error("Exception calling reference ${} at {}", + rootString, StringUtils.formatFileString(uberInfo)); + throw e; + } + + if (obj == null && strictRef && astAlternateValue == null) + { + if (!context.containsKey(rootString)) + { + log.error("Variable ${} has not been set at {}", + rootString, StringUtils.formatFileString(uberInfo)); + throw new MethodInvocationException("Variable $" + rootString + + " has not been set", null, rsvc.getLogContext().getStackTrace(), identifier, + uberInfo.getTemplateName(), uberInfo.getLine(), uberInfo.getColumn()); + } + } + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java new file mode 100644 index 00000000..0cf4d566 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java @@ -0,0 +1,304 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.app.event.EventHandlerUtil; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.introspection.Info; + +import java.io.IOException; +import java.io.Writer; + +/** + * Node for the #set directive + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTSetDirective extends SimpleNode +{ + private String leftReference = ""; + private Node right = null; + private ASTReference left = null; + private boolean isInitialized; + private String prefix = ""; + private String postfix = ""; + + /* + * '#' and '$' prefix characters eaten by javacc MORE mode + */ + private String morePrefix = ""; + + + /** + * This is really immutable after the init, so keep one for this node + */ + protected Info uberInfo; + + /** + * Indicates if we are running in strict reference mode. + */ + protected boolean strictRef = false; + + /** + * @param id + */ + public ASTSetDirective(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTSetDirective(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * simple init. We can get the RHS and LHS as the the tree structure is static + * @param context + * @param data + * @return Init result. + * @throws TemplateInitException + */ + @Override + public synchronized Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + /* This method is synchronized to prevent double initialization or initialization while rendering */ + + if (!isInitialized) + { + /* + * init the tree correctly + */ + + super.init( context, data ); + + /* + * handle '$' and '#' chars prefix + */ + Token t = getFirstToken(); + int pos = -1; + while (t != null && (pos = t.image.lastIndexOf(rsvc.getParserConfiguration().getHashChar())) == -1) + { + t = t.next; + } + if (t != null && pos > 0) + { + morePrefix = t.image.substring(0, pos); + } + + + uberInfo = new Info(getTemplateName(), + getLine(), getColumn()); + + right = getRightHandSide(); + left = getLeftHandSide(); + + strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false); + + /* + * grab this now. No need to redo each time + */ + leftReference = left.firstImage.substring(1); + + /* handle backward compatible space gobbling if asked so */ + if (rsvc.getSpaceGobbling() == SpaceGobbling.BC) + { + Node previousNode = null; + for (int brother = 0; brother < parent.jjtGetNumChildren(); ++brother) + { + Node node = parent.jjtGetChild(brother); + if (node == this) break; + previousNode = node; + } + if (previousNode == null) prefix = ""; + else if (previousNode instanceof ASTText) + { + ASTText text = (ASTText)previousNode; + if (text.getCtext().matches("[ \t]*")) + { + text.setCtext(""); + } + } + else prefix = ""; + } + + isInitialized = true; + + cleanupParserAndTokens(); + } + + return data; + } + + /** + * set indentation prefix + * @param prefix + */ + public void setPrefix(String prefix) + { + this.prefix = prefix; + } + + /** + * get indentation prefix + * @return indentation prefix + */ + public String getPrefix() + { + return prefix; + } + + /** + * set indentation postfix + * @param postfix + */ + public void setPostfix(String postfix) + { + this.postfix = postfix; + } + + /** + * get indentation postfix + * @return indentation prefix + */ + public String getPostfix() + { + return postfix; + } + + /** + * puts the value of the RHS into the context under the key of the LHS + * @param context + * @param writer + * @return True if rendering was sucessful. + * @throws IOException + * @throws MethodInvocationException + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException, MethodInvocationException + { + try + { + rsvc.getLogContext().pushLogContext(this, uberInfo); + + SpaceGobbling spaceGobbling = rsvc.getSpaceGobbling(); + + /* Velocity 1.x space gobbling for #set is rather wacky: + prefix is eaten *only* if previous token is not a text node. + We handle this by appropriately emptying the prefix in BC mode. + */ + + if (morePrefix.length() > 0 || spaceGobbling.compareTo(SpaceGobbling.LINES) < 0) + { + writer.write(prefix); + } + + writer.write(morePrefix); + + /* + * get the RHS node, and its value + */ + + Object value = right.value(context); + + if ( value == null && !strictRef) + { + String rightReference = null; + if (right instanceof ASTExpression) + { + rightReference = ((ASTExpression) right).lastImage; + } + EventHandlerUtil.invalidSetMethod(rsvc, context, leftReference, rightReference, uberInfo); + } + + if (morePrefix.length() > 0 || spaceGobbling == SpaceGobbling.NONE) + { + writer.write(postfix); + } + + return left.setValue(context, value); + } + finally + { + rsvc.getLogContext().popLogContext(); + StringBuilder builder; + } + } + + /** + * Returns the string "#set($<i>reference</i> = ...)". RHS is not rendered. This method is only + * used for displaying the VTL stacktrace when a rendering error is encountered when runtime.log.track_location is true. + * @return + */ + @Override + public String literal() + { + if (literal != null) + { + return literal; + } + StringBuilder builder = new StringBuilder(); + builder.append("#set(").append(left.literal()).append(" = ...)"); + return literal = builder.toString(); + } + + /** + * returns the ASTReference that is the LHS of the set statement + * + * @return left hand side of #set statement + */ + private ASTReference getLeftHandSide() + { + return (ASTReference) jjtGetChild(0); + } + + /** + * returns the RHS Node of the set statement + * + * @return right hand side of #set statement + */ + private Node getRightHandSide() + { + return jjtGetChild(1); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java new file mode 100644 index 00000000..44134727 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java @@ -0,0 +1,364 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.parser.ParseException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.StringBuilderWriter; +import org.apache.velocity.util.StringUtils; + +import java.io.IOException; +import java.io.StringReader; +import java.io.Writer; + +/** + * ASTStringLiteral support. Will interpolate! + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @version $Id$ + */ +public class ASTStringLiteral extends SimpleNode +{ + /* cache the value of the interpolation switch */ + private boolean interpolate = true; + + private SimpleNode nodeTree = null; + + private String image = ""; + + /** + * @param id + */ + public ASTStringLiteral(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTStringLiteral(Parser p, int id) + { + super(p, id); + } + + /** + * init : we don't have to do much. Init the tree (there shouldn't be one) + * and then see if interpolation is turned on. + * + * @param context + * @param data + * @return Init result. + * @throws TemplateInitException + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + /* + * simple habit... we prollie don't have an AST beneath us + */ + + super.init(context, data); + + /* + * the stringlit is set at template parse time, so we can do this here + * for now. if things change and we can somehow create stringlits at + * runtime, this must move to the runtime execution path + * + * so, only if interpolation is turned on AND it starts with a " AND it + * has a directive or reference, then we can interpolate. Otherwise, + * don't bother. + */ + + interpolate = rsvc.getBoolean( + RuntimeConstants.INTERPOLATE_STRINGLITERALS, true) + && getFirstToken().image.startsWith("\"") + && ((getFirstToken().image.indexOf(rsvc.getParserConfiguration().getDollarChar()) != -1) || (getFirstToken().image + .indexOf(rsvc.getParserConfiguration().getHashChar()) != -1)); + + /* + * get the contents of the string, minus the '/" at each end + */ + String img = getFirstToken().image; + + /* + the literal string *should* contain enclosing quotes + */ + literal = img; + + image = img.substring(1, img.length() - 1); + + if (img.startsWith("\"")) + { + image = unescape(image); + } + if (img.charAt(0) == '"' || img.charAt(0) == '\'' ) + { + // replace double-double quotes like "" with a single double quote " + // replace double single quotes '' with a single quote ' + image = replaceQuotes(image, img.charAt(0)); + } + + if (interpolate) + { + /* + * parse and init the nodeTree + */ + StringReader br = new StringReader(image); + + /* + * it's possible to not have an initialization context - or we don't + * want to trust the caller - so have a fallback value if so + * + * Also, do *not* dump the VM namespace for this template + */ + + Template template = null; + if (context != null) + { + template = (Template)context.getCurrentResource(); + } + if (template == null) + { + template = new Template(); + template.setName("StringLiteral"); + } + try + { + nodeTree = rsvc.parse(br, template); + } + catch (ParseException e) + { + String msg = "Failed to parse String literal at "+ + StringUtils.formatFileString(template.getName(), getLine(), getColumn()); + throw new TemplateInitException(msg, e, rsvc.getLogContext().getStackTrace(), template.getName(), getColumn(), getLine()); + } + + adjTokenLineNums(nodeTree); + + /* + * init with context. It won't modify anything + */ + + nodeTree.init(context, rsvc); + } + + cleanupParserAndTokens(); + + return data; + } + + /** + * Adjust all the line and column numbers that comprise a node so that they + * are corrected for the string literals position within the template file. + * This is necessary if an exception is thrown while processing the node so + * that the line and column position reported reflects the error position + * within the template and not just relative to the error position within + * the string literal. + * @param node + */ + public void adjTokenLineNums(Node node) + { + Token tok = node.getFirstToken(); + // Test against null is probably not necessary, but just being safe + while(tok != null && tok != node.getLastToken()) + { + // If tok is on the first line, then the actual column is + // offset by the template column. + + if (tok.beginLine == 1) + tok.beginColumn += getColumn(); + + if (tok.endLine == 1) + tok.endColumn += getColumn(); + + tok.beginLine += getLine()- 1; + tok.endLine += getLine() - 1; + tok = tok.next; + } + } + + /** + * Replaces double double-quotes with a single double quote ("" to "). + * Replaces double single quotes with a single quote ('' to '). + * + * @param s StringLiteral without the surrounding quotes + * @param literalQuoteChar char that starts the StringLiteral (" or ') + */ + private String replaceQuotes(String s, char literalQuoteChar) + { + if( (literalQuoteChar == '"' && !s.contains("\"")) || + (literalQuoteChar == '\'' && !s.contains("'")) ) + { + return s; + } + + StringBuilder result = new StringBuilder(s.length()); + for(int i = 0, is = s.length(); i < is; i++) + { + char c = s.charAt(i); + result.append(c); + + if( i + 1 < is ) + { + char next = s.charAt(i + 1); + // '""' -> "", "''" -> '' + // thus it is not necessary to double quotes if the "surrounding" quotes + // of the StringLiteral are different. See VELOCITY-785 + if( (literalQuoteChar == '"' && (next == '"' && c == '"')) || + (literalQuoteChar == '\'' && (next == '\'' && c == '\'')) ) + { + i++; + } + } + } + return result.toString(); + } + + /** + * @param string + * @return unescaped string + * @since 1.6 + */ + public static String unescape(final String string) + { + int u = string.indexOf("\\u"); + if (u < 0) return string; + + StringBuilder result = new StringBuilder(); + + int lastCopied = 0; + + for (;;) + { + result.append(string.substring(lastCopied, u)); + + /* we don't worry about an exception here, + * because the lexer checked that string is correct */ + char c = (char) Integer.parseInt(string.substring(u + 2, u + 6), 16); + result.append(c); + + lastCopied = u + 6; + + u = string.indexOf("\\u", lastCopied); + if (u < 0) + { + result.append(string.substring(lastCopied)); + return result.toString(); + } + } + } + + + /** + * @param visitor + * @param data + * @return rendered object + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, + * java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * Check to see if this is an interpolated string. + * @return true if this is constant (not an interpolated string) + * @since 1.6 + */ + public boolean isConstant() + { + return !interpolate; + } + + /** + * renders the value of the string literal If the properties allow, and the + * string literal contains a $ or a # the literal is rendered against the + * context Otherwise, the stringlit is returned. + * + * @param context + * @return result of the rendering. + */ + @Override + public Object value(InternalContextAdapter context) + { + if (interpolate) + { + try + { + /* + * now render against the real context + */ + + Writer writer = new StringBuilderWriter(); + nodeTree.render(context, writer); + + /* + * and return the result as a String + */ + + return writer.toString(); + } + + /* + * pass through application level runtime exceptions + */ + catch (RuntimeException e) + { + throw e; + } + + catch (IOException e) + { + String msg = "Error in interpolating string literal"; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + + } + + /* + * ok, either not allowed to interpolate, there wasn't a ref or + * directive, or we failed, so just output the literal + */ + + return image; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + String str = (String)value(context); + return str != null && (!rsvc.getBoolean(RuntimeConstants.CHECK_EMPTY_OBJECTS, true) || !str.isEmpty()); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java new file mode 100644 index 00000000..25642c13 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java @@ -0,0 +1,67 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.runtime.parser.Parser; + +/** + * Handles subtraction of nodes (in #set() )<br><br> + * + * Please look at the Parser.jjt file which is + * what controls the generation of this class. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ASTSubtractNode extends ASTMathNode +{ + /** + * @param id + */ + public ASTSubtractNode(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTSubtractNode(Parser p, int id) + { + super(p, id); + } + + @Override + public String getLiteralOperator() + { + return "-"; + } + + @Override + public Number perform(Number left, Number right, InternalContextAdapter context) + { + return MathUtils.subtract(left, right); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java new file mode 100644 index 00000000..0f4be56f --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java @@ -0,0 +1,122 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; + +import java.io.IOException; +import java.io.Writer; + +/** + * + */ +public class ASTText extends SimpleNode +{ + private String ctext; + + /** + * @param id + */ + public ASTText(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTText(Parser p, int id) + { + super(p, id); + } + + /** + * text getter + * @return ctext + */ + public String getCtext() + { + return ctext; + } + + /** + * text setter + * @param ctext + */ + public void setCtext(String ctext) + { + this.ctext = ctext; + } + + /** + * text getter + * @return literal representation + */ + @Override + public String literal() + { + return ctext; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + StringBuilder builder = new StringBuilder(); + Token t = getFirstToken(); + for (; t != getLastToken(); t = t.next) + { + builder.append(NodeUtils.tokenLiteral(parser, t)); + } + builder.append(NodeUtils.tokenLiteral(parser, t)); + ctext = builder.toString(); + + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException + { + writer.write(ctext); + return true; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java new file mode 100644 index 00000000..d133b292 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java @@ -0,0 +1,94 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; + +import java.io.IOException; +import java.io.Writer; + +/** + * This node holds the "Textblock" data which should not be interpreted by Velocity. + * + * Textblocks are marked in Velocity with #[[content here]]# notation. Velocity + * will output everything between the markers and does not attempt to parse it in any way. + */ +public class ASTTextblock extends SimpleNode +{ + public final String START; + public final String END; + private char[] ctext; + + /** + * @param p + * @param id + */ + public ASTTextblock(Parser p, int id) + { + super(p, id); + START = parser.hash() + "[["; + END = "]]" + parser.hash(); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) + throws TemplateInitException + { + Token t = getFirstToken(); + + String text = t.image; + + // t.image is in format: #[[ <string> ]]# + // we must strip away the hash tags + text = text.substring(START.length(), text.length() - END.length()); + + ctext = text.toCharArray(); + + cleanupParserAndTokens(); + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException + { + writer.write(ctext); + return true; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java new file mode 100644 index 00000000..7763ed60 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java @@ -0,0 +1,89 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTTrue extends SimpleNode +{ + private static Boolean value = Boolean.TRUE; + + /** + * @param id + */ + public ASTTrue(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTTrue(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + { + return true; + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + { + return value; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java new file mode 100644 index 00000000..6b104667 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java @@ -0,0 +1,70 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + + +/** + * + */ +public class ASTVariable extends SimpleNode +{ + /** + * @param id + */ + public ASTVariable(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTVariable(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java new file mode 100644 index 00000000..9493babf --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java @@ -0,0 +1,69 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTWord extends SimpleNode +{ + /** + * @param id + */ + public ASTWord(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTWord(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + saveTokenImages(); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java new file mode 100644 index 00000000..e9c67039 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java @@ -0,0 +1,68 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.parser.Parser; + +/** + * + */ +public class ASTprocess extends SimpleNode +{ + /** + * @param id + */ + public ASTprocess(int id) + { + super(id); + } + + /** + * @param p + * @param id + */ + public ASTprocess(Parser p, int id) + { + super(p, id); + } + + /** + * @see org.apache.velocity.runtime.parser.node.SimpleNode#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + Object obj = super.init(context, data); + cleanupParserAndTokens(); // drop reference to Parser and all JavaCC Tokens + return obj; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java new file mode 100644 index 00000000..d29b6cfe --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java @@ -0,0 +1,83 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.slf4j.Logger; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Abstract class that is used to execute an arbitrary + * method that is in introspected. This is the superclass + * for the GetExecutor and PropertyExecutor. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public abstract class AbstractExecutor +{ + /** */ + protected Logger log = null; + + /** + * Method to be executed. + */ + private Method method = null; + + /** + * Execute method against context. + * @param o + * @return The resulting object. + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public abstract Object execute(Object o) + throws IllegalAccessException, InvocationTargetException; + + /** + * Tell whether the executor is alive by looking + * at the value of the method. + * + * @return True if executor is alive. + */ + public boolean isAlive() + { + return (method != null); + } + + /** + * @return The current method. + */ + public Method getMethod() + { + return method; + } + + /** + * @param method + * @since 1.5 + */ + protected void setMethod(final Method method) + { + this.method = method; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java new file mode 100644 index 00000000..1c834f8c --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java @@ -0,0 +1,123 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +/** + * Handles discovery and valuation of a + * boolean object property, of the + * form public boolean is<property> when executed. + * + * We do this separately as to preserve the current + * quasi-broken semantics of get<as is property> + * get<flip 1st char> get("property") and now followed + * by is<Property> + * + * @author <a href="geirm@apache.org">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class BooleanPropertyExecutor extends PropertyExecutor +{ + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @since 1.5 + */ + public BooleanPropertyExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property) + { + this(log, introspector, clazz, property, false); + } + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @param wrapArray + * @since 1.5 + */ + public BooleanPropertyExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property, final boolean wrapArray) + { + super(log, introspector, clazz, property, wrapArray); + } + + @Override + protected void discover(final Class<?> clazz, final String property) + { + try + { + Object [] params = {}; + + StringBuilder sb = new StringBuilder("is"); + sb.append(property); + + setMethod(getIntrospector().getMethod(clazz, sb.toString(), params)); + + if (!isAlive()) + { + /* + * now the convenience, flip the 1st character + */ + + char c = sb.charAt(2); + + if (Character.isLowerCase(c)) + { + sb.setCharAt(2, Character.toUpperCase(c)); + } + else + { + sb.setCharAt(2, Character.toLowerCase(c)); + } + + setMethod(getIntrospector().getMethod(clazz, sb.toString(), params)); + } + + if (isAlive()) + { + if( getMethod().getReturnType() != Boolean.TYPE && + getMethod().getReturnType() != Boolean.class ) + { + setMethod(null); + } + } + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for boolean property getter for '" + property; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java new file mode 100644 index 00000000..e08d2c69 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java @@ -0,0 +1,120 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +import java.lang.reflect.InvocationTargetException; + + +/** + * Executor that simply tries to execute a get(key) + * operation. This will try to find a get(key) method + * for any type of object, not just objects that + * implement the Map interface as was previously + * the case. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @version $Id$ + */ +public class GetExecutor extends AbstractExecutor +{ + private final Introspector introspector; + + // This is still threadsafe because this object is only read except in the C'tor. + private Object [] params = {}; + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @since 1.5 + */ + public GetExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property) + { + this.log = log; + this.introspector = introspector; + + // If you passed in null as property, we don't use the value + // for parameter lookup. Instead we just look for get() without + // any parameters. + // + // In any other case, the following condition will set up an array + // for looking up get(String) on the class. + + if (property != null) + { + this.params = new Object[] { property }; + } + discover(clazz); + } + + /** + * @param clazz + * @since 1.5 + */ + protected void discover(final Class<?> clazz) + { + try + { + setMethod(introspector.getMethod(clazz, "get", params)); + /* get(Number) or get(integral) are NOT admissible, + * as the key is a string + */ + if (getMethod() != null) + { + Class<?> [] args = getMethod().getParameterTypes(); + if (args.length == 1 && (args[0].isPrimitive() || Number.class.isAssignableFrom(args[0]))) + { + setMethod(null); + } + } + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for get('" + params[0] + "') method"; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * @param o + * @return value + * @see org.apache.velocity.runtime.parser.node.AbstractExecutor#execute(java.lang.Object) + */ + @Override + public Object execute(final Object o) + throws IllegalAccessException, InvocationTargetException + { + return isAlive() ? getMethod().invoke(o, params) : null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java new file mode 100644 index 00000000..907aaecb --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java @@ -0,0 +1,362 @@ +package org.apache.velocity.runtime.parser.node; + +import org.apache.velocity.runtime.directive.Directive; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Helper class to fix indentation in structured mode. + */ + +public class IndentationFixer implements ParserVisitor +{ + protected String parentIndentation = null; + protected String extraIndentation = null; + protected Pattern fix = null; + + protected void fillExtraIndentation(String prefix) + { + Pattern captureExtraIndentation = Pattern.compile("^" + parentIndentation + "(\\s+)"); + Matcher matcher = captureExtraIndentation.matcher(prefix); + if (matcher.find()) + { + extraIndentation = matcher.group(1); + fix = Pattern.compile("^" + parentIndentation + extraIndentation, Pattern.MULTILINE); + } + else + { + extraIndentation = ""; + } + } + + public IndentationFixer(String parentIndentation) + { + this.parentIndentation = parentIndentation; + } + + @Override + public Object visit(SimpleNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTprocess node, Object data) + { + return null; + } + + @Override + public Object visit(ASTText node, Object data) + { + String text = node.getCtext(); + if (extraIndentation == null) + { + fillExtraIndentation(text); + } + if (extraIndentation.length() > 0) + { + Matcher matcher = fix.matcher(text); + node.setCtext(matcher.replaceAll(parentIndentation)); + } + return null; + } + + @Override + public Object visit(ASTEscapedDirective node, Object data) + { + return null; + } + + @Override + public Object visit(ASTEscape node, Object data) + { + return null; + } + + @Override + public Object visit(ASTComment node, Object data) + { + return null; + } + + @Override + public Object visit(ASTTextblock node, Object data) + { + return null; + } + + @Override + public Object visit(ASTFloatingPointLiteral node, Object data) + { + return null; + } + + @Override + public Object visit(ASTIntegerLiteral node, Object data) + { + return null; + } + + @Override + public Object visit(ASTStringLiteral node, Object data) + { + return null; + } + + @Override + public Object visit(ASTIdentifier node, Object data) + { + return null; + } + + @Override + public Object visit(ASTWord node, Object data) + { + return null; + } + + @Override + public Object visit(ASTDirectiveAssign node, Object data) + { + return null; + } + + @Override + public Object visit(ASTDirective node, Object data) + { + String prefix = node.getPrefix(); + if (prefix.length() > 0) + { + if (extraIndentation == null) + { + fillExtraIndentation(prefix); + } + if (extraIndentation.length() > 0) + { + Matcher matcher = fix.matcher(prefix); + node.setPrefix(matcher.replaceAll(parentIndentation)); + if (node.getDirectiveType() == Directive.BLOCK) + { + node.childrenAccept(this, null); + } + } + } + return null; + } + + @Override + public Object visit(ASTBlock node, Object data) + { + String prefix = node.getPrefix(); + if (prefix.length() > 0) + { + node.childrenAccept(this, null); + } + return null; + } + + @Override + public Object visit(ASTMap node, Object data) + { + return null; + } + + @Override + public Object visit(ASTObjectArray node, Object data) + { + return null; + } + + @Override + public Object visit(ASTIntegerRange node, Object data) + { + return null; + } + + @Override + public Object visit(ASTMethod node, Object data) + { + return null; + } + + @Override + public Object visit(ASTIndex node, Object data) + { + return null; + } + + @Override + public Object visit(ASTReference node, Object data) + { + return null; + } + + @Override + public Object visit(ASTTrue node, Object data) + { + return null; + } + + @Override + public Object visit(ASTFalse node, Object data) + { + return null; + } + + @Override + public Object visit(ASTIfStatement node, Object data) + { + String prefix = node.getPrefix(); + if (prefix.length() > 0) + { + if (extraIndentation == null) + { + fillExtraIndentation(prefix); + } + if (extraIndentation.length() > 0) + { + Matcher matcher = fix.matcher(prefix); + node.setPrefix(matcher.replaceAll(parentIndentation)); + node.childrenAccept(this, null); + } + } + return null; + } + + @Override + public Object visit(ASTElseStatement node, Object data) + { + if (extraIndentation != null && extraIndentation.length() > 0) + { + node.childrenAccept(this, null); + } + return null; + } + + @Override + public Object visit(ASTElseIfStatement node, Object data) + { + if (extraIndentation != null && extraIndentation.length() > 0) + { + node.childrenAccept(this, null); + } + return null; + } + + @Override + public Object visit(ASTSetDirective node, Object data) + { + String prefix = node.getPrefix(); + if (prefix.length() > 0) + { + if (extraIndentation == null) + { + fillExtraIndentation(prefix); + } + if (extraIndentation.length() > 0) + { + Matcher matcher = fix.matcher(prefix); + node.setPrefix(matcher.replaceAll(parentIndentation)); + } + } + return null; + } + + @Override + public Object visit(ASTExpression node, Object data) + { + return null; + } + + @Override + public Object visit(ASTAssignment node, Object data) + { + return null; + } + + @Override + public Object visit(ASTOrNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTAndNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTEQNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTNENode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTLTNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTGTNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTLENode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTGENode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTAddNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTSubtractNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTMulNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTDivNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTModNode node, Object data) + { + return null; + } + + @Override + public Object visit(ASTNotNode node, Object data) + { + return null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java new file mode 100644 index 00000000..9f84bfb0 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java @@ -0,0 +1,5 @@ +package org.apache.velocity.runtime.parser.node; + +public class JJTParserState extends JJTStandardParserState +{ +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java new file mode 100644 index 00000000..2c9aaea5 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java @@ -0,0 +1,105 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.slf4j.Logger; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * GetExecutor that is smart about Maps. If it detects one, it does not + * use Reflection but a cast to access the getter. + * + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class MapGetExecutor + extends AbstractExecutor +{ + private final String property; + private final boolean isAlive; + + public MapGetExecutor(final Logger log, final Object object, final String property) + { + this.log = log; + this.property = property; + isAlive = discover(object); + } + + @Override + public Method getMethod() + { + if (isAlive()) + { + return MapGetMethod.instance(); + } + return null; + } + + @Override + public boolean isAlive() + { + return isAlive; + } + + protected boolean discover (final Object object) + { + if (object instanceof Map) + { + if (property != null) + { + return true; + } + } + return false; + } + + @Override + public Object execute(final Object o) + { + return ((Map) o).get(property); + } + + private static final class MapGetMethod + { + private static final Method instance; + + static + { + try + { + instance = Map.class.getMethod("get", Object.class); + } + catch (final NoSuchMethodException mapGetMethodMissingError) + { + throw new Error(mapGetMethodMissingError); + } + } + + private MapGetMethod() { } + + static Method instance() + { + return instance; + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java new file mode 100644 index 00000000..3dd3301a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java @@ -0,0 +1,84 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.exception.VelocityException; +import org.slf4j.Logger; + +import java.util.Map; + +/** + * SetExecutor that is smart about Maps. If it detects one, it does not + * use Reflection but a cast to access the setter. + * + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class MapSetExecutor + extends SetExecutor +{ + private final String property; + + public MapSetExecutor(final Logger log, final Class clazz, final String property) + { + this.log = log; + this.property = property; + discover(clazz); + } + + protected void discover (final Class<?> clazz) + { + Class<?> [] interfaces = clazz.getInterfaces(); + for (Class<?> anInterface : interfaces) + { + if (anInterface.equals(Map.class)) + { + try + { + if (property != null) + { + setMethod(Map.class.getMethod("put", Object.class, Object.class)); + } + } + /* + * pass through application level runtime exceptions + */ + catch (RuntimeException e) + { + throw e; + } + catch (Exception e) + { + String msg = "Exception while looking for put('" + property + "') method"; + log.error(msg, e); + throw new VelocityException(msg, e); + } + break; + } + } + } + + @Override + public Object execute(final Object o, final Object arg) + { + return ((Map) o).put(property, arg); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java new file mode 100644 index 00000000..52cedccb --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java @@ -0,0 +1,539 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility-class for all arithmetic-operations.<br><br> + * + * All operations (+ - / *) return a Number which type is the type of the bigger argument.<br> + * Example:<br> + * <code>add ( new Integer(10), new Integer(1))</code> will return an <code>Integer</code>-Object with the value 11<br> + * <code>add ( new Long(10), new Integer(1))</code> will return an <code>Long</code>-Object with the value 11<br> + * <code>add ( new Integer(10), new Float(1))</code> will return an <code>Float</code>-Object with the value 11<br><br> + * + * Overflow checking:<br> + * For integral values (byte, short, int) there is an implicit overflow correction (the next "bigger" + * type will be returned). For example, if you call <code>add (new Integer (Integer.MAX_VALUE), 1)</code> a + * <code>Long</code>-object will be returned with the correct value of <code>Integer.MAX_VALUE+1</code>.<br> + * In addition to that the methods <code>multiply</code>,<code>add</code> and <code>substract</code> implement overflow + * checks for <code>long</code>-values. That means that if an overflow occurs while working with long values a BigInteger + * will be returned.<br> + * For all other operations and types (such as Float and Double) there is no overflow checking. + * + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @since 1.5 + */ +public abstract class MathUtils +{ + + /** + * A BigDecimal representing the number 0 + */ + protected static final BigDecimal DECIMAL_ZERO = new BigDecimal ( BigInteger.ZERO ); + + /** + * The constants are used to determine in which context we have to calculate. + */ + protected static final int BASE_LONG = 0; + protected static final int BASE_FLOAT = 1; + protected static final int BASE_DOUBLE = 2; + protected static final int BASE_BIGINTEGER = 3; + protected static final int BASE_BIGDECIMAL = 4; + + /** + * The <code>Class</code>-object is key, the maximum-value is the value + */ + private static final Map<Class<? extends Number>, BigDecimal> ints = new HashMap<>(); + static + { + ints.put (Byte.class, BigDecimal.valueOf (Byte.MAX_VALUE)); + ints.put (Short.class, BigDecimal.valueOf (Short.MAX_VALUE)); + ints.put (Integer.class, BigDecimal.valueOf (Integer.MAX_VALUE)); + ints.put (Long.class, BigDecimal.valueOf (Long.MAX_VALUE)); + ints.put (BigInteger.class, BigDecimal.valueOf (-1)); + } + + /** + * The "size" of the number-types - ascending. + */ + private static final List<Class<? extends Number>> typesBySize = new ArrayList<>(); + static + { + typesBySize.add (Byte.class); + typesBySize.add (Short.class); + typesBySize.add (Integer.class); + typesBySize.add (Long.class); + typesBySize.add (Float.class); + typesBySize.add (Double.class); + } + + /** + * Convert the given Number to a BigDecimal + * @param n + * @return The number as BigDecimal + */ + public static BigDecimal toBigDecimal (Number n) + { + + if (n instanceof BigDecimal) + { + return (BigDecimal)n; + } + + if (n instanceof BigInteger) + { + return new BigDecimal ( (BigInteger)n ); + } + + return BigDecimal.valueOf(n.doubleValue()); + + } + + /** + * Convert the given Number to a BigInteger + * @param n + * @return The number as BigInteger + */ + public static BigInteger toBigInteger (Number n) + { + + if (n instanceof BigInteger) + { + return (BigInteger)n; + } + + return BigInteger.valueOf (n.longValue()); + + } + + /** + * Compare the given Number to 0. + * @param n + * @return True if number is 0. + */ + public static boolean isZero (Number n) + { + if (isInteger( n ) ) + { + if (n instanceof BigInteger) + { + return ((BigInteger)n).compareTo (BigInteger.ZERO) == 0; + } + return n.doubleValue() == 0; + } + if (n instanceof Float) + { + return n.floatValue() == 0f; + } + if (n instanceof Double) + { + return n.doubleValue() == 0d; + } + return toBigDecimal( n ).compareTo( DECIMAL_ZERO) == 0; + } + + /** + * Test, whether the given object is an integer value + * (Byte, Short, Integer, Long, BigInteger) + * @param n + * @return True if n is an integer. + */ + public static boolean isInteger (Number n) + { + return ints.containsKey (n.getClass()); + } + + /** + * Wrap the given primitive into the given class if the value is in the + * range of the destination type. If not the next bigger type will be chosen. + * @param value + * @param type + * @return Number object representing the primitive. + */ + public static Number wrapPrimitive (long value, Class<?> type) + { + if (type == Byte.class) + { + if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE) + { + type = Short.class; + } + else + { + return (byte) value; + } + } + if (type == Short.class) + { + if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) + { + type = Integer.class; + } + else + { + return (short) value; + } + } + if (type == Integer.class) + { + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) + { + type = Long.class; + } + else + { + return (int) value; + } + } + if (type == Long.class) + { + return value; + } + return BigInteger.valueOf(value); + } + + /** + * Wrap the result in the object of the bigger type. + * + * @param value result of operation (as a long) - used to check size + * @param op1 first operand of binary operation + * @param op2 second operand of binary operation + * @return Number object of appropriate size to fit the value and operators + */ + private static Number wrapPrimitive (long value, Number op1, Number op2) + { + if ( typesBySize.indexOf( op1.getClass()) > typesBySize.indexOf( op2.getClass())) + { + return wrapPrimitive(value, op1.getClass()); + } + return wrapPrimitive(value, op2.getClass()); + } + + /** + * Find the common Number-type to be used in calculations. + * + * @param op1 first operand of binary operation + * @param op2 second operand of binary operation + * @return constant indicating type of Number to use in calculations + */ + private static int findCalculationBase (Number op1, Number op2) + { + + boolean op1Int = isInteger(op1); + boolean op2Int = isInteger(op2); + + if ( (op1 instanceof BigDecimal || op2 instanceof BigDecimal) || + ( (!op1Int || !op2Int) && (op1 instanceof BigInteger || op2 instanceof BigInteger)) ) + { + return BASE_BIGDECIMAL; + } + + if (op1Int && op2Int) { + if (op1 instanceof BigInteger || op2 instanceof BigInteger) + { + return BASE_BIGINTEGER; + } + return BASE_LONG; + } + + if ((op1 instanceof Double) || (op2 instanceof Double)) + { + return BASE_DOUBLE; + } + return BASE_FLOAT; + } + + /** + * Find the Number-type to be used for a single number + * + * @param op operand + * @return constant indicating type of Number to use in calculations + */ + public static int findCalculationBase(Number op) + { + if (isInteger(op)) + { + if (op instanceof BigInteger) + { + return BASE_BIGINTEGER; + } + return BASE_LONG; + } else if (op instanceof BigDecimal) + { + return BASE_BIGDECIMAL; + } else if (op instanceof Double) + { + return BASE_DOUBLE; + } + return BASE_FLOAT; + } + + /** + * Add two numbers and return the correct value / type. + * Overflow detection is done for integer values (byte, short, int, long) only! + * @param op1 + * @param op2 + * @return Addition result. + */ + public static Number add (Number op1, Number op2) + { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) + { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).add( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + long result = l1+l2; + + // Overflow check + if ((result ^ l1) < 0 && (result ^ l2) < 0) + { + return toBigInteger( op1).add( toBigInteger( op2)); + } + return wrapPrimitive( result, op1, op2); + case BASE_FLOAT: + return op1.floatValue() + op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() + op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).add( toBigDecimal( op2 )); + } + } + + /** + * Subtract two numbers and return the correct value / type. + * Overflow detection is done for integer values (byte, short, int, long) only! + * @param op1 + * @param op2 + * @return Subtraction result. + */ + public static Number subtract (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).subtract( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + long result = l1-l2; + + // Overflow check + if ((result ^ l1) < 0 && (result ^ ~l2) < 0) { + return toBigInteger( op1).subtract( toBigInteger( op2)); + } + return wrapPrimitive( result, op1, op2); + case BASE_FLOAT: + return op1.floatValue() - op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() - op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).subtract( toBigDecimal( op2 )); + } + } + + /** + * Multiply two numbers and return the correct value / type. + * Overflow detection is done for integer values (byte, short, int, long) only! + * @param op1 + * @param op2 + * @return Multiplication result. + */ + public static Number multiply (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).multiply( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + long result = l1*l2; + + // Overflow detection + if ((l2 != 0) && (result / l2 != l1)) { + return toBigInteger( op1).multiply( toBigInteger( op2)); + } + return wrapPrimitive( result, op1, op2); + case BASE_FLOAT: + return op1.floatValue() * op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() * op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).multiply( toBigDecimal( op2 )); + } + } + + /** + * Divide two numbers. The result will be returned as Integer-type if and only if + * both sides of the division operator are Integer-types. Otherwise a Float, Double, + * or BigDecimal will be returned. + * @param op1 + * @param op2 + * @return Division result. + */ + public static Number divide (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + BigInteger b1 = toBigInteger( op1 ); + BigInteger b2 = toBigInteger( op2 ); + return b1.divide( b2); + + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + return wrapPrimitive( l1 / l2, op1, op2); + + case BASE_FLOAT: + return op1.floatValue() / op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() / op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).divide( toBigDecimal( op2 ), BigDecimal.ROUND_HALF_DOWN); + } + } + + /** + * Modulo two numbers. + * @param op1 + * @param op2 + * @return Modulo result. + * + * @throws ArithmeticException If at least one parameter is a BigDecimal + */ + public static Number modulo (Number op1, Number op2) throws ArithmeticException { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).mod( toBigInteger( op2 )); + case BASE_LONG: + return wrapPrimitive( op1.longValue() % op2.longValue(), op1, op2); + case BASE_FLOAT: + return op1.floatValue() % op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() % op2.doubleValue(); + + // Default is BigDecimal operation + default: + throw new ArithmeticException( "Cannot calculate the modulo of BigDecimals."); + } + } + + /** + * Compare two numbers. + * @param op1 + * @param op2 + * @return 1 if n1 > n2, -1 if n1 < n2 and 0 if equal. + */ + public static int compare (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).compareTo( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + if (l1 < l2) { + return -1; + } + if (l1 > l2) { + return 1; + } + return 0; + case BASE_FLOAT: + float f1 = op1.floatValue(); + float f2 = op2.floatValue(); + if (f1 < f2) { + return -1; + } + if (f1 > f2) { + return 1; + } + return 0; + case BASE_DOUBLE: + double d1 = op1.doubleValue(); + double d2 = op2.doubleValue(); + if (d1 < d2) { + return -1; + } + if (d1 > d2) { + return 1; + } + return 0; + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).compareTo( toBigDecimal ( op2 )); + } + } + + /** + * Negate a number + * @param op n + * @return -n (unary negation of n) + */ + public static Number negate(Number op) + { + int calcBase = findCalculationBase( op); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger(op).negate(); + case BASE_LONG: + long l = op.longValue(); + /* overflow check */ + if (l == Long.MIN_VALUE) + { + return toBigInteger(l).negate(); + } + return wrapPrimitive(-l, op.getClass()); + case BASE_FLOAT: + float f = op.floatValue(); + return -f; + case BASE_DOUBLE: + double d = op.doubleValue(); + return -d; + // Default is BigDecimal operation + default: + return toBigDecimal(op).negate(); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java new file mode 100644 index 00000000..ff867a15 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java @@ -0,0 +1,232 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.Renderable; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; + +import java.io.IOException; +import java.io.Writer; + +/** + * This file describes the interface between the Velocity code + * and the JavaCC generated code. + * + * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a> + * @version $Id$ + */ + +public interface Node extends Renderable +{ + /** This method is called after the node has been made the current + * node. It indicates that child nodes can now be added to it. */ + void jjtOpen(); + + /** This method is called after all the child nodes have been + added. + */ + void jjtClose(); + + /** + * This pair of methods are used to inform the node of its + * parent. + * @param n + * + */ + void jjtSetParent(Node n); + + /** + * @return The node parent. + */ + Node jjtGetParent(); + + /** + * This method tells the node to add its argument to the node's + * list of children. + * @param n + * @param i + */ + void jjtAddChild(Node n, int i); + + /** + * This method returns a child node. The children are numbered + * from zero, left to right. + * @param i + * @return A child node. + */ + Node jjtGetChild(int i); + + /** + * Return the number of children the node has. + * @return The number of children of this node. + */ + int jjtGetNumChildren(); + + /** + * @param visitor + * @param data + * @return The Node execution result object. + */ + Object jjtAccept(ParserVisitor visitor, Object data); + + /* + * ======================================================================== + * + * The following methods are not generated automatically be the Parser but + * added manually to be used by Velocity. + * + * ======================================================================== + */ + + /** + * @see #jjtAccept(ParserVisitor, Object) + * @param visitor + * @param data + * @return The node execution result. + */ + Object childrenAccept(ParserVisitor visitor, Object data); + + /** + * @return The first token. + */ + Token getFirstToken(); + /** + * @return The last token. + */ + Token getLastToken(); + /** + * @return The NodeType. + */ + int getType(); + + /** + * @param context + * @param data + * @return The init result. + * @throws TemplateInitException + */ + Object init(InternalContextAdapter context, Object data) throws TemplateInitException; + + /** + * @param context + * @return The evaluation result. + * @throws MethodInvocationException + */ + boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException; + + /** + * @param context + * @return The node value. + * @throws MethodInvocationException + */ + Object value(InternalContextAdapter context) + throws MethodInvocationException; + + /** + * @param context + * @param writer + * @return True if the node rendered successfully. + * @throws IOException + * @throws MethodInvocationException + * @throws ParseErrorException + * @throws ResourceNotFoundException + */ + @Override + boolean render(InternalContextAdapter context, Writer writer) + throws IOException,MethodInvocationException, ParseErrorException, ResourceNotFoundException; + + /** + * @param o + * @param context + * @return The execution result. + * @throws MethodInvocationException + */ + Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException; + + /** + * @param info + */ + void setInfo(int info); + + /** + * @return The current node info. + */ + int getInfo(); + + /** + * @return A literal. + */ + String literal(); + + /** + * Mark the node as invalid. + */ + void setInvalid(); + + /** + * @return True if the node is invalid. + */ + boolean isInvalid(); + + /** + * @return The current line position. + */ + int getLine(); + + /** + * @return The current column position. + */ + int getColumn(); + + /** + * @return the file name of the template + */ + String getTemplateName(); + + /** + * @return cached image (String) of the first Token for this Node returned by the Parser + */ + String getFirstTokenImage(); + + /** + * @return cached image (String) of the last Token for this Node returned by the Parser + */ + String getLastTokenImage(); + + /** + * @return the template this node belongs to + */ + Template getTemplate(); + + /** + * @return the parser which generated this node + * @since 2.2 + */ + Parser getParser(); +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java new file mode 100644 index 00000000..84b735bd --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java @@ -0,0 +1,162 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.StandardParserConstants; +import org.apache.velocity.runtime.parser.Token; + +/** + * Utilities for dealing with the AST node structure. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class NodeUtils +{ + /** + * Collect all the <SPECIAL_TOKEN>s that + * are carried along with a token. Special + * tokens do not participate in parsing but + * can still trigger certain lexical actions. + * In some cases you may want to retrieve these + * special tokens, this is simply a way to + * extract them. + * @param t the Token + * @return StrBuilder with the special tokens. + * @since 2.0.0 + */ + public static StringBuilder getSpecialText(Parser parser, Token t) + { + StringBuilder sb = new StringBuilder(); + + Token tmp_t = t.specialToken; + + while (tmp_t.specialToken != null) + { + tmp_t = tmp_t.specialToken; + } + + while (tmp_t != null) + { + String st = tmp_t.image; + + for(int i = 0, is = st.length(); i < is; i++) + { + char c = st.charAt(i); + + if ( c == parser.hash() || c == parser.dollar() ) + { + sb.append( c ); + } + + /* + * more dreaded MORE hack :) + * + * looking for ("\\")*"$" sequences + */ + + if ( c == '\\') + { + boolean ok = true; + boolean term = false; + + int j = i; + for( ok = true; ok && j < is; j++) + { + char cc = st.charAt( j ); + + if (cc == '\\') + { + /* + * if we see a \, keep going + */ + continue; + } + else if( cc == parser.dollar() ) + { + /* + * a $ ends it correctly + */ + term = true; + ok = false; + } + else + { + /* + * nah... + */ + ok = false; + } + } + + if (term) + { + String foo = st.substring( i, j ); + sb.append( foo ); + i = j; + } + } + } + + tmp_t = tmp_t.next; + } + return sb; + } + + /** + * complete node literal + * @param t + * @return A node literal. + */ + public static String tokenLiteral( Parser parser, Token t ) + { + // Look at kind of token and return "" when it's a block comment + if (t.kind == StandardParserConstants.MULTI_LINE_COMMENT) + { + return ""; + } + else if (t.specialToken == null || t.specialToken.image.startsWith(parser.lineComment())) + { + return t.image; + } + else + { + StringBuilder special = getSpecialText(parser, t); + if (special.length() > 0) + { + return special.append(t.image).toString(); + } + return t.image; + } + } + + /** + * Fix children indentation in structured space gobbling mode. + * @param parent + * @param parentIndentation + */ + public static void fixIndentation(SimpleNode parent, String parentIndentation) + { + IndentationFixer fixer = new IndentationFixer(parentIndentation); + parent.childrenAccept(fixer, null); + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java new file mode 100644 index 00000000..7cc1dc53 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java @@ -0,0 +1,24 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +public interface ParserTreeConstants extends StandardParserTreeConstants +{ +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java new file mode 100644 index 00000000..8fe14fe6 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java @@ -0,0 +1,332 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +/** + * Interface used in implementation of visitor pattern. Based on + * code autogenerated by JavaCC. Formerly found in package + * org.apache.velocity.runtime.parser. + * + * @version $Id$ + * @since 1.5 + */ +public interface ParserVisitor +{ + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(SimpleNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + + Object visit(ASTprocess node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTText node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTEscapedDirective node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTEscape node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTComment node, Object data); + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTTextblock node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTFloatingPointLiteral node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTIntegerLiteral node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTStringLiteral node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTIdentifier node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTWord node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + + Object visit(ASTDirectiveAssign node, Object data); + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTDirective node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTBlock node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTMap node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTObjectArray node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTIntegerRange node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTMethod node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTIndex node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTReference node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTTrue node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTFalse node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTIfStatement node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTElseStatement node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTElseIfStatement node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTSetDirective node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTExpression node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTAssignment node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTOrNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTAndNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTEQNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTNENode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTLTNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTGTNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTLENode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTGENode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTAddNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTSubtractNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTMulNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTDivNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTModNode node, Object data); + + /** + * @param node + * @param data + * @return The object rendered by this node. + */ + Object visit(ASTNotNode node, Object data); +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java new file mode 100644 index 00000000..7763724c --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java @@ -0,0 +1,151 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.ArrayListWrapper; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +import java.lang.reflect.InvocationTargetException; + +/** + * Returned the value of object property when executed. + */ +public class PropertyExecutor extends AbstractExecutor +{ + private final Introspector introspector; + private final boolean wrapArray; + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @since 1.5 + */ + public PropertyExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property) + { + this(log, introspector, clazz, property, false); + } + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @param wrapArray + * @since 1.5 + */ + public PropertyExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property, final boolean wrapArray) + { + this.log = log; + this.introspector = introspector; + this.wrapArray = wrapArray; + + // Don't allow passing in the empty string or null because + // it will either fail with a StringIndexOutOfBounds error + // or the introspector will get confused. + if (StringUtils.isNotEmpty(property)) + { + discover(clazz, property); + } + } + + /** + * @return The current introspector. + * @since 1.5 + */ + protected Introspector getIntrospector() + { + return this.introspector; + } + + /** + * @param clazz + * @param property + */ + protected void discover(final Class<?> clazz, final String property) + { + /* + * this is gross and linear, but it keeps it straightforward. + */ + + try + { + Object [] params = {}; + + StringBuilder sb = new StringBuilder("get"); + sb.append(property); + + setMethod(introspector.getMethod(clazz, sb.toString(), params)); + + if (!isAlive()) + { + /* + * now the convenience, flip the 1st character + */ + + char c = sb.charAt(3); + + if (Character.isLowerCase(c)) + { + sb.setCharAt(3, Character.toUpperCase(c)); + } + else + { + sb.setCharAt(3, Character.toLowerCase(c)); + } + + setMethod(introspector.getMethod(clazz, sb.toString(), params)); + } + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for property getter for '" + property; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * @see org.apache.velocity.runtime.parser.node.AbstractExecutor#execute(java.lang.Object) + */ + @Override + public Object execute(Object o) + throws IllegalAccessException, InvocationTargetException + { + if (wrapArray) + { + o = new ArrayListWrapper(o); + } + return isAlive() ? getMethod().invoke(o, ((Object []) null)) : null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java new file mode 100644 index 00000000..643f9817 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java @@ -0,0 +1,128 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; + +/** + * Returns the value of a public field when executed. + */ +public class PublicFieldExecutor extends AbstractExecutor +{ + private final Introspector introspector; + + /** + * Field to be accessed + */ + private Field field = null; + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @since 1.5 + */ + public PublicFieldExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property) + { + this.log = log; + this.introspector = introspector; + + // Don't allow passing in the empty string or null because + // it will either fail with a StringIndexOutOfBounds error + // or the introspector will get confused. + if (StringUtils.isNotEmpty(property)) + { + discover(clazz, property); + } + } + + @Override + public boolean isAlive() { + return getField() != null; + } + + /** + * @return The current field. + */ + public Field getField() + { + return field; + } + + /** + * @param field + */ + protected void setField(final Field field) + { + this.field = field; + } + + /** + * @return The current introspector. + * @since 1.5 + */ + protected Introspector getIntrospector() + { + return this.introspector; + } + + /** + * @param clazz + * @param property + */ + protected void discover(final Class<?> clazz, final String property) + { + try + { + setField(introspector.getField(clazz, property)); + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for public field '" + property; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * @see org.apache.velocity.runtime.parser.node.AbstractExecutor#execute(java.lang.Object) + */ + @Override + public Object execute(Object o) + throws IllegalAccessException, InvocationTargetException + { + return isAlive() ? getField().get(o) : null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java new file mode 100644 index 00000000..09072615 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java @@ -0,0 +1,133 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +import java.lang.reflect.InvocationTargetException; + + +/** + * Executor that simply tries to execute a put(key, value) + * operation. This will try to find a put(key) method + * for any type of object, not just objects that + * implement the Map interface as was previously + * the case. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class PutExecutor extends SetExecutor +{ + private final Introspector introspector; + private final String property; + + /** + * @param log + * @param introspector + * @param clazz + * @param arg + * @param property + */ + public PutExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final Object arg, final String property) + { + this.log = log; + this.introspector = introspector; + this.property = property; + + discover(clazz, arg); + } + + /** + * @param clazz + * @param arg + */ + protected void discover(final Class<?> clazz, final Object arg) + { + Object [] params; + + // If you passed in null as property, we don't use the value + // for parameter lookup. Instead we just look for put(Object) without + // any parameters. + // + // In any other case, the following condition will set up an array + // for looking up put(String, Object) on the class. + + if (property == null) + { + // The passed in arg object is used by the Cache to look up the method. + params = new Object[] { arg }; + } + else + { + params = new Object[] { property, arg }; + } + + try + { + setMethod(introspector.getMethod(clazz, "put", params)); + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for put('" + params[0] + "') method"; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * @see org.apache.velocity.runtime.parser.node.SetExecutor#execute(java.lang.Object, java.lang.Object) + */ + @Override + public Object execute(final Object o, final Object value) + throws IllegalAccessException, InvocationTargetException + { + Object [] params; + + if (isAlive()) + { + // If property != null, pass in the name for put(key, value). Else just put(value). + if (property == null) + { + params = new Object [] { value }; + } + else + { + params = new Object [] { property, value }; + } + + return getMethod().invoke(o, params); + } + + return null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java new file mode 100644 index 00000000..461c92a8 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java @@ -0,0 +1,87 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.slf4j.Logger; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Abstract class that is used to execute an arbitrary + * method that is in introspected. This is the superclass + * for the PutExecutor and SetPropertyExecutor. + * + * There really should be a superclass for this and AbstractExecutor (which should + * be refactored to GetExecutor) because they differ only in the execute() method. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public abstract class SetExecutor +{ + /** Class logger */ + protected Logger log = null; + + /** + * Method to be executed. + */ + private Method method = null; + + /** + * Execute method against context. + * @param o + * @param value + * @return The result of the invocation. + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + public abstract Object execute(Object o, Object value) + throws IllegalAccessException, InvocationTargetException; + + /** + * Tell whether the executor is alive by looking + * at the value of the method. + * @return True if the executor is alive. + */ + public boolean isAlive() + { + return (method != null); + } + + /** + * @return The method to invoke. + */ + public Method getMethod() + { + return method; + } + + /** + * @param method + */ + protected void setMethod(final Method method) + { + this.method = method; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java new file mode 100644 index 00000000..e02ca4e6 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java @@ -0,0 +1,138 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +import java.lang.reflect.InvocationTargetException; + +/** + * Executor for looking up property names in the passed in class + * This will try to find a set<foo>(key, value) method + * + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class SetPropertyExecutor + extends SetExecutor +{ + private final Introspector introspector; + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @param arg + */ + public SetPropertyExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property, final Object arg) + { + this.log = log; + this.introspector = introspector; + + // Don't allow passing in the empty string or null because + // it will either fail with a StringIndexOutOfBounds error + // or the introspector will get confused. + if (StringUtils.isNotEmpty(property)) + { + discover(clazz, property, arg); + } + } + + /** + * @return The current introspector. + */ + protected Introspector getIntrospector() + { + return this.introspector; + } + + /** + * @param clazz + * @param property + * @param arg + */ + protected void discover(final Class<?> clazz, final String property, final Object arg) + { + Object [] params = new Object [] { arg }; + + try + { + StringBuilder sb = new StringBuilder("set"); + sb.append(property); + + setMethod(introspector.getMethod(clazz, sb.toString(), params)); + + if (!isAlive()) + { + /* + * now the convenience, flip the 1st character + */ + + char c = sb.charAt(3); + + if (Character.isLowerCase(c)) + { + sb.setCharAt(3, Character.toUpperCase(c)); + } + else + { + sb.setCharAt(3, Character.toLowerCase(c)); + } + + setMethod(introspector.getMethod(clazz, sb.toString(), params)); + } + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for property setter for '" + property; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * Execute method against context. + * @param o + * @param value + * @return The value of the invocation. + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + @Override + public Object execute(final Object o, final Object value) + throws IllegalAccessException, InvocationTargetException + { + Object [] params = new Object [] { value }; + return isAlive() ? getMethod().invoke(o, params) : null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java new file mode 100644 index 00000000..7dead361 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java @@ -0,0 +1,149 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.introspection.Introspector; +import org.slf4j.Logger; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +/** + * Executor for setting public fields in objects + * + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a> + */ +public class SetPublicFieldExecutor + extends SetExecutor +{ + private final Introspector introspector; + + /** + * Field to be accessed + */ + private Field field = null; + + /** + * @param log + * @param introspector + * @param clazz + * @param property + * @param arg + */ + public SetPublicFieldExecutor(final Logger log, final Introspector introspector, + final Class<?> clazz, final String property, final Object arg) + { + this.log = log; + this.introspector = introspector; + + // Don't allow passing in the empty string or null because + // it will either fail with a StringIndexOutOfBounds error + // or the introspector will get confused. + if (StringUtils.isNotEmpty(property)) + { + discover(clazz, property, arg); + } + } + + @Override + public boolean isAlive() { + return getField() != null; + } + + /** + * @return The current field. + */ + public Field getField() + { + return field; + } + + /** + * @param field + */ + protected void setField(final Field field) + { + this.field = field; + } + + /** + * @return The current introspector. + */ + protected Introspector getIntrospector() + { + return this.introspector; + } + + /** + * @param clazz + * @param property + * @param arg + */ + protected void discover(final Class<?> clazz, final String property, final Object arg) + { + try + { + Field field = introspector.getField(clazz, property); + if(!Modifier.isFinal(field.getModifiers())) + { + setField(field); + } + } + /* + * pass through application level runtime exceptions + */ + catch( RuntimeException e ) + { + throw e; + } + catch(Exception e) + { + String msg = "Exception while looking for public field '" + property; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * Execute method against context. + * @param o + * @param value + * @return The value of the invocation. + * @throws IllegalAccessException + * @throws InvocationTargetException + */ + @Override + public Object execute(final Object o, final Object value) + throws IllegalAccessException, InvocationTargetException + { + if (isAlive()) + { + Object oldValue = getField().get(o); + getField().set(o, value); + return oldValue; + } + else + return null; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java new file mode 100644 index 00000000..f85d6926 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java @@ -0,0 +1,650 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * 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. + */ + + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.charset.Charset; + +import org.apache.velocity.Template; +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.exception.MethodInvocationException; +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.TemplateInitException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.parser.Parser; +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.util.StringUtils; + +import org.slf4j.Logger; + +/** + * + */ +public class SimpleNode implements Node, Cloneable +{ + /** */ + protected RuntimeServices rsvc = null; + + /** */ + protected Logger log = null; + + /** */ + protected Node parent; + + /** */ + protected Node[] children; + + /** */ + protected int id; + + /** */ + protected Parser parser; + + /** */ + protected int info; + + /** */ + public boolean state; + + /** */ + protected boolean invalid = false; + + /** */ + protected Token first; + + /** */ + protected Token last; + + protected Template template; + + /** + * For caching the literal value. + */ + protected String literal = null; + + /** + * Line number for this Node in the vm source file. + */ + + protected int line; + + /** + * Column number for this Node in the vm source file. + */ + protected int column; + + /** + * String image variable of the first Token element that was parsed and connected to this Node. + */ + protected String firstImage; + + /** + * String image variable of the last Token element that was parsed and connected to this Node. + */ + protected String lastImage; + + public RuntimeServices getRuntimeServices() + { + return rsvc; + } + + /** + * @param i + */ + public SimpleNode(int i) + { + id = i; + } + + /** + * @param p + * @param i + */ + public SimpleNode(Parser p, int i) + { + this(i); + parser = p; + template = parser.getCurrentTemplate(); + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtOpen() + */ + @Override + public void jjtOpen() + { + first = parser.getToken(1); // added + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtClose() + */ + @Override + public void jjtClose() + { + last = parser.getToken(0); // added + } + + /** + * @param t + */ + public void setFirstToken(Token t) + { + this.first = t; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#getFirstToken() + */ + @Override + public Token getFirstToken() + { + return first; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#getLastToken() + */ + @Override + public Token getLastToken() + { + return last; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtSetParent(org.apache.velocity.runtime.parser.node.Node) + */ + @Override + public void jjtSetParent(Node n) + { + parent = n; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtGetParent() + */ + @Override + public Node jjtGetParent() + { + return parent; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtAddChild(org.apache.velocity.runtime.parser.node.Node, int) + */ + @Override + public void jjtAddChild(Node n, int i) + { + if (children == null) + { + children = new Node[i + 1]; + } + else if (i >= children.length) + { + Node c[] = new Node[i + 1]; + System.arraycopy(children, 0, c, 0, children.length); + children = c; + } + children[i] = n; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtGetChild(int) + */ + @Override + public Node jjtGetChild(int i) + { + return children[i]; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtGetNumChildren() + */ + @Override + public int jjtGetNumChildren() + { + return (children == null) ? 0 : children.length; + } + + + /** + * @see org.apache.velocity.runtime.parser.node.Node#jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) + { + return visitor.visit(this, data); + } + + + /** + * @see org.apache.velocity.runtime.parser.node.Node#childrenAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) + */ + @Override + public Object childrenAccept(ParserVisitor visitor, Object data) + { + if (children != null) + { + for (Node aChildren : children) + { + aChildren.jjtAccept(visitor, data); + } + } + return data; + } + + /* You can override these two methods in subclasses of SimpleNode to + customize the way the node appears when the tree is dumped. If + your output uses more than one line you should override + toString(String), otherwise overriding toString() is probably all + you need to do. */ + /** + * @param prefix display prefix + * @return String representation of this node. + */ + public String toString(String prefix) + { + return prefix + "_" + toString(); + } + + /** + * <p>Dumps nodes tree on System.out.</p> + * <p>Override {@link #dump(String, PrintWriter)} if you want to customize + * how the node dumps out its children. + * + * @param prefix + */ + public final void dump(String prefix) + { + dump(prefix, System.out); + } + + /** + * <p>Dumps nodes tree on System.out.</p> + * <p>Override {@link #dump(String, PrintWriter)} if you want to customize + * how the node dumps out its children. + * + * @param prefix display prefix + * @param out output print stream + */ + public final void dump(String prefix, PrintStream out) + { + Charset charset = null; + if (rsvc != null) /* may be null if node isn't yet initialized */ + { + String encoding = rsvc.getString(RuntimeConstants.INPUT_ENCODING); + try + { + charset = Charset.forName(encoding); + } + catch (Exception e) {} + } + if (charset == null) + { + charset = Charset.defaultCharset(); + } + PrintWriter pw = new PrintWriter(new OutputStreamWriter(out, charset)); + dump(prefix, pw); + pw.flush(); + } + + /** + * <p>Dumps nodes tree on System.out.</p> + * <p>Override this method if you want to customize how the node dumps + * out its children.</p> + * + * @param prefix display prefix + * @param out output print writer + */ + public void dump(String prefix, PrintWriter out) + { + out.println(toString()); + if (children != null) + { + for (int i = 0; i < children.length; ++i) + { + SimpleNode n = (SimpleNode) children[i]; + out.print(prefix + " |_"); + if (n != null) + { + n.dump(prefix + ( i == children.length - 1 ? " " : " | " ), out); + } + } + } + } + + /** + * Return a string that tells the current location of this node. + * @param context + * @return location + */ + protected String getLocation(InternalContextAdapter context) + { + return StringUtils.formatFileString(this); + } + + // All additional methods + + /** + * @see org.apache.velocity.runtime.parser.node.Node#literal() + */ + @Override + public String literal() + { + if( literal != null ) + { + return literal; + } + + // if we have only one string, just return it and avoid + // buffer allocation. VELOCITY-606 + if (first == last) + { + literal = NodeUtils.tokenLiteral(parser, first); + return literal; + } + + Token t = first; + StringBuilder sb = new StringBuilder(NodeUtils.tokenLiteral(parser, t)); + while (t != last) + { + t = t.next; + sb.append(NodeUtils.tokenLiteral(parser, t)); + } + literal = sb.toString(); + return literal; + } + + /** + * @throws TemplateInitException + * @see org.apache.velocity.runtime.parser.node.Node#init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object) + */ + @Override + public Object init(InternalContextAdapter context, Object data) throws TemplateInitException + { + /* + * hold onto the RuntimeServices + */ + + rsvc = (RuntimeServices) data; + log = rsvc.getLog("rendering"); + + int i, k = jjtGetNumChildren(); + + for (i = 0; i < k; i++) + { + jjtGetChild(i).init( context, data); + } + + line = first.beginLine; + column = first.beginColumn; + + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#evaluate(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public boolean evaluate(InternalContextAdapter context) + throws MethodInvocationException + { + return false; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#value(org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object value(InternalContextAdapter context) + throws MethodInvocationException + { + return null; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) + */ + @Override + public boolean render(InternalContextAdapter context, Writer writer) + throws IOException, MethodInvocationException, ParseErrorException, ResourceNotFoundException + { + int i, k = jjtGetNumChildren(); + + for (i = 0; i < k; i++) + jjtGetChild(i).render(context, writer); + + return true; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter) + */ + @Override + public Object execute(Object o, InternalContextAdapter context) + throws MethodInvocationException + { + return null; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#getType() + */ + @Override + public int getType() + { + return id; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#setInfo(int) + */ + @Override + public void setInfo(int info) + { + this.info = info; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#getInfo() + */ + @Override + public int getInfo() + { + return info; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#setInvalid() + */ + @Override + public void setInvalid() + { + invalid = true; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#isInvalid() + */ + @Override + public boolean isInvalid() + { + return invalid; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#getLine() + */ + @Override + public int getLine() + { + return line; + } + + /** + * @see org.apache.velocity.runtime.parser.node.Node#getColumn() + */ + @Override + public int getColumn() + { + return column; + } + + /** + * @since 1.5 + */ + public String toString() + { + StringBuilder tokens = new StringBuilder(); + + for (Token t = getFirstToken(); t != null; ) + { + tokens.append("[").append(t.image.replace("\n", "\\n")).append("]"); + if (t.next != null) + { + if (t.equals(getLastToken())) + { + break; + } + else + { + tokens.append(", "); + } + } + t = t.next; + } + String tok = tokens.toString(); + if (tok.length() > 50) tok = tok.substring(0, 50) + "..."; + return getClass().getSimpleName() + " [id=" + id + ", info=" + info + ", invalid=" + + invalid + + ", tokens=" + tok + "]"; + } + + @Override + public String getTemplateName() + { + return template.getName(); + } + + /** + * Call before calling cleanupParserAndTokens() if you want to store image of + * the first and last token of this node. + */ + public void saveTokenImages() + { + if( first != null ) + { + this.firstImage = first.image; + } + if( last != null ) + { + this.lastImage = last.image; + } + } + + /** + * Removes references to Parser and Tokens since they are not needed anymore at this point. + * + * This allows us to save memory quite a bit. + */ + public void cleanupParserAndTokens() + { + this.parser = null; + this.first = null; + this.last = null; + } + + /** + * @return String image variable of the first Token element that was parsed and connected to this Node. + */ + @Override + public String getFirstTokenImage() + { + return firstImage; + } + + /** + * @return String image variable of the last Token element that was parsed and connected to this Node. + */ + @Override + public String getLastTokenImage() + { + return lastImage; + } + + @Override + public Template getTemplate() { return template; } + + /** + * @return the parser which created this node + * @since 2.2 + */ + @Override + public Parser getParser() + { + return parser; + } + + /** + * Root node deep cloning + * @param template owner template + * @return cloned node + * @throws CloneNotSupportedException + * @since 2.4 + */ + public Node clone(Template template) throws CloneNotSupportedException + { + if (parent != null) { + throw new IllegalStateException("cannot clone a child node without knowing its parent"); + } + return clone(template, null); + } + + /** + * Child node deep cloning + * @param template owner template + * @param parent parent node + * @return cloned node + * @throws CloneNotSupportedException + * @since 2.4 + */ + protected Node clone(Template template, Node parent) throws CloneNotSupportedException + { + SimpleNode clone = (SimpleNode)super.clone(); + clone.template = template; + clone.parent = parent; + if (children != null) + { + clone.children = new SimpleNode[children.length]; + for (int i = 0; i < children.length; ++i) + { + clone.children[i] = ((SimpleNode)children[i]).clone(template, clone); + } + } + return clone; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ContentResource.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ContentResource.java new file mode 100644 index 00000000..67974d1e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ContentResource.java @@ -0,0 +1,105 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.util.StringBuilderWriter; + +import java.io.BufferedReader; +import java.io.Writer; + +/** + * This class represent a general text resource that may have been + * retrieved from any number of possible sources. + * + * Also of interest is Velocity's {@link org.apache.velocity.Template} + * <code>Resource</code>. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ContentResource extends Resource +{ + /** Default empty constructor */ + public ContentResource() + { + super(); + + setType(ResourceManager.RESOURCE_CONTENT); + } + + /** + * Pull in static content and store it. + * @return True if everything went ok. + * + * @exception ResourceNotFoundException Resource could not be + * found. + */ + @Override + public boolean process() + throws ResourceNotFoundException + { + BufferedReader reader = null; + + try + { + Writer sw = new StringBuilderWriter(); + + reader = new BufferedReader(resourceLoader.getResourceReader(name, encoding)); + + char buf[] = new char[1024]; + int len = 0; + + while ( ( len = reader.read( buf, 0, 1024 )) != -1) + sw.write( buf, 0, len ); + + setData(sw.toString()); + + return true; + } + catch ( ResourceNotFoundException e ) + { + // Tell the ContentManager to continue to look through any + // remaining configured ResourceLoaders. + throw e; + } + catch ( Exception e ) + { + String msg = "Cannot process content resource"; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (Exception ignored) + { + } + } + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/Resource.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/Resource.java new file mode 100644 index 00000000..5922604d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/Resource.java @@ -0,0 +1,320 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.resource.loader.ResourceLoader; +import org.slf4j.Logger; + +/** + * This class represent a general text resource that + * may have been retrieved from any number of possible + * sources. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public abstract class Resource implements Cloneable +{ + protected RuntimeServices rsvc = null; + protected Logger log = null; + + /** + * The template loader that initially loaded the input + * stream for this template, and knows how to check the + * source of the input stream for modification. + */ + protected ResourceLoader resourceLoader; + + /** + * The number of milliseconds in a minute, used to calculate the + * check interval. + */ + protected static final long MILLIS_PER_SECOND = 1000; + + /** + * How often the file modification time is checked (in seconds). + */ + protected long modificationCheckInterval = 0; + + /** + * The file modification time (in milliseconds) for the cached template. + */ + protected long lastModified = 0; + + /** + * The next time the file modification time will be checked (in + * milliseconds). + */ + protected long nextCheck = 0; + + /** + * Name of the resource + */ + protected String name; + + /** + * Character encoding of this resource + */ + protected String encoding = RuntimeConstants.ENCODING_DEFAULT; + + /** + * Resource might require ancillary storage of some kind + */ + protected Object data = null; + + /** + * Resource type (RESOURCE_TEMPLATE or RESOURCE_CONTENT) + */ + protected int type; + + /** + * Default constructor + */ + public Resource() + { + } + + /** + * @param rs + */ + public void setRuntimeServices( RuntimeServices rs ) + { + rsvc = rs; + log = rsvc.getLog("loader"); + } + + /** + * Perform any subsequent processing that might need + * to be done by a resource. In the case of a template + * the actual parsing of the input stream needs to be + * performed. + * + * @return Whether the resource could be processed successfully. + * For a {@link org.apache.velocity.Template} or {@link + * org.apache.velocity.runtime.resource.ContentResource}, this + * indicates whether the resource could be read. + * @exception ResourceNotFoundException Similar in semantics as + * returning <code>false</code>. + * @throws ParseErrorException + */ + public abstract boolean process() + throws ResourceNotFoundException, ParseErrorException; + + /** + * @return True if source has been modified. + */ + public boolean isSourceModified() + { + return resourceLoader.isSourceModified(this); + } + + /** + * Set the modification check interval. + * @param modificationCheckInterval The interval (in seconds). + */ + public void setModificationCheckInterval(long modificationCheckInterval) + { + this.modificationCheckInterval = modificationCheckInterval; + } + + /** + * Is it time to check to see if the resource + * source has been updated? + * @return True if resource must be checked. + */ + public boolean requiresChecking() + { + /* + * short circuit this if modificationCheckInterval == 0 + * as this means "don't check" + */ + + if (modificationCheckInterval <= 0 ) + { + return false; + } + + /* + * see if we need to check now + */ + + return ( System.currentTimeMillis() >= nextCheck ); + } + + /** + * 'Touch' this template and thereby resetting + * the nextCheck field. + */ + public void touch() + { + nextCheck = System.currentTimeMillis() + ( MILLIS_PER_SECOND * modificationCheckInterval); + } + + /** + * Set the name of this resource, for example + * test.vm. + * @param name + */ + public void setName(String name) + { + this.name = name; + } + + /** + * Get the name of this template. + * @return The name of this template. + */ + public String getName() + { + return name; + } + + /** + * set the encoding of this resource + * for example, "ISO-8859-1" + * @param encoding + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * get the encoding of this resource + * for example, "ISO-8859-1" + * @return The encoding of this resource. + */ + public String getEncoding() + { + return encoding; + } + + + /** + * Return the lastModifed time of this + * resource. + * @return The lastModifed time of this resource. + */ + public long getLastModified() + { + return lastModified; + } + + /** + * Set the last modified time for this + * resource. + * @param lastModified + */ + public void setLastModified(long lastModified) + { + this.lastModified = lastModified; + } + + /** + * Return the template loader that pulled + * in the template stream + * @return The resource loader for this resource. + */ + public ResourceLoader getResourceLoader() + { + return resourceLoader; + } + + /** + * Set the template loader for this template. Set + * when the Runtime determines where this template + * came from the list of possible sources. + * @param resourceLoader + */ + public void setResourceLoader(ResourceLoader resourceLoader) + { + this.resourceLoader = resourceLoader; + } + + /** + * Set arbitrary data object that might be used + * by the resource. + * @param data + */ + public void setData(Object data) + { + this.data = data; + } + + /** + * Get arbitrary data object that might be used + * by the resource. + * @return The data object for this resource. + */ + public Object getData() + { + return data; + } + + /** + * Sets the type of this Resource (RESOURCE_TEMPLATE or RESOURCE_CONTENT) + * @param type RESOURCE_TEMPLATE or RESOURCE_CONTENT + * @since 1.6 + */ + public void setType(int type) + { + this.type = type; + } + + /** + * @return type code of the Resource + * @since 1.6 + */ + public int getType() + { + return type; + } + + /** + * @return cloned resource + * @since 2.4 + */ + @Override + public Object clone() { + try + { + Resource clone = (Resource) super.clone(); + clone.deepCloneData(); + return clone; + } + catch (CloneNotSupportedException e) { + throw new RuntimeException("cloning not supported"); + } + } + + /** + * Deep cloning of resource data + * @return cloned data + */ + protected void deepCloneData() throws CloneNotSupportedException + { + // default implementation does nothing + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCache.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCache.java new file mode 100644 index 00000000..98570971 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCache.java @@ -0,0 +1,81 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.runtime.RuntimeServices; + +import java.util.Iterator; + +/** + * Interface that defines the shape of a pluggable resource cache + * for the included ResourceManager + * + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public interface ResourceCache +{ + /** + * initializes the ResourceCache. Will be + * called before any utilization + * + * @param rs RuntimeServices to use for logging, etc + */ + void initialize(RuntimeServices rs); + + /** + * retrieves a Resource from the + * cache + * + * @param resourceKey key for Resource to be retrieved + * @return Resource specified or null if not found + */ + Resource get(Object resourceKey); + + /** + * stores a Resource in the cache + * + * @param resourceKey key to associate with the Resource + * @param resource Resource to be stored + * @return existing Resource stored under this key, or null if none + */ + Resource put(Object resourceKey, Resource resource); + + /** + * removes a Resource from the cache + * + * @param resourceKey resource to be removed + * @return stored under key + */ + Resource remove(Object resourceKey); + + /** + * Removes all of the resources from this cache. + * The cache will be empty after this call returns. + * @since 2.0 + */ + void clear(); + + /** + * returns an Iterator of Keys in the cache. + * @return An Iterator of Keys in the cache. + */ + Iterator enumerateKeys(); +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCacheImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCacheImpl.java new file mode 100644 index 00000000..7ad03518 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCacheImpl.java @@ -0,0 +1,168 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Default implementation of the resource cache for the default + * ResourceManager. The cache uses a <i>least recently used</i> (LRU) + * algorithm, with a maximum size specified via the + * <code>resource.manager.cache.size</code> property (identified by the + * {@link + * org.apache.velocity.runtime.RuntimeConstants#RESOURCE_MANAGER_DEFAULTCACHE_SIZE} + * constant). This property get be set to <code>0</code> or less for + * a greedy, unbounded cache (the behavior from pre-v1.5). + * + * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> + * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> + * @version $Id$ + */ +public class ResourceCacheImpl implements ResourceCache +{ + + /** + * A simple LRU Map based on {@link LinkedHashSet}. + * + * @param <K> The key type of the map. + * @param <V> The value type of the map. + */ + private static class LRUMap<K, V> extends LinkedHashMap<K, V>{ + + /** + * The serial version uid; + */ + private static final long serialVersionUID = 5889225121697975043L; + + /** + * The size of the cache. + */ + private int cacheSize; + + /** + * Constructor. + * + * @param cacheSize The size of the cache. After reaching this size, the + * eldest-accessed element will be erased. + */ + public LRUMap(int cacheSize) + { + this.cacheSize = cacheSize; + } + + /** {@inheritDoc} */ + @Override + protected boolean removeEldestEntry(Entry<K, V> eldest) + { + return size() > cacheSize; + } + } + + /** + * Cache storage, assumed to be thread-safe. + */ + protected Map<Object, Resource> cache = new ConcurrentHashMap<>(512, 0.5f, 30); + + /** + * Runtime services, generally initialized by the + * <code>initialize()</code> method. + */ + protected RuntimeServices rsvc = null; + + protected Logger log; + + /** + * @see org.apache.velocity.runtime.resource.ResourceCache#initialize(org.apache.velocity.runtime.RuntimeServices) + */ + @Override + public void initialize(RuntimeServices rs ) + { + rsvc = rs; + + int maxSize = + rsvc.getInt(RuntimeConstants.RESOURCE_MANAGER_DEFAULTCACHE_SIZE, 89); + if (maxSize > 0) + { + // Create a whole new Map here to avoid hanging on to a + // handle to the unsynch'd LRUMap for our lifetime. + Map<Object, Resource> lruCache = Collections.synchronizedMap(new LRUMap<>(maxSize)); + lruCache.putAll(cache); + cache = lruCache; + } + rsvc.getLog().debug("initialized ({}) with {} cache map.", this.getClass(), cache.getClass()); + } + + /** + * @see org.apache.velocity.runtime.resource.ResourceCache#get(java.lang.Object) + */ + @Override + public Resource get(Object key ) + { + return cache.get( key ); + } + + /** + * @see org.apache.velocity.runtime.resource.ResourceCache#put(java.lang.Object, org.apache.velocity.runtime.resource.Resource) + */ + @Override + public Resource put(Object key, Resource value ) + { + return cache.put( key, value ); + } + + /** + * @see org.apache.velocity.runtime.resource.ResourceCache#remove(java.lang.Object) + */ + @Override + public Resource remove(Object key ) + { + return cache.remove( key ); + } + + /** + * @see org.apache.velocity.runtime.resource.ResourceCache#clear() + * @since 2.0 + */ + @Override + public void clear() + { + cache.clear(); + } + + /** + * @see org.apache.velocity.runtime.resource.ResourceCache#enumerateKeys() + */ + @Override + public Iterator<Object> enumerateKeys() + { + return cache.keySet().iterator(); + } +} + diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceFactory.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceFactory.java new file mode 100644 index 00000000..e95712aa --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceFactory.java @@ -0,0 +1,59 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.Template; +import org.apache.velocity.exception.VelocityException; + +/** + * Class responsible for instantiating <code>Resource</code> objects, + * given name and type. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ResourceFactory +{ + /** + * @param resourceName + * @param resourceType + * @return The resource described by name and type. + */ + public static Resource getResource(String resourceName, int resourceType) + { + Resource resource = null; + + switch (resourceType) + { + case ResourceManager.RESOURCE_TEMPLATE: + resource = new Template(); + break; + + case ResourceManager.RESOURCE_CONTENT: + resource = new ContentResource(); + break; + default: + throw new VelocityException("invalide resource type"); + } + + return resource; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManager.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManager.java new file mode 100644 index 00000000..a5b64395 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManager.java @@ -0,0 +1,81 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.RuntimeServices; + +/** + * Class to manage the text resource for the Velocity + * Runtime. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public interface ResourceManager +{ + /** + * A template resources. + */ + int RESOURCE_TEMPLATE = 1; + + /** + * A static content resource. + */ + int RESOURCE_CONTENT = 2; + + /** + * Initialize the ResourceManager. + * @param rs + */ + void initialize(RuntimeServices rs); + + /** + * Gets the named resource. Returned class type corresponds to specified type + * (i.e. <code>Template</code> to <code>RESOURCE_TEMPLATE</code>). + * + * @param resourceName The name of the resource to retrieve. + * @param resourceType The type of resource (<code>RESOURCE_TEMPLATE</code>, + * <code>RESOURCE_CONTENT</code>, etc.). + * @param encoding The character encoding to use. + * @return Resource with the template parsed and ready. + * @throws ResourceNotFoundException if template not found + * from any available source. + * @throws ParseErrorException if template cannot be parsed due + * to syntax (or other) error. + */ + Resource getResource(String resourceName, int resourceType, String encoding) + throws ResourceNotFoundException, ParseErrorException; + + /** + * Determines is a template exists, and returns name of the loader that + * provides it. This is a slightly less hokey way to support + * the Velocity.templateExists() utility method, which was broken + * when per-template encoding was introduced. We can revisit this. + * + * @param resourceName Name of template or content resource + * @return class name of loader than can provide it + */ + String getLoaderNameForResource(String resourceName); + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java new file mode 100644 index 00000000..82843ac1 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java @@ -0,0 +1,607 @@ +package org.apache.velocity.runtime.resource; + +/* + * 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. + */ + +import org.apache.velocity.exception.ParseErrorException; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.resource.loader.ResourceLoader; +import org.apache.velocity.runtime.resource.loader.ResourceLoaderFactory; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.ExtProperties; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Vector; + + +/** + * Class to manage the text resource for the Velocity Runtime. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + */ +public class ResourceManagerImpl + implements ResourceManager +{ + + /** A template resources. */ + public static final int RESOURCE_TEMPLATE = 1; + + /** A static content resource. */ + public static final int RESOURCE_CONTENT = 2; + + /** Object implementing ResourceCache to be our resource manager's Resource cache. */ + protected ResourceCache globalCache = null; + + /** The List of templateLoaders that the Runtime will use to locate the InputStream source of a template. */ + protected final List<ResourceLoader> resourceLoaders = new ArrayList<>(); + + /** + * This is a list of the template input stream source initializers, basically properties for a particular template stream + * source. The order in this list reflects numbering of the properties i.e. + * + * <p>resource.loader.<loader-id>.<property> = <value></p> + */ + private final List<ExtProperties> sourceInitializerList = new ArrayList<>(); + + /** + * Has this Manager been initialized? + */ + private boolean isInit = false; + + /** switch to turn off log notice when a resource is found for the first time. */ + private boolean logWhenFound = true; + + /** The internal RuntimeServices object. */ + protected RuntimeServices rsvc = null; + + /** Logging. */ + protected Logger log = null; + + /** + * Initialize the ResourceManager. + * + * @param rs The Runtime Services object which is associated with this Resource Manager. + */ + @Override + public synchronized void initialize(final RuntimeServices rs) + { + if (isInit) + { + log.debug("Re-initialization of ResourceLoader attempted and ignored."); + return; + } + + ResourceLoader resourceLoader = null; + + rsvc = rs; + log = rsvc.getLog("loader"); + + log.trace("ResourceManager initializing: {}", this.getClass()); + + assembleResourceLoaderInitializers(); + + for (ExtProperties configuration : sourceInitializerList) + { + /* + * Resource loader can be loaded either via class name or be passed + * in as an instance. + */ + + String loaderClass = StringUtils.trim(configuration.getString(RuntimeConstants.RESOURCE_LOADER_CLASS)); + ResourceLoader loaderInstance = (ResourceLoader) configuration.get(RuntimeConstants.RESOURCE_LOADER_INSTANCE); + + if (loaderInstance != null) + { + resourceLoader = loaderInstance; + } else if (loaderClass != null) + { + resourceLoader = ResourceLoaderFactory.getLoader(rsvc, loaderClass); + } else + { + String msg = "Unable to find 'resource.loader." + + configuration.getString(RuntimeConstants.RESOURCE_LOADER_IDENTIFIER) + + ".class' specification in configuration." + + " This is a critical value. Please adjust configuration."; + log.error(msg); + throw new VelocityException(msg, null, rsvc.getLogContext().getStackTrace()); + } + + resourceLoader.commonInit(rsvc, configuration); + resourceLoader.init(configuration); + resourceLoaders.add(resourceLoader); + } + + /* + * now see if this is overridden by configuration + */ + + logWhenFound = rsvc.getBoolean(RuntimeConstants.RESOURCE_MANAGER_LOGWHENFOUND, true); + + /* + * now, is a global cache specified? + */ + + String cacheClassName = rsvc.getString(RuntimeConstants.RESOURCE_MANAGER_CACHE_CLASS); + + Object cacheObject = null; + + if (StringUtils.isNotEmpty(cacheClassName)) + { + try + { + cacheObject = ClassUtils.getNewInstance(cacheClassName); + } + catch (ClassNotFoundException cnfe) + { + String msg = "The specified class for ResourceCache (" + cacheClassName + + ") does not exist or is not accessible to the current classloader."; + log.error(msg, cnfe); + throw new VelocityException(msg, cnfe); + } + catch (IllegalAccessException ae) + { + throw new VelocityException("Could not access class '" + + cacheClassName + "'", ae); + } + catch (InstantiationException ie) + { + throw new VelocityException("Could not instantiate class '" + + cacheClassName + "'", ie); + } + + if (!(cacheObject instanceof ResourceCache)) + { + String msg = "The specified resource cache class (" + cacheClassName + + ") must implement " + ResourceCache.class.getName(); + log.error(msg); + throw new RuntimeException(msg); + } + } + + /* + * if we didn't get through that, just use the default. + */ + if (cacheObject == null) + { + cacheObject = new ResourceCacheImpl(); + } + + globalCache = (ResourceCache) cacheObject; + + globalCache.initialize(rsvc); + + isInit = true; + + log.trace("Default ResourceManager initialization complete."); + } + + /** + * This will produce a List of Hashtables, each hashtable contains the initialization info for a particular resource loader. This + * Hashtable will be passed in when initializing the the template loader. + */ + private void assembleResourceLoaderInitializers() + { + Vector<String> resourceLoaderNames = rsvc.getConfiguration().getVector(RuntimeConstants.RESOURCE_LOADERS); + + for (ListIterator<String> it = resourceLoaderNames.listIterator(); it.hasNext(); ) + { + /* + * The loader id might look something like the following: + * + * resource.loader.file + * + * The loader id is the prefix used for all properties + * pertaining to a particular loader. + */ + String loaderName = StringUtils.trim(it.next()); + it.set(loaderName); + StringBuilder loaderID = new StringBuilder(); + loaderID.append(RuntimeConstants.RESOURCE_LOADER).append('.').append(loaderName); + + ExtProperties loaderConfiguration = + rsvc.getConfiguration().subset(loaderID.toString()); + + /* + * we can't really count on ExtProperties to give us an empty set + */ + if (loaderConfiguration == null) + { + log.debug("ResourceManager : No configuration information found "+ + "for resource loader named '{}' (id is {}). Skipping it...", + loaderName, loaderID); + continue; + } + + /* + * add the loader name token to the initializer if we need it + * for reference later. We can't count on the user to fill + * in the 'name' field + */ + + loaderConfiguration.setProperty(RuntimeConstants.RESOURCE_LOADER_IDENTIFIER, loaderName); + + /* + * Add resources to the list of resource loader + * initializers. + */ + sourceInitializerList.add(loaderConfiguration); + } + } + + /** + * Gets the named resource. Returned class type corresponds to specified type (i.e. <code>Template</code> to <code> + * RESOURCE_TEMPLATE</code>). + * + * This method is now unsynchronized which requires that ResourceCache + * implementations be thread safe (as the default is). + * + * @param resourceName The name of the resource to retrieve. + * @param resourceType The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.). + * @param encoding The character encoding to use. + * + * @return Resource with the template parsed and ready. + * + * @throws ResourceNotFoundException if template not found from any available source. + * @throws ParseErrorException if template cannot be parsed due to syntax (or other) error. + */ + @Override + public Resource getResource(final String resourceName, final int resourceType, final String encoding) + throws ResourceNotFoundException, + ParseErrorException + { + /* + * Check to see if the resource was placed in the cache. + * If it was placed in the cache then we will use + * the cached version of the resource. If not we + * will load it. + * + * Note: the type is included in the key to differentiate ContentResource + * (static content from #include) with a Template. + */ + + String resourceKey = resourceType + resourceName; + Resource resource = globalCache.get(resourceKey); + + if (resource != null) + { + try + { + // avoids additional method call to refreshResource + if (resource.requiresChecking()) + { + /* + * both loadResource() and refreshResource() now return + * a new Resource instance when they are called + * (put in the cache when appropriate) in order to allow + * several threads to parse the same template simultaneously. + * It is redundant work and will cause more garbage collection but the + * benefit is that it allows concurrent parsing and processing + * without race conditions when multiple requests try to + * refresh/load the same template at the same time. + * + * Another alternative is to limit template parsing/retrieval + * so that only one thread can parse each template at a time + * but that creates a scalability bottleneck. + * + * See VELOCITY-606, VELOCITY-595 and VELOCITY-24 + */ + resource = refreshResource(resource, encoding); + } + } + catch (ResourceNotFoundException rnfe) + { + /* + * something exceptional happened to that resource + * this could be on purpose, + * so clear the cache and try again + */ + + globalCache.remove(resourceKey); + + return getResource(resourceName, resourceType, encoding); + } + catch (RuntimeException re) + { + log.error("ResourceManager.getResource() exception", re); + throw re; + } + } + else + { + try + { + /* + * it's not in the cache, so load it. + */ + resource = loadResource(resourceName, resourceType, encoding); + + if (resource.getResourceLoader().isCachingOn()) + { + globalCache.put(resourceKey, resource); + } + } + catch (ResourceNotFoundException rnfe) + { + log.error("ResourceManager: unable to find resource '{}' in any resource loader.", resourceName); + throw rnfe; + } + catch (ParseErrorException pee) + { + log.error("ResourceManager: parse exception: {}", pee.getMessage()); + throw pee; + } + catch (RuntimeException re) + { + log.error("ResourceManager.getResource() load exception", re); + throw re; + } + } + + return resource; + } + + /** + * Create a new Resource of the specified type. + * + * @param resourceName The name of the resource to retrieve. + * @param resourceType The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.). + * @return new instance of appropriate resource type + * @since 1.6 + */ + protected Resource createResource(String resourceName, int resourceType) + { + return ResourceFactory.getResource(resourceName, resourceType); + } + + /** + * Loads a resource from the current set of resource loaders. + * + * @param resourceName The name of the resource to retrieve. + * @param resourceType The type of resource (<code>RESOURCE_TEMPLATE</code>, <code>RESOURCE_CONTENT</code>, etc.). + * @param encoding The character encoding to use. + * + * @return Resource with the template parsed and ready. + * + * @throws ResourceNotFoundException if template not found from any available source. + * @throws ParseErrorException if template cannot be parsed due to syntax (or other) error. + */ + protected Resource loadResource(String resourceName, int resourceType, String encoding) + throws ResourceNotFoundException, + ParseErrorException + { + Resource resource = createResource(resourceName, resourceType); + resource.setRuntimeServices(rsvc); + resource.setName(resourceName); + resource.setEncoding(encoding); + + /* + * Now we have to try to find the appropriate + * loader for this resource. We have to cycle through + * the list of available resource loaders and see + * which one gives us a stream that we can use to + * make a resource with. + */ + + long howOldItWas = 0; + + for (ResourceLoader resourceLoader : resourceLoaders) + { + resource.setResourceLoader(resourceLoader); + + /* + * catch the ResourceNotFound exception + * as that is ok in our new multi-loader environment + */ + + try + { + + if (resource.process()) + { + /* + * FIXME (gmj) + * moved in here - technically still + * a problem - but the resource needs to be + * processed before the loader can figure + * it out due to to the new + * multi-path support - will revisit and fix + */ + + if (logWhenFound) + { + log.debug("ResourceManager: found {} with loader {}", + resourceName, resourceLoader.getClassName()); + } + + howOldItWas = resourceLoader.getLastModified(resource); + + break; + } + } + catch (ResourceNotFoundException rnfe) + { + /* + * that's ok - it's possible to fail in + * multi-loader environment + */ + } + } + + /* + * Return null if we can't find a resource. + */ + if (resource.getData() == null) + { + throw new ResourceNotFoundException("Unable to find resource '" + resourceName + "'", null, rsvc.getLogContext().getStackTrace()); + } + + /* + * some final cleanup + */ + + resource.setLastModified(howOldItWas); + resource.setModificationCheckInterval(resource.getResourceLoader().getModificationCheckInterval()); + + resource.touch(); + + return resource; + } + + /** + * Takes an existing resource, and 'refreshes' it. This generally means that the source of the resource is checked for changes + * according to some cache/check algorithm and if the resource changed, then the resource data is reloaded and re-parsed. + * + * @param resource resource to refresh + * @param encoding character encoding of the resource to refresh. + * @return resource + * @throws ResourceNotFoundException if template not found from current source for this Resource + * @throws ParseErrorException if template cannot be parsed due to syntax (or other) error. + */ + protected Resource refreshResource(Resource resource, final String encoding) + throws ResourceNotFoundException, ParseErrorException + { + /* + * The resource knows whether it needs to be checked + * or not, and the resource's loader can check to + * see if the source has been modified. If both + * these conditions are true then we must reload + * the input stream and parse it to make a new + * AST for the resource. + */ + + String resourceKey = resource.getType() + resource.getName(); + + /* + * touch() the resource to reset the counters + */ + resource.touch(); + + /* check whether this can now be found in a higher priority + * resource loader. if so, pass the request off to loadResource. + */ + ResourceLoader loader = resource.getResourceLoader(); + if (resourceLoaders.size() > 0 && resourceLoaders.indexOf(loader) > 0) + { + String name = resource.getName(); + if (loader != getLoaderForResource(name)) + { + resource = loadResource(name, resource.getType(), encoding); + if (resource.getResourceLoader().isCachingOn()) + { + globalCache.put(resourceKey, resource); + } + } + } + + if (resource.isSourceModified()) + { + /* + * now check encoding info. It's possible that the newly declared + * encoding is different than the encoding already in the resource + * this strikes me as bad... + */ + + if (!StringUtils.equals(resource.getEncoding(), encoding)) + { + log.warn("Declared encoding for template '{}' is different on reload. Old = '{}' New = '{}'", + resource.getName(), resource.getEncoding(), encoding); + resource.setEncoding(encoding); + } + + /* + * read how old the resource is _before_ + * processing (=>reading) it + */ + long howOldItWas = loader.getLastModified(resource); + + /* + * we create a copy to avoid partially overwriting a + * template which may be in use in another thread + */ + + Resource newResource = + ResourceFactory.getResource(resource.getName(), resource.getType()); + + newResource.setRuntimeServices(rsvc); + newResource.setName(resource.getName()); + newResource.setEncoding(resource.getEncoding()); + newResource.setResourceLoader(loader); + newResource.setModificationCheckInterval(loader.getModificationCheckInterval()); + + newResource.process(); + newResource.setLastModified(howOldItWas); + resource = newResource; + resource.touch(); + + globalCache.put(resourceKey, newResource); + } + return resource; + } + + /** + * Determines if a template exists, and returns name of the loader that provides it. This is a slightly less hokey way to + * support the Velocity.templateExists() utility method, which was broken when per-template encoding was introduced. We can + * revisit this. + * + * @param resourceName Name of template or content resource + * + * @return class name of loader than can provide it + */ + @Override + public String getLoaderNameForResource(String resourceName) + { + ResourceLoader loader = getLoaderForResource(resourceName); + if (loader == null) + { + return null; + } + return loader.getClass().toString(); + } + + /** + * Returns the first {@link ResourceLoader} in which the specified + * resource exists. + */ + private ResourceLoader getLoaderForResource(String resourceName) + { + for (ResourceLoader loader : resourceLoaders) + { + if (loader.resourceExists(resourceName)) + { + return loader; + } + } + return null; + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ClasspathResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ClasspathResourceLoader.java new file mode 100644 index 00000000..9bfd0f14 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ClasspathResourceLoader.java @@ -0,0 +1,169 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.ExtProperties; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/** + * ClasspathResourceLoader is a simple loader that will load + * templates from the classpath. + * <br> + * <br> + * Will load templates from from multiple instances of + * and arbitrary combinations of: + * <ul> + * <li> jar files + * <li> zip files + * <li> template directories (any directory containing templates) + * </ul> + * This is a configuration-free loader, in that there are no + * parameters to be specified in the configuration properties, + * other than specifying this as the loader to use. For example + * the following is all that the loader needs to be functional: + * <br> + * <pre><code> + * resource.loaders = class + * resource.loader.class.class =org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader + * </code></pre> + * <br> + * <br> + * To use, put your template directories, jars + * and zip files into the classpath or other mechanisms that make + * resources accessible to the classloader. + * <br> + * <br> + * This makes deployment trivial for web applications running in + * any Servlet 2.2 compliant servlet runner, such as Tomcat 3.2 + * and others. + * <br> + * <br> + * For a Servlet Spec v2.2 servlet runner, + * just drop the jars of template files into the WEB-INF/lib + * directory of your webapp, and you won't have to worry about setting + * template paths or altering them with the root of the webapp + * before initializing. + * <br> + * <br> + * I have also tried it with a WAR deployment, and that seemed to + * work just fine. + * + * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public class ClasspathResourceLoader extends ResourceLoader +{ + + /** + * This is abstract in the base class, so we need it + * @param configuration + */ + @Override + public void init(ExtProperties configuration) + { + log.trace("ClasspathResourceLoader: initialization complete."); + } + + /** + * Get a Reader so that the Runtime can build a + * template with it. + * + * @param name name of template to get + * @param encoding asked encoding + * @return InputStream containing the template + * @throws ResourceNotFoundException if template not found + * in classpath. + * @since 2.0 + */ + @Override + public Reader getResourceReader(String name, String encoding ) + throws ResourceNotFoundException + { + Reader result = null; + + if (StringUtils.isEmpty(name)) + { + throw new ResourceNotFoundException ("No template name provided"); + } + + /* + * look for resource in thread classloader first (e.g. WEB-INF\lib in + * a servlet container) then fall back to the system classloader. + */ + + InputStream rawStream = null; + try + { + rawStream = ClassUtils.getResourceAsStream( getClass(), name ); + if (rawStream != null) + { + result = buildReader(rawStream, encoding); + } + } + catch( Exception fnfe ) + { + if (rawStream != null) + { + try + { + rawStream.close(); + } + catch (IOException ioe) {} + } + throw new ResourceNotFoundException("ClasspathResourceLoader problem with template: " + name, fnfe, rsvc.getLogContext().getStackTrace() ); + } + + if (result == null) + { + String msg = "ClasspathResourceLoader Error: cannot find resource " + name; + + throw new ResourceNotFoundException( msg, null, rsvc.getLogContext().getStackTrace() ); + } + + return result; + } + + /** + * @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public boolean isSourceModified(Resource resource) + { + return false; + } + + /** + * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public long getLastModified(Resource resource) + { + return 0; + } +} + diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/DataSourceResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/DataSourceResourceLoader.java new file mode 100644 index 00000000..b9cc48cc --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/DataSourceResourceLoader.java @@ -0,0 +1,550 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.ExtProperties; + +import org.apache.commons.lang3.StringUtils; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.sql.DataSource; +import java.io.FilterReader; +import java.io.IOException; +import java.io.Reader; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; + +/** + * <p>This is a simple template file loader that loads templates + * from a DataSource instead of plain files.</p> + * + * <p>It can be configured with a datasource name, a table name, + * id column (name), content column (the template body) and a + * datetime column (for last modification info).</p> + * <br> + * <p>Example configuration snippet for velocity.properties:</p> + * <br> + * <pre><code> + * resource.loaders = file, ds + * + * resource.loader.ds.description = Velocity DataSource Resource Loader <br> + * resource.loader.ds.class = org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader <br> + * resource.loader.ds.resource.datasource_url = java:comp/env/jdbc/Velocity <br> + * resource.loader.ds.resource.table = tb_velocity_template <br> + * resource.loader.ds.resource.key_column = id_template <br> + * resource.loader.ds.resource.template_column = template_definition <br> + * resource.loader.ds.resource.timestamp_column = template_timestamp <br> + * resource.loader.ds.cache = false <br> + * resource.loader.ds.modification_check_interval = 60 <br> + * </code></pre> + * <p>Optionally, the developer can instantiate the DataSourceResourceLoader and set the DataSource via code in + * a manner similar to the following:</p> + * <br> + * <pre><code> + * DataSourceResourceLoader ds = new DataSourceResourceLoader(); + * ds.setDataSource(DATASOURCE); + * Velocity.setProperty("resource.loader.ds.instance",ds); + * </code></pre> + * <p> The property <code>resource.loader.ds.class</code> should be left out, otherwise all the other + * properties in velocity.properties would remain the same.</p> + * <br> + * <p>Example WEB-INF/web.xml:</p> + * <br> + * <pre><code> + * <resource-ref> + * <description>Velocity template DataSource</description> + * <res-ref-name>jdbc/Velocity</res-ref-name> + * <res-type>javax.sql.DataSource</res-type> + * <res-auth>Container</res-auth> + * </resource-ref> + * </code></pre> + * <br> + * and Tomcat 4 server.xml file: <br> + * <pre><code> + * [...] + * <Context path="/exampleVelocity" docBase="exampleVelocity" debug="0"> + * [...] + * <ResourceParams name="jdbc/Velocity"> + * <parameter> + * <name>driverClassName</name> + * <value>org.hsql.jdbcDriver</value> + * </parameter> + * <parameter> + * <name>driverName</name> + * <value>jdbc:HypersonicSQL:database</value> + * </parameter> + * <parameter> + * <name>user</name> + * <value>database_username</value> + * </parameter> + * <parameter> + * <name>password</name> + * <value>database_password</value> + * </parameter> + * </ResourceParams> + * [...] + * </Context> + * [...] + * </code></pre> + * <br> + * <p>Example sql script:</p> + * <pre><code> + * CREATE TABLE tb_velocity_template ( + * id_template varchar (40) NOT NULL , + * template_definition text (16) NOT NULL , + * template_timestamp datetime NOT NULL + * ); + * </code></pre> + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a> + * @author <a href="mailto:david.kinnvall@alertir.com">David Kinnvall</a> + * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a> + * @author <a href="mailto:lachiewicz@plusnet.pl">Sylwester Lachiewicz</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class DataSourceResourceLoader extends ResourceLoader +{ + private String dataSourceName; + private String tableName; + private String keyColumn; + private String templateColumn; + private String timestampColumn; + private InitialContext ctx; + private DataSource dataSource; + + /* + Keep connection and prepared statements open. It's not just an optimization: + For several engines, the connection, and/or the statement, and/or the result set + must be kept open for the reader to be valid. + */ + private Connection connection = null; + private PreparedStatement templatePrepStatement = null; + private PreparedStatement timestampPrepStatement = null; + + private static class SelfCleaningReader extends FilterReader + { + private ResultSet resultSet; + + public SelfCleaningReader(Reader reader, ResultSet resultSet) + { + super(reader); + this.resultSet = resultSet; + } + + @Override + public void close() throws IOException + { + super.close(); + try + { + resultSet.close(); + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception e) + { + // ignore + } + } + } + + /** + * @see ResourceLoader#init(org.apache.velocity.util.ExtProperties) + */ + @Override + public void init(ExtProperties configuration) + { + dataSourceName = StringUtils.trim(configuration.getString("datasource_url")); + tableName = StringUtils.trim(configuration.getString("resource.table")); + keyColumn = StringUtils.trim(configuration.getString("resource.key_column")); + templateColumn = StringUtils.trim(configuration.getString("resource.template_column")); + timestampColumn = StringUtils.trim(configuration.getString("resource.timestamp_column")); + + if (dataSource != null) + { + log.debug("DataSourceResourceLoader: using dataSource instance with table \"{}\"", tableName); + log.debug("DataSourceResourceLoader: using columns \"{}\", \"{}\" and \"{}\"", keyColumn, templateColumn, timestampColumn); + + log.trace("DataSourceResourceLoader initialized."); + } + else if (dataSourceName != null) + { + log.debug("DataSourceResourceLoader: using \"{}\" datasource with table \"{}\"", dataSourceName, tableName); + log.debug("DataSourceResourceLoader: using columns \"{}\", \"{}\" and \"{}\"", keyColumn, templateColumn, timestampColumn); + + log.trace("DataSourceResourceLoader initialized."); + } + else + { + String msg = "DataSourceResourceLoader not properly initialized. No DataSource was identified."; + log.error(msg); + throw new RuntimeException(msg); + } + } + + /** + * Set the DataSource used by this resource loader. Call this as an alternative to + * specifying the data source name via properties. + * @param dataSource The data source for this ResourceLoader. + */ + public void setDataSource(final DataSource dataSource) + { + this.dataSource = dataSource; + } + + /** + * @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public boolean isSourceModified(final Resource resource) + { + return (resource.getLastModified() != + readLastModified(resource, "checking timestamp")); + } + + /** + * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public long getLastModified(final Resource resource) + { + return readLastModified(resource, "getting timestamp"); + } + + /** + * Get an InputStream so that the Runtime can build a + * template with it. + * + * @param name name of template + * @param encoding asked encoding + * @return InputStream containing template + * @throws ResourceNotFoundException + * @since 2.0 + */ + @Override + public synchronized Reader getResourceReader(final String name, String encoding) + throws ResourceNotFoundException + { + if (StringUtils.isEmpty(name)) + { + throw new ResourceNotFoundException("DataSourceResourceLoader: Template name was empty or null"); + } + + ResultSet rs = null; + try + { + checkDBConnection(); + rs = fetchResult(templatePrepStatement, name); + + if (rs.next()) + { + Reader reader = getReader(rs, templateColumn, encoding); + if (reader == null) + { + throw new ResourceNotFoundException("DataSourceResourceLoader: " + + "template column for '" + + name + "' is null"); + } + return new SelfCleaningReader(reader, rs); + } + else + { + throw new ResourceNotFoundException("DataSourceResourceLoader: " + + "could not find resource '" + + name + "'"); + + } + } + catch (SQLException | NamingException sqle) + { + String msg = "DataSourceResourceLoader: database problem while getting resource '" + + name + "': "; + + log.error(msg, sqle); + throw new ResourceNotFoundException(msg); + } + } + + /** + * Fetches the last modification time of the resource + * + * @param resource Resource object we are finding timestamp of + * @param operation string for logging, indicating caller's intention + * + * @return timestamp as long + */ + private long readLastModified(final Resource resource, final String operation) + { + long timeStamp = 0; + + /* get the template name from the resource */ + String name = resource.getName(); + if (name == null || name.length() == 0) + { + String msg = "DataSourceResourceLoader: Template name was empty or null"; + log.error(msg); + throw new NullPointerException(msg); + } + else + { + ResultSet rs = null; + + try + { + checkDBConnection(); + rs = fetchResult(timestampPrepStatement, name); + + if (rs.next()) + { + Timestamp ts = rs.getTimestamp(timestampColumn); + timeStamp = ts != null ? ts.getTime() : 0; + } + else + { + String msg = "DataSourceResourceLoader: could not find resource " + + name + " while " + operation; + log.error(msg); + throw new ResourceNotFoundException(msg); + } + } + catch (SQLException | NamingException sqle) + { + String msg = "DataSourceResourceLoader: database problem while " + + operation + " of '" + name + "': "; + + log.error(msg, sqle); + throw new VelocityException(msg, sqle, rsvc.getLogContext().getStackTrace()); + } + finally + { + closeResultSet(rs); + } + } + return timeStamp; + } + + /** + * Gets connection to the datasource specified through the configuration + * parameters. + * + */ + private void openDBConnection() throws NamingException, SQLException + { + if (dataSource == null) + { + if (ctx == null) + { + ctx = new InitialContext(); + } + + dataSource = (DataSource) ctx.lookup(dataSourceName); + } + + if (connection != null) + { + closeDBConnection(); + } + + connection = dataSource.getConnection(); + templatePrepStatement = prepareStatement(connection, templateColumn, tableName, keyColumn); + timestampPrepStatement = prepareStatement(connection, timestampColumn, tableName, keyColumn); + } + + /** + * Checks the connection is valid + * + */ + private void checkDBConnection() throws NamingException, SQLException + { + if (connection == null || !connection.isValid(0)) + { + openDBConnection(); + } + } + + /** + * Close DB connection on finalization + * + * @throws Throwable + */ + @Override + protected void finalize() + throws Throwable + { + closeDBConnection(); + } + + /** + * Closes the prepared statements and the connection to the datasource + */ + private void closeDBConnection() + { + if (templatePrepStatement != null) + { + try + { + templatePrepStatement.close(); + } + catch (RuntimeException re) + { + throw re; + } + catch (SQLException e) + { + // ignore + } + finally + { + templatePrepStatement = null; + } + } + if (timestampPrepStatement != null) + { + try + { + timestampPrepStatement.close(); + } + catch (RuntimeException re) + { + throw re; + } + catch (SQLException e) + { + // ignore + } + finally + { + timestampPrepStatement = null; + } + } + if (connection != null) + { + try + { + connection.close(); + } + catch (RuntimeException re) + { + throw re; + } + catch (SQLException e) + { + // ignore + } + finally + { + connection = null; + } + } + } + + /** + * Closes the result set. + */ + private void closeResultSet(final ResultSet rs) + { + if (rs != null) + { + try + { + rs.close(); + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception e) + { + // ignore + } + } + } + + /** + * Creates the following PreparedStatement query : + * <br> + * SELECT <i>columnNames</i> FROM <i>tableName</i> WHERE <i>keyColumn</i> + * = '<i>templateName</i>' + * <br> + * where <i>keyColumn</i> is a class member set in init() + * + * @param conn connection to datasource + * @param columnNames columns to fetch from datasource + * @param tableName table to fetch from + * @param keyColumn column whose value should match templateName + * @return PreparedStatement + * @throws SQLException + */ + protected PreparedStatement prepareStatement( + final Connection conn, + final String columnNames, + final String tableName, + final String keyColumn + ) throws SQLException + { + PreparedStatement ps = conn.prepareStatement("SELECT " + columnNames + " FROM "+ tableName + " WHERE " + keyColumn + " = ?"); + return ps; + } + + /** + * Fetches the result for a given template name. + * Inherit this method if there is any calculation to perform on the template name. + * + * @param ps target prepared statement + * @param templateName input template name + * @return result set + * @throws SQLException + */ + protected ResultSet fetchResult( + final PreparedStatement ps, + final String templateName + ) throws SQLException + { + ps.setString(1, templateName); + return ps.executeQuery(); + } + + /** + * Gets a reader from a result set's column + * @param resultSet + * @param column + * @param encoding + * @return reader + * @throws SQLException + */ + protected Reader getReader(ResultSet resultSet, String column, String encoding) + throws SQLException + { + return resultSet.getCharacterStream(column); + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/FileResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/FileResourceLoader.java new file mode 100644 index 00000000..ba9e5f98 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/FileResourceLoader.java @@ -0,0 +1,373 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.ExtProperties; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +/** + * A loader for templates stored on the file system. Treats the template + * as relative to the configured root path. If the root path is empty + * treats the template name as an absolute path. + * + * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> + * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a> + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @version $Id$ + */ +public class FileResourceLoader extends ResourceLoader +{ + /** + * The paths to search for templates. + */ + private List<String> paths = new ArrayList<>(); + + /** + * Used to map the path that a template was found on + * so that we can properly check the modification + * times of the files. This is synchronizedMap + * instance. + */ + private Map<String, String> templatePaths = Collections.synchronizedMap(new HashMap<>()); + + /** + * @see ResourceLoader#init(org.apache.velocity.util.ExtProperties) + */ + @Override + public void init(ExtProperties configuration) + { + log.trace("FileResourceLoader: initialization starting."); + + paths.addAll( configuration.getVector(RuntimeConstants.RESOURCE_LOADER_PATHS) ); + + // trim spaces from all paths + for (ListIterator<String> it = paths.listIterator(); it.hasNext(); ) + { + String path = StringUtils.trim(it.next()); + it.set(path); + log.debug("FileResourceLoader: adding path '{}'", path); + } + log.trace("FileResourceLoader: initialization complete."); + } + + /** + * Get a Reader so that the Runtime can build a + * template with it. + * + * @param templateName name of template to get + * @return Reader containing the template + * @throws ResourceNotFoundException if template not found + * in the file template path. + * @since 2.0 + */ + @Override + public Reader getResourceReader(String templateName, String encoding) + throws ResourceNotFoundException + { + /* + * Make sure we have a valid templateName. + */ + if (org.apache.commons.lang3.StringUtils.isEmpty(templateName)) + { + /* + * If we don't get a properly formed templateName then + * there's not much we can do. So we'll forget about + * trying to search any more paths for the template. + */ + throw new ResourceNotFoundException( + "Need to specify a file name or file path!"); + } + + String template = FilenameUtils.normalize( templateName, true ); + if ( template == null || template.length() == 0 ) + { + String msg = "File resource error: argument " + template + + " contains .. and may be trying to access " + + "content outside of template root. Rejected."; + + log.error("FileResourceLoader: {}", msg); + + throw new ResourceNotFoundException ( msg ); + } + + int size = paths.size(); + for (String path : paths) + { + InputStream rawStream = null; + Reader reader = null; + + try + { + rawStream = findTemplate(path, template); + if (rawStream != null) + { + reader = buildReader(rawStream, encoding); + } + } + catch (IOException ioe) + { + closeQuiet(rawStream); + String msg = "Exception while loading Template " + template; + log.error(msg, ioe); + throw new VelocityException(msg, ioe, rsvc.getLogContext().getStackTrace()); + } + if (reader != null) + { + /* + * Store the path that this template came + * from so that we can check its modification + * time. + */ + templatePaths.put(templateName, path); + return reader; + } + } + + /* + * We have now searched all the paths for + * templates and we didn't find anything so + * throw an exception. + */ + throw new ResourceNotFoundException("FileResourceLoader: cannot find " + template); + } + + /** + * Overrides superclass for better performance. + * @since 1.6 + */ + @Override + public boolean resourceExists(String name) + { + if (name == null) + { + return false; + } + name = FilenameUtils.normalize(name); + if (name == null || name.length() == 0) + { + return false; + } + + int size = paths.size(); + for (String path : paths) + { + try + { + File file = getFile(path, name); + if (file.canRead()) + { + return true; + } + } + catch (Exception ioe) + { + log.debug("Exception while checking for template {}", name); + } + } + return false; + } + + /** + * Try to find a template given a normalized path. + * + * @param path a normalized path + * @param template name of template to find + * @return InputStream input stream that will be parsed + * + */ + private InputStream findTemplate(final String path, final String template) + throws IOException + { + try + { + File file = getFile(path, template); + + if (file.canRead()) + { + FileInputStream fis = null; + try + { + fis = new FileInputStream(file.getAbsolutePath()); + return fis; + } + catch (IOException e) + { + closeQuiet(fis); + throw e; + } + } + else + { + return null; + } + } + catch(FileNotFoundException fnfe) + { + /* + * log and convert to a general Velocity ResourceNotFoundException + */ + return null; + } + } + + private void closeQuiet(final InputStream is) + { + if (is != null) + { + try + { + is.close(); + } + catch(IOException ioe) + { + // Ignore + } + } + } + + /** + * How to keep track of all the modified times + * across the paths. Note that a file might have + * appeared in a directory which is earlier in the + * path; so we should search the path and see if + * the file we find that way is the same as the one + * that we have cached. + * @param resource + * @return True if the source has been modified. + */ + @Override + public boolean isSourceModified(Resource resource) + { + /* + * we assume that the file needs to be reloaded; + * if we find the original file and it's unchanged, + * then we'll flip this. + */ + boolean modified = true; + + String fileName = resource.getName(); + String path = templatePaths.get(fileName); + File currentFile = null; + + for (int i = 0; currentFile == null && i < paths.size(); i++) + { + String testPath = (String) paths.get(i); + File testFile = getFile(testPath, fileName); + if (testFile.canRead()) + { + currentFile = testFile; + } + } + File file = getFile(path, fileName); + if (currentFile == null || !file.exists()) + { + /* + * noop: if the file is missing now (either the cached + * file is gone, or the file can no longer be found) + * then we leave modified alone (it's set to true); a + * reload attempt will be done, which will either use + * a new template or fail with an appropriate message + * about how the file couldn't be found. + */ + } + else if (currentFile.equals(file) && file.canRead()) + { + /* + * if only if currentFile is the same as file and + * file.lastModified() is the same as + * resource.getLastModified(), then we should use the + * cached version. + */ + modified = (file.lastModified() != resource.getLastModified()); + } + + /* + * rsvc.debug("isSourceModified for " + fileName + ": " + modified); + */ + return modified; + } + + /** + * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public long getLastModified(Resource resource) + { + String path = templatePaths.get(resource.getName()); + File file = getFile(path, resource.getName()); + + if (file.canRead()) + { + return file.lastModified(); + } + else + { + return 0; + } + } + + + /** + * Create a File based on either a relative path if given, or absolute path otherwise + */ + private File getFile(String path, String template) + { + + File file = null; + + if("".equals(path)) + { + file = new File( template ); + } + else + { + /* + * if a / leads off, then just nip that :) + */ + if (template.startsWith("/")) + { + template = template.substring(1); + } + + file = new File ( path, template ); + } + + return file; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarHolder.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarHolder.java new file mode 100644 index 00000000..3ccdc944 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarHolder.java @@ -0,0 +1,169 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeServices; + +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * A small wrapper around a Jar + * + * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a> + * @version $Id$ + */ +public class JarHolder +{ + private String urlpath = null; + private JarFile theJar = null; + private JarURLConnection conn = null; + + private Logger log = null; + + /** + * @param rs + * @param urlpath + * @param log + */ + public JarHolder( RuntimeServices rs, String urlpath, Logger log ) + { + this.log = log; + + this.urlpath=urlpath; + init(); + + log.debug("JarHolder: initialized JAR: {}", urlpath); + } + + /** + * + */ + public void init() + { + try + { + log.debug("JarHolder: attempting to connect to {}", urlpath); + + URL url = new URL( urlpath ); + conn = (JarURLConnection) url.openConnection(); + conn.setAllowUserInteraction(false); + conn.setDoInput(true); + conn.setDoOutput(false); + conn.connect(); + theJar = conn.getJarFile(); + } + catch (IOException ioe) + { + String msg = "JarHolder: error establishing connection to JAR at \"" + + urlpath + "\""; + log.error(msg, ioe); + throw new VelocityException(msg, ioe); + } + } + + /** + * + */ + public void close() + { + try + { + theJar.close(); + } + catch ( Exception e ) + { + String msg = "JarHolder: error closing the JAR file"; + log.error(msg, e); + throw new VelocityException(msg, e); + } + theJar = null; + conn = null; + + log.trace("JarHolder: JAR file closed"); + } + + /** + * @param theentry + * @return The requested resource. + * @throws ResourceNotFoundException + */ + public InputStream getResource( String theentry ) + throws ResourceNotFoundException { + InputStream data = null; + + try + { + JarEntry entry = theJar.getJarEntry( theentry ); + + if ( entry != null ) + { + data = theJar.getInputStream( entry ); + } + } + catch(Exception fnfe) + { + log.error("JarHolder: getResource() error", fnfe); + throw new ResourceNotFoundException(fnfe); + } + + return data; + } + + /** + * @return The entries of the jar as a hashtable. + */ + public Map<String, String> getEntries() + { + Map<String, String> allEntries = new HashMap<>(559); + + Enumeration<JarEntry> all = theJar.entries(); + while ( all.hasMoreElements() ) + { + JarEntry je = all.nextElement(); + + // We don't map plain directory entries + if ( !je.isDirectory() ) + { + allEntries.put( je.getName(), this.urlpath ); + } + } + return allEntries; + } + + /** + * @return The URL path of this jar holder. + */ + public String getUrlPath() + { + return urlpath; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarResourceLoader.java new file mode 100644 index 00000000..23db3743 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarResourceLoader.java @@ -0,0 +1,263 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.ExtProperties; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +/** + * <p> + * ResourceLoader to load templates from multiple Jar files. + * </p> + * <p> + * The configuration of the JarResourceLoader is straightforward - + * You simply add the JarResourceLoader to the configuration via + * </p> + * <pre><code> + * resource.loaders = jar + * resource.loader.jar.class = org.apache.velocity.runtime.resource.loader.JarResourceLoader + * resource.loader.jar.path = list of JAR <URL>s + * </code></pre> + * + * <p> So for example, if you had a jar file on your local filesystem, you could simply do</p> + * <pre><code> + * resource.loader.jar.path = jar:file:/opt/myfiles/jar1.jar + * </code></pre> + * <p> Note that jar specification for the <code>.path</code> configuration property + * conforms to the same rules for the java.net.JarUrlConnection class. + * </p> + * + * <p> For a working example, see the unit test case, + * org.apache.velocity.test.MultiLoaderTestCase class + * </p> + * + * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a> + * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a> + * @version $Id$ + */ +public class JarResourceLoader extends ResourceLoader +{ + /** + * Maps entries to the parent JAR File + * Key = the entry *excluding* plain directories + * Value = the JAR URL + */ + private Map<String, String> entryDirectory = new HashMap<>(559); + + /** + * Maps JAR URLs to the actual JAR + * Key = the JAR URL + * Value = the JAR + */ + private Map<String, JarHolder> jarfiles = new HashMap<>(89); + + /** + * Called by Velocity to initialize the loader + * @param configuration + */ + @Override + public void init(ExtProperties configuration) + { + log.trace("JarResourceLoader: initialization starting."); + + List<String> paths = configuration.getList(RuntimeConstants.RESOURCE_LOADER_PATHS); + + if (paths != null) + { + log.debug("JarResourceLoader # of paths: {}", paths.size() ); + + for (ListIterator<String> it = paths.listIterator(); it.hasNext(); ) + { + String jar = StringUtils.trim(it.next()); + it.set(jar); + loadJar(jar); + } + } + + log.trace("JarResourceLoader: initialization complete."); + } + + private void loadJar( String path ) + { + log.debug("JarResourceLoader: trying to load \"{}\"", path); + + // Check path information + if ( path == null ) + { + String msg = "JarResourceLoader: can not load JAR - JAR path is null"; + log.error(msg); + throw new RuntimeException(msg); + } + if ( !path.startsWith("jar:") ) + { + String msg = "JarResourceLoader: JAR path must start with jar: -> see java.net.JarURLConnection for information"; + log.error(msg); + throw new RuntimeException(msg); + } + if (!path.contains("!/")) + { + path += "!/"; + } + + // Close the jar if it's already open + // this is useful for a reload + closeJar( path ); + + // Create a new JarHolder + JarHolder temp = new JarHolder( rsvc, path, log ); + // Add it's entries to the entryCollection + addEntries(temp.getEntries()); + // Add it to the Jar table + jarfiles.put(temp.getUrlPath(), temp); + } + + /** + * Closes a Jar file and set its URLConnection + * to null. + */ + private void closeJar( String path ) + { + if ( jarfiles.containsKey(path) ) + { + JarHolder theJar = jarfiles.get(path); + theJar.close(); + } + } + + /** + * Copy all the entries into the entryDirectory + * It will overwrite any duplicate keys. + */ + private void addEntries( Map<String, String> entries ) + { + entryDirectory.putAll( entries ); + } + + /** + * Get a Reader so that the Runtime can build a + * template with it. + * + * @param source name of template to get + * @param encoding asked encoding + * @return InputStream containing the template + * @throws ResourceNotFoundException if template not found + * in the file template path. + * @since 2.0 + */ + @Override + public Reader getResourceReader(String source, String encoding ) + throws ResourceNotFoundException + { + Reader result = null; + + if (org.apache.commons.lang3.StringUtils.isEmpty(source)) + { + throw new ResourceNotFoundException("Need to have a resource!"); + } + + String normalizedPath = FilenameUtils.normalize( source, true ); + + if ( normalizedPath == null || normalizedPath.length() == 0 ) + { + String msg = "JAR resource error: argument " + normalizedPath + + " contains .. and may be trying to access " + + "content outside of template root. Rejected."; + + log.error( "JarResourceLoader: {}", msg ); + + throw new ResourceNotFoundException ( msg ); + } + + /* + * if a / leads off, then just nip that :) + */ + if ( normalizedPath.startsWith("/") ) + { + normalizedPath = normalizedPath.substring(1); + } + + if ( entryDirectory.containsKey( normalizedPath ) ) + { + String jarurl = entryDirectory.get( normalizedPath ); + + if ( jarfiles.containsKey( jarurl ) ) + { + JarHolder holder = (JarHolder)jarfiles.get( jarurl ); + InputStream rawStream = holder.getResource( normalizedPath ); + try + { + return buildReader(rawStream, encoding); + } + catch (Exception e) + { + if (rawStream != null) + { + try + { + rawStream.close(); + } + catch (IOException ioe) {} + } + String msg = "JAR resource error: Exception while loading " + source; + log.error(msg, e); + throw new VelocityException(msg, e, rsvc.getLogContext().getStackTrace()); + } + } + } + + throw new ResourceNotFoundException( "JarResourceLoader Error: cannot find resource " + + source ); + + } + + // TODO: SHOULD BE DELEGATED TO THE JARHOLDER + + /** + * @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public boolean isSourceModified(Resource resource) + { + return true; + } + + /** + * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public long getLastModified(Resource resource) + { + return 0; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoader.java new file mode 100644 index 00000000..9a2d5643 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoader.java @@ -0,0 +1,324 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.io.UnicodeInputStream; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.runtime.resource.ResourceCacheImpl; +import org.apache.velocity.util.ExtProperties; + +import org.slf4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; + +/** + * This is abstract class the all text resource loaders should + * extend. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a> + * @version $Id$ + */ +public abstract class ResourceLoader +{ + /** + * Does this loader want templates produced with it + * cached in the Runtime. + */ + protected boolean isCachingOn = false; + + /** + * This property will be passed on to the templates + * that are created with this loader. + */ + protected long modificationCheckInterval = 2; + + /** + * Class name for this loader, for logging/debuggin + * purposes. + */ + protected String className = null; + + protected RuntimeServices rsvc = null; + protected Logger log = null; + + /** + * This initialization is used by all resource + * loaders and must be called to set up common + * properties shared by all resource loaders + * + * @param rs + * @param configuration + */ + public void commonInit(RuntimeServices rs, ExtProperties configuration) + { + this.rsvc = rs; + String loaderName = configuration.getString(RuntimeConstants.RESOURCE_LOADER_IDENTIFIER); + log = rsvc.getLog("loader." + (loaderName == null ? this.getClass().getSimpleName() : loaderName)); + + /* + * these two properties are not required for all loaders. + * For example, for ClasspathLoader, what would cache mean? + * so adding default values which I think are the safest + * + * don't cache, and modCheckInterval irrelevant... + */ + + try + { + isCachingOn = configuration.getBoolean(RuntimeConstants.RESOURCE_LOADER_CACHE, false); + } + catch (Exception e) + { + isCachingOn = false; + String msg = "Exception parsing cache setting: " + configuration.getString(RuntimeConstants.RESOURCE_LOADER_CACHE); + log.error(msg, e); + throw new VelocityException(msg, e); + } + try + { + modificationCheckInterval = configuration.getLong(RuntimeConstants.RESOURCE_LOADER_CHECK_INTERVAL, 0); + } + catch (Exception e) + { + modificationCheckInterval = 0; + String msg = "Exception parsing modificationCheckInterval setting: " + RuntimeConstants.RESOURCE_LOADER_CHECK_INTERVAL; + log.error(msg, e); + throw new VelocityException(msg, e); + } + + /* + * this is a must! + */ + className = ResourceCacheImpl.class.getName(); + try + { + className = configuration.getString(RuntimeConstants.RESOURCE_LOADER_CLASS, className); + } + catch (Exception e) + { + String msg = "Exception retrieving resource cache class name"; + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + + /** + * Initialize the template loader with a + * a resources class. + * + * @param configuration + */ + public abstract void init(ExtProperties configuration); + + /** + * Get the Reader that the Runtime will parse + * to create a template. + * + * @param source + * @param encoding + * @return The reader for the requested resource. + * @throws ResourceNotFoundException + * @since 2.0 + */ + public abstract Reader getResourceReader(String source, String encoding) + throws ResourceNotFoundException; + + /** + * Given a template, check to see if the source of InputStream + * has been modified. + * + * @param resource + * @return True if the resource has been modified. + */ + public abstract boolean isSourceModified(Resource resource); + + /** + * Get the last modified time of the InputStream source + * that was used to create the template. We need the template + * here because we have to extract the name of the template + * in order to locate the InputStream source. + * + * @param resource + * @return Time in millis when the resource has been modified. + */ + public abstract long getLastModified(Resource resource); + + /** + * Return the class name of this resource Loader + * + * @return Class name of the resource loader. + */ + public String getClassName() + { + return className; + } + + /** + * Set the caching state. If true, then this loader + * would like the Runtime to cache templates that + * have been created with InputStreams provided + * by this loader. + * + * @param value + */ + public void setCachingOn(boolean value) + { + isCachingOn = value; + } + + /** + * The Runtime uses this to find out whether this + * template loader wants the Runtime to cache + * templates created with InputStreams provided + * by this loader. + * + * @return True if this resource loader caches. + */ + public boolean isCachingOn() + { + return isCachingOn; + } + + /** + * Set the interval at which the InputStream source + * should be checked for modifications. + * + * @param modificationCheckInterval + */ + public void setModificationCheckInterval(long modificationCheckInterval) + { + this.modificationCheckInterval = modificationCheckInterval; + } + + /** + * Get the interval at which the InputStream source + * should be checked for modifications. + * + * @return The modification check interval. + */ + public long getModificationCheckInterval() + { + return modificationCheckInterval; + } + + /** + * Check whether any given resource exists. This is not really + * a very efficient test and it can and should be overridden in the + * subclasses extending ResourceLoader2. + * + * @param resourceName The name of a resource. + * @return true if a resource exists and can be accessed. + * @since 1.6 + */ + public boolean resourceExists(final String resourceName) + { + Reader reader = null; + try + { + reader = getResourceReader(resourceName, null); + } + catch (ResourceNotFoundException e) + { + log.debug("Could not load resource '{}' from ResourceLoader {}", + resourceName, this.getClass().getName()); + } + finally + { + try + { + if (reader != null) + { + reader.close(); + } + } + catch (Exception e) + { + String msg = "While closing InputStream for resource '" + + resourceName + "' from ResourceLoader " + + this.getClass().getName(); + log.error(msg, e); + throw new VelocityException(msg, e); + } + } + return (reader != null); + } + + /** + * Builds a Reader given a raw InputStream and an encoding. Should be use + * by every subclass that whishes to accept optional BOMs in resources. + * This method does *not* close the given input stream whenever an exception is thrown. + * + * @param rawStream The raw input stream. + * @param encoding The asked encoding. + * @return found reader + * @throws IOException + * @throws UnsupportedEncodingException + * @since 2.0 + */ + protected Reader buildReader(InputStream rawStream, String encoding) + throws IOException + { + UnicodeInputStream inputStream = new UnicodeInputStream(rawStream); + /* + * Check encoding + */ + String foundEncoding = inputStream.getEncodingFromStream(); + if (foundEncoding != null && encoding != null && !UnicodeInputStream.sameEncoding(foundEncoding, encoding)) + { + log.warn("Found BOM encoding '{}' differs from asked encoding: '{}' - using BOM encoding to read resource.", foundEncoding, encoding); + encoding = foundEncoding; + } + if (encoding == null) + { + if (foundEncoding == null) + { + encoding = rsvc.getString(RuntimeConstants.INPUT_ENCODING); + } else + { + encoding = foundEncoding; + } + } + + try + { + return new InputStreamReader(inputStream, encoding); + } + catch (UnsupportedEncodingException uee) + { + try + { + inputStream.close(); + } + catch (IOException ioe) {} + throw uee; + } + } + +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoaderFactory.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoaderFactory.java new file mode 100644 index 00000000..09f1c73e --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoaderFactory.java @@ -0,0 +1,62 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeServices; +import org.apache.velocity.util.ClassUtils; + +/** + * Factory to grab a template loader. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @version $Id$ + */ +public class ResourceLoaderFactory +{ + /** + * Gets the loader specified in the configuration file. + * @param rs + * @param loaderClassName + * @return TemplateLoader + */ + public static ResourceLoader getLoader(RuntimeServices rs, String loaderClassName) + { + ResourceLoader loader = null; + + try + { + loader = (ResourceLoader) ClassUtils.getNewInstance( loaderClassName ); + + rs.getLog().debug("ResourceLoader instantiated: {}", loader.getClass().getName()); + + return loader; + } + // The ugly three strike again: ClassNotFoundException,IllegalAccessException,InstantiationException + catch(Exception e) + { + String msg = "Problem instantiating the template loader: "+loaderClassName+"." + System.lineSeparator() + + "Look at your properties file and make sure the" + System.lineSeparator() + + "name of the template loader is correct."; + rs.getLog().error(msg, e); + throw new VelocityException(msg, e); + } + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/StringResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/StringResourceLoader.java new file mode 100644 index 00000000..fd5d8c4a --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/StringResourceLoader.java @@ -0,0 +1,438 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.exception.VelocityException; +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.runtime.resource.util.StringResource; +import org.apache.velocity.runtime.resource.util.StringResourceRepository; +import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl; +import org.apache.velocity.util.ClassUtils; +import org.apache.velocity.util.ExtProperties; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Resource loader that works with Strings. Users should manually add + * resources to the repository that is used by the resource loader instance. + * + * Below is an example configuration for this loader. + * Note that 'repository.class' is not necessary; + * if not provided, the factory will fall back on using + * {@link StringResourceRepositoryImpl} as the default. + * <pre> + * resource.loaders = string + * resource.loader.string.description = Velocity StringResource loader + * resource.loader.string.class = org.apache.velocity.runtime.resource.loader.StringResourceLoader + * resource.loader.string.repository.name = MyRepositoryName (optional, to avoid using the default repository) + * resource.loader.string.repository.class = org.apache.velocity.runtime.resource.loader.StringResourceRepositoryImpl + * </pre> + * Resources can be added to the repository like this: + * <pre><code> + * StringResourceRepository repo = StringResourceLoader.getRepository(); + * + * String myTemplateName = "/some/imaginary/path/hello.vm"; + * String myTemplate = "Hi, ${username}... this is some template!"; + * repo.putStringResource(myTemplateName, myTemplate); + * </code></pre> + * + * After this, the templates can be retrieved as usual. + * <br> + * <p>If there will be multiple StringResourceLoaders used in an application, + * you should consider specifying a 'resource.loader.string.repository.name = foo' + * property in order to keep you string resources in a non-default repository. + * This can help to avoid conflicts between different frameworks or components + * that are using StringResourceLoader. + * You can then retrieve your named repository like this: + * <pre><code> + * StringResourceRepository repo = StringResourceLoader.getRepository("foo"); + * </code></pre> + * <p>and add string resources to the repo just as in the previous example. + * </p> + * <p>If you have concerns about memory leaks or for whatever reason do not wish + * to have your string repository stored statically as a class member, then you + * should set 'resource.loader.string.repository.static = false' in your properties. + * This will tell the resource loader that the string repository should be stored + * in the Velocity application attributes. To retrieve the repository, do:</p> + * <pre><code> + * StringResourceRepository repo = velocityEngine.getApplicationAttribute("foo"); + * </code></pre> + * <p>If you did not specify a name for the repository, then it will be stored under the + * class name of the repository implementation class (for which the default is + * 'org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl'). + * Incidentally, this is also true for the default statically stored repository. + * </p> + * <p>Whether your repository is stored statically or in Velocity's application + * attributes, you can also manually create and set it prior to Velocity + * initialization. For a static repository, you can do something like this: + * <pre><code> + * StringResourceRepository repo = new MyStringResourceRepository(); + * repo.magicallyAddSomeStringResources(); + * StringResourceLoader.setRepository("foo", repo); + * </code></pre> + * <p>Or for a non-static repository:</p> + * <pre><code> + * StringResourceRepository repo = new MyStringResourceRepository(); + * repo.magicallyAddSomeStringResources(); + * velocityEngine.setApplicationAttribute("foo", repo); + * </code></pre> + * <p>Then, assuming the 'resource.loader.string.repository.name' property is + * set to 'some.name', the StringResourceLoader will use that already created + * repository, rather than creating a new one. + * </p> + * + * @author <a href="mailto:eelco.hillenius@openedge.nl">Eelco Hillenius</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @author Nathan Bubna + * @version $Id$ + * @since 1.5 + */ +public class StringResourceLoader extends ResourceLoader +{ + /** + * Key to determine whether the repository should be set as the static one or not. + * @since 1.6 + */ + public static final String REPOSITORY_STATIC = "repository.static"; + + /** + * By default, repositories are stored statically (shared across the VM). + * @since 1.6 + */ + public static final boolean REPOSITORY_STATIC_DEFAULT = true; + + /** Key to look up the repository implementation class. */ + public static final String REPOSITORY_CLASS = "repository.class"; + + /** The default implementation class. */ + public static final String REPOSITORY_CLASS_DEFAULT = + StringResourceRepositoryImpl.class.getName(); + + /** + * Key to look up the name for the repository to be used. + * @since 1.6 + */ + public static final String REPOSITORY_NAME = "repository.name"; + + /** The default name for string resource repositories + * ('org.apache.velocity.runtime.resource.util.StringResourceRepository'). + * @since 1.6 + */ + public static final String REPOSITORY_NAME_DEFAULT = + StringResourceRepository.class.getName(); + + /** Key to look up the repository char encoding. */ + public static final String REPOSITORY_ENCODING = "repository.encoding"; + + protected static final Map<String, StringResourceRepository> STATIC_REPOSITORIES = + Collections.synchronizedMap(new HashMap<>()); + + /** + * Returns a reference to the default static repository. + * @return default static repository + */ + public static StringResourceRepository getRepository() + { + return getRepository(REPOSITORY_NAME_DEFAULT); + } + + /** + * Returns a reference to the repository stored statically under the + * specified name. + * @param name + * @return named repository + * @since 1.6 + */ + public static StringResourceRepository getRepository(String name) + { + return (StringResourceRepository)STATIC_REPOSITORIES.get(name); + } + + /** + * Sets the specified {@link StringResourceRepository} in static storage + * under the specified name. + * @param name + * @param repo + * @since 1.6 + */ + public static void setRepository(String name, StringResourceRepository repo) + { + STATIC_REPOSITORIES.put(name, repo); + } + + /** + * Removes the {@link StringResourceRepository} stored under the specified + * name. + * @param name + * @return removed repository + * @since 1.6 + */ + public static StringResourceRepository removeRepository(String name) + { + return (StringResourceRepository)STATIC_REPOSITORIES.remove(name); + } + + /** + * Removes all statically stored {@link StringResourceRepository}s. + * @since 1.6 + */ + public static void clearRepositories() + { + STATIC_REPOSITORIES.clear(); + } + + + /** + * the repository used internally by this resource loader + */ + protected StringResourceRepository repository; + + + /** + * @param configuration + * @see ResourceLoader#init(org.apache.velocity.util.ExtProperties) + */ + @Override + public void init(final ExtProperties configuration) + { + log.trace("StringResourceLoader: initialization starting."); + + // get the repository configuration info + String repoClass = configuration.getString(REPOSITORY_CLASS, REPOSITORY_CLASS_DEFAULT); + String repoName = configuration.getString(REPOSITORY_NAME, REPOSITORY_NAME_DEFAULT); + boolean isStatic = configuration.getBoolean(REPOSITORY_STATIC, REPOSITORY_STATIC_DEFAULT); + String encoding = configuration.getString(REPOSITORY_ENCODING); + + // look for an existing repository of that name and isStatic setting + if (isStatic) + { + this.repository = getRepository(repoName); + if (repository != null) + { + log.debug("Loaded repository '{}' from static repo store", repoName); + } + } + else + { + this.repository = (StringResourceRepository)rsvc.getApplicationAttribute(repoName); + if (repository != null) + { + log.debug("Loaded repository '{}' from application attributes", repoName); + } + } + + if (this.repository == null) + { + // since there's no repository under the repo name, create a new one + this.repository = createRepository(repoClass, encoding); + + // and store it according to the isStatic setting + if (isStatic) + { + setRepository(repoName, this.repository); + } + else + { + rsvc.setApplicationAttribute(repoName, this.repository); + } + } + else + { + // ok, we already have a repo + // warn them if they are trying to change the class of the repository + if (!this.repository.getClass().getName().equals(repoClass)) + { + log.debug("Cannot change class of string repository '{}' from {} to {}." + + " The change will be ignored.", + repoName, this.repository.getClass().getName(), repoClass); + } + + // allow them to change the default encoding of the repo + if (encoding != null && + !this.repository.getEncoding().equals(encoding)) + { + log.debug("Changing the default encoding of string repository '{}' from {} to {}", + repoName, this.repository.getEncoding(), encoding); + this.repository.setEncoding(encoding); + } + } + + log.trace("StringResourceLoader: initialization complete."); + } + + /** + * @param className + * @param encoding + * @return created repository + * @since 1.6 + */ + public StringResourceRepository createRepository(final String className, + final String encoding) + { + log.debug("Creating string repository using class {}...", className); + + StringResourceRepository repo; + try + { + repo = (StringResourceRepository) ClassUtils.getNewInstance(className); + } + catch (ClassNotFoundException cnfe) + { + throw new VelocityException("Could not find '" + className + "'", cnfe); + } + catch (IllegalAccessException iae) + { + throw new VelocityException("Could not access '" + className + "'", iae); + } + catch (InstantiationException ie) + { + throw new VelocityException("Could not instantiate '" + className + "'", ie); + } + + if (encoding != null) + { + repo.setEncoding(encoding); + } + else + { + repo.setEncoding(RuntimeConstants.ENCODING_DEFAULT); + } + + log.debug("Default repository encoding is {}", repo.getEncoding()); + return repo; + } + + /** + * Overrides superclass for better performance. + * @param name resource name + * @return whether resource exists + * @since 1.6 + */ + @Override + public boolean resourceExists(final String name) + { + if (name == null) + { + return false; + } + return (this.repository.getStringResource(name) != null); + } + + /** + * Get a reader so that the Runtime can build a + * template with it. + * + * @param name name of template to get. + * @param encoding asked encoding + * @return Reader containing the template. + * @throws ResourceNotFoundException Ff template not found + * in the RepositoryFactory. + * @since 2.0 + */ + @Override + public Reader getResourceReader(String name, String encoding) + throws ResourceNotFoundException + { + if (StringUtils.isEmpty(name)) + { + throw new ResourceNotFoundException("No template name provided"); + } + + StringResource resource = this.repository.getStringResource(name); + + if(resource == null) + { + throw new ResourceNotFoundException("Could not locate resource '" + name + "'"); + } + + byte [] byteArray = null; + InputStream rawStream = null; + + try + { + byteArray = resource.getBody().getBytes(resource.getEncoding()); + rawStream = new ByteArrayInputStream(byteArray); + return new InputStreamReader(rawStream, resource.getEncoding()); + } + catch(UnsupportedEncodingException ue) + { + if (rawStream != null) + { + try + { + rawStream.close(); + } + catch (IOException ioe) {} + } + throw new VelocityException("Could not convert String using encoding " + resource.getEncoding(), ue, rsvc.getLogContext().getStackTrace()); + } + } + + /** + * @param resource + * @return whether resource was modified + * @see ResourceLoader#isSourceModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public boolean isSourceModified(final Resource resource) + { + StringResource original = null; + boolean result = true; + + original = this.repository.getStringResource(resource.getName()); + + if (original != null) + { + result = original.getLastModified() != resource.getLastModified(); + } + + return result; + } + + /** + * @param resource + * @return last modified timestamp + * @see ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource) + */ + @Override + public long getLastModified(final Resource resource) + { + StringResource original = null; + + original = this.repository.getStringResource(resource.getName()); + + return (original != null) + ? original.getLastModified() + : 0; + } + +} + diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/URLResourceLoader.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/URLResourceLoader.java new file mode 100644 index 00000000..81fe33b9 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/URLResourceLoader.java @@ -0,0 +1,214 @@ +package org.apache.velocity.runtime.resource.loader; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; +import org.apache.velocity.exception.ResourceNotFoundException; +import org.apache.velocity.runtime.resource.Resource; +import org.apache.velocity.util.ExtProperties; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; + +/** + * This is a simple URL-based loader. + * + * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> + * @author <a href="mailto:nbubna@apache.org">Nathan Bubna</a> + * @version $Id: URLResourceLoader.java 191743 2005-06-21 23:22:20Z dlr $ + * @since 1.5 + */ +public class URLResourceLoader extends ResourceLoader +{ + private String[] roots = null; + protected Map<String, String> templateRoots = null; + private int timeout = -1; + + /** + * @param configuration + * @see ResourceLoader#init(org.apache.velocity.util.ExtProperties) + */ + @Override + public void init(ExtProperties configuration) + { + log.trace("URLResourceLoader: initialization starting."); + + roots = configuration.getStringArray("root"); + if (log.isDebugEnabled()) + { + for (String root : roots) + { + log.debug("URLResourceLoader: adding root '{}'", root); + } + } + + timeout = configuration.getInt("timeout", -1); + + // init the template paths map + templateRoots = new HashMap<>(); + + log.trace("URLResourceLoader: initialization complete."); + } + + /** + * Get a Reader so that the Runtime can build a + * template with it. + * + * @param name name of template to fetch bytestream of + * @param encoding asked encoding + * @return InputStream containing the template + * @throws ResourceNotFoundException if template not found + * in the file template path. + * @since 2.0 + */ + @Override + public synchronized Reader getResourceReader(String name, String encoding) + throws ResourceNotFoundException + { + if (StringUtils.isEmpty(name)) + { + throw new ResourceNotFoundException("URLResourceLoader: No template name provided"); + } + + Reader reader = null; + Exception exception = null; + for (String root : roots) + { + InputStream rawStream = null; + try + { + URL u = new URL(root + name); + URLConnection conn = u.openConnection(); + conn.setConnectTimeout(timeout); + conn.setReadTimeout(timeout); + rawStream = conn.getInputStream(); + reader = buildReader(rawStream, encoding); + + if (reader != null) + { + log.debug("URLResourceLoader: Found '{}' at '{}'", name, root); + + // save this root for later re-use + templateRoots.put(name, root); + break; + } + } + catch (IOException ioe) + { + if (rawStream != null) + { + try + { + rawStream.close(); + } + catch (IOException e) + { + } + } + log.debug("URLResourceLoader: Exception when looking for '{}' at '{}'", name, root, ioe); + + // only save the first one for later throwing + if (exception == null) + { + exception = ioe; + } + } + } + + // if we never found the template + if (reader == null) + { + String msg; + if (exception == null) + { + msg = "URLResourceLoader: Resource '" + name + "' not found."; + } + else + { + msg = exception.getMessage(); + } + // convert to a general Velocity ResourceNotFoundException + throw new ResourceNotFoundException(msg); + } + + return reader; + } + + /** + * Checks to see if a resource has been deleted, moved or modified. + * + * @param resource Resource The resource to check for modification + * @return boolean True if the resource has been modified, moved, or unreachable + */ + @Override + public boolean isSourceModified(Resource resource) + { + long fileLastModified = getLastModified(resource); + // if the file is unreachable or otherwise changed + return fileLastModified == 0 || + fileLastModified != resource.getLastModified(); + } + + /** + * Checks to see when a resource was last modified + * + * @param resource Resource the resource to check + * @return long The time when the resource was last modified or 0 if the file can't be reached + */ + @Override + public long getLastModified(Resource resource) + { + // get the previously used root + String name = resource.getName(); + String root = templateRoots.get(name); + + try + { + // get a connection to the URL + URL u = new URL(root + name); + URLConnection conn = u.openConnection(); + conn.setConnectTimeout(timeout); + conn.setReadTimeout(timeout); + return conn.getLastModified(); + } + catch (IOException ioe) + { + // the file is not reachable at its previous address + String msg = "URLResourceLoader: '"+name+"' is no longer reachable at '"+root+"'"; + log.error(msg, ioe); + throw new ResourceNotFoundException(msg, ioe, rsvc.getLogContext().getStackTrace()); + } + } + + /** + * Returns the current, custom timeout setting. If negative, there is no custom timeout. + * @return timeout + * @since 1.6 + */ + public int getTimeout() + { + return timeout; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResource.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResource.java new file mode 100644 index 00000000..e0887eb6 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResource.java @@ -0,0 +1,108 @@ +package org.apache.velocity.runtime.resource.util; + +/* + * 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. + */ + +/** + * Wrapper for Strings containing templates, allowing to add additional meta + * data like timestamps. + * + * @author <a href="mailto:eelco.hillenius@openedge.nl">Eelco Hillenius</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public final class StringResource +{ + /** template body */ + private String body; + + /** encoding */ + private String encoding; + + /** last modified ts */ + private long lastModified; + + /** + * convenience constructor; sets body to 'body' and sets lastModified to now + * @param body + * @param encoding + */ + public StringResource(final String body, final String encoding) + { + setBody(body); + setEncoding(encoding); + } + + /** + * Sets the template body. + * @return String containing the template body. + */ + public String getBody() + { + return body; + } + + /** + * Returns the modification date of the template. + * @return Modification date in milliseconds. + */ + public long getLastModified() + { + return lastModified; + } + + /** + * Sets a new value for the template body. + * @param body New body value + */ + public void setBody(final String body) + { + this.body = body; + this.lastModified = System.currentTimeMillis(); + } + + /** + * Changes the last modified parameter. + * @param lastModified The modification time in millis. + */ + public void setLastModified(final long lastModified) + { + this.lastModified = lastModified; + } + + /** + * Returns the encoding of this String resource. + * + * @return The encoding of this String resource. + */ + public String getEncoding() { + return this.encoding; + } + + /** + * Sets the encoding of this string resource. + * + * @param encoding The new encoding of this resource. + */ + public void setEncoding(final String encoding) + { + this.encoding = encoding; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepository.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepository.java new file mode 100644 index 00000000..961e72a1 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepository.java @@ -0,0 +1,76 @@ +package org.apache.velocity.runtime.resource.util; + +/* + * 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. + */ + +/** + * A StringResourceRepository functions as a central repository for Velocity templates + * stored in Strings. + * + * @author <a href="mailto:eelco.hillenius@openedge.nl">Eelco Hillenius</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public interface StringResourceRepository +{ + /** + * get the string resource that is stored with given key + * @param name String name to retrieve from the repository. + * @return A StringResource containing the template. + */ + StringResource getStringResource(String name); + + /** + * add a string resource with given key. + * @param name The String name to store the template under. + * @param body A String containing a template. + */ + void putStringResource(String name, String body); + + /** + * add a string resource with given key. + * @param name The String name to store the template under. + * @param body A String containing a template. + * @param encoding The encoding of this string template + * @since 1.6 + */ + void putStringResource(String name, String body, String encoding); + + /** + * delete a string resource with given key. + * @param name The string name to remove from the repository. + */ + void removeStringResource(String name); + + /** + * Sets the default encoding of the repository. Encodings can also be stored per + * template string. The default implementation does this correctly. + * + * @param encoding The encoding to use. + */ + void setEncoding(String encoding); + + /** + * Returns the current encoding of this repository. + * + * @return The current encoding of this repository. + */ + String getEncoding(); +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepositoryImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepositoryImpl.java new file mode 100644 index 00000000..fc93602d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepositoryImpl.java @@ -0,0 +1,104 @@ +package org.apache.velocity.runtime.resource.util; + +/* + * 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. + */ + +import org.apache.velocity.runtime.RuntimeConstants; +import org.apache.velocity.runtime.resource.Resource; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Default implementation of StringResourceRepository. + * Uses a HashMap for storage + * + * @author <a href="mailto:eelco.hillenius@openedge.nl">Eelco Hillenius</a> + * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> + * @version $Id$ + * @since 1.5 + */ +public class StringResourceRepositoryImpl implements StringResourceRepository +{ + /** + * mem store + */ + protected Map<String, StringResource> resources = Collections.synchronizedMap(new HashMap<>()); + + /** + * Current Repository encoding. + */ + private String encoding = RuntimeConstants.ENCODING_DEFAULT; + + /** + * @see StringResourceRepository#getStringResource(java.lang.String) + */ + @Override + public StringResource getStringResource(final String name) + { + return resources.get(name); + } + + /** + * @see StringResourceRepository#putStringResource(java.lang.String, java.lang.String) + */ + @Override + public void putStringResource(final String name, final String body) + { + resources.put(name, new StringResource(body, getEncoding())); + } + + /** + * @see StringResourceRepository#putStringResource(java.lang.String, java.lang.String, java.lang.String) + * @since 1.6 + */ + @Override + public void putStringResource(final String name, final String body, final String encoding) + { + resources.put(name, new StringResource(body, encoding)); + } + + /** + * @see StringResourceRepository#removeStringResource(java.lang.String) + */ + @Override + public void removeStringResource(final String name) + { + resources.remove(name); + } + + /** + * @see org.apache.velocity.runtime.resource.util.StringResourceRepository#getEncoding() + */ + @Override + public String getEncoding() + { + return encoding; + } + + /** + * @see org.apache.velocity.runtime.resource.util.StringResourceRepository#setEncoding(java.lang.String) + */ + @Override + public void setEncoding(final String encoding) + { + this.encoding = encoding; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java new file mode 100644 index 00000000..397a3cd6 --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java @@ -0,0 +1,494 @@ +package org.apache.velocity.runtime.visitor; + +/* + * 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. + */ + +import org.apache.velocity.context.InternalContextAdapter; +import org.apache.velocity.runtime.parser.node.*; + +import java.io.Writer; + +/** + * This is the base class for all visitors. + * For each AST node, this class will provide + * a bare-bones method for traversal. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> + * @version $Id$ + */ +public abstract class BaseVisitor implements ParserVisitor +{ + /** Context used during traversal */ + protected InternalContextAdapter context; + + /** Writer used as the output sink */ + protected Writer writer; + + /** + * @param writer + */ + public void setWriter( Writer writer ) + { + this.writer = writer; + } + + /** + * @param context + */ + public void setContext( InternalContextAdapter context) + { + this.context = context; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.SimpleNode, java.lang.Object) + */ + @Override + public Object visit(SimpleNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTprocess, java.lang.Object) + */ + @Override + public Object visit(ASTprocess node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTExpression, java.lang.Object) + */ + @Override + public Object visit(ASTExpression node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAssignment, java.lang.Object) + */ + @Override + public Object visit(ASTAssignment node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTOrNode, java.lang.Object) + */ + @Override + public Object visit(ASTOrNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAndNode, java.lang.Object) + */ + @Override + public Object visit(ASTAndNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEQNode, java.lang.Object) + */ + @Override + public Object visit(ASTEQNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTNENode, java.lang.Object) + */ + @Override + public Object visit(ASTNENode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTLTNode, java.lang.Object) + */ + @Override + public Object visit(ASTLTNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTGTNode, java.lang.Object) + */ + @Override + public Object visit(ASTGTNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTLENode, java.lang.Object) + */ + @Override + public Object visit(ASTLENode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTGENode, java.lang.Object) + */ + @Override + public Object visit(ASTGENode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAddNode, java.lang.Object) + */ + @Override + public Object visit(ASTAddNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTSubtractNode, java.lang.Object) + */ + @Override + public Object visit(ASTSubtractNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMulNode, java.lang.Object) + */ + @Override + public Object visit(ASTMulNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDivNode, java.lang.Object) + */ + @Override + public Object visit(ASTDivNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTModNode, java.lang.Object) + */ + @Override + public Object visit(ASTModNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTNotNode, java.lang.Object) + */ + @Override + public Object visit(ASTNotNode node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIntegerLiteral, java.lang.Object) + */ + @Override + public Object visit(ASTIntegerLiteral node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTFloatingPointLiteral, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTFloatingPointLiteral node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTStringLiteral, java.lang.Object) + */ + @Override + public Object visit(ASTStringLiteral node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIdentifier, java.lang.Object) + */ + @Override + public Object visit(ASTIdentifier node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMethod, java.lang.Object) + */ + @Override + public Object visit(ASTMethod node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIndex, java.lang.Object) + */ + @Override + public Object visit(ASTIndex node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTReference, java.lang.Object) + */ + @Override + public Object visit(ASTReference node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTTrue, java.lang.Object) + */ + @Override + public Object visit(ASTTrue node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTFalse, java.lang.Object) + */ + @Override + public Object visit(ASTFalse node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTBlock, java.lang.Object) + */ + @Override + public Object visit(ASTBlock node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTText, java.lang.Object) + */ + @Override + public Object visit(ASTText node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIfStatement, java.lang.Object) + */ + @Override + public Object visit(ASTIfStatement node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTElseStatement, java.lang.Object) + */ + @Override + public Object visit(ASTElseStatement node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTElseIfStatement, java.lang.Object) + */ + @Override + public Object visit(ASTElseIfStatement node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTComment, java.lang.Object) + */ + @Override + public Object visit(ASTComment node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTTextblock, java.lang.Object) + */ + @Override + public Object visit(ASTTextblock node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTObjectArray, java.lang.Object) + */ + @Override + public Object visit(ASTObjectArray node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTWord, java.lang.Object) + */ + @Override + public Object visit(ASTWord node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTSetDirective, java.lang.Object) + */ + @Override + public Object visit(ASTSetDirective node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDirectiveAssign, java.lang.Object) + */ + @Override + public Object visit(ASTDirectiveAssign node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDirective, java.lang.Object) + */ + @Override + public Object visit(ASTDirective node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEscapedDirective, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTEscapedDirective node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEscape, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTEscape node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMap, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTMap node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } + + /** + * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIntegerRange, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTIntegerRange node, Object data) + { + data = node.childrenAccept(this, data); + return data; + } +} diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/NodeViewMode.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/NodeViewMode.java new file mode 100644 index 00000000..baac596d --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/NodeViewMode.java @@ -0,0 +1,436 @@ +package org.apache.velocity.runtime.visitor; + +/* + * 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. + */ + +import org.apache.velocity.runtime.parser.Token; +import org.apache.velocity.runtime.parser.node.*; + +/** + * This class is simply a visitor implementation + * that traverses the AST, produced by the Velocity + * parsing process, and creates a visual structure + * of the AST. This is primarily used for + * debugging, but it useful for documentation + * as well. + * + * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> + * @version $Id$ + */ +public class NodeViewMode extends BaseVisitor +{ + private int indent = 0; + private boolean showTokens = true; + + /** Indent child nodes to help visually identify + * the structure of the AST. + */ + private String indentString() + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < indent; ++i) + { + sb.append(" "); + } + return sb.toString(); + } + + /** + * Display the type of nodes and optionally the + * first token. + */ + private Object showNode(Node node, Object data) + { + String tokens = ""; + String special = ""; + Token t; + + if (showTokens) + { + // TODO: Token reference + t = node.getFirstToken(); + + if (t.specialToken != null && ! t.specialToken.image.startsWith(node.getParser().lineComment())) + special = t.specialToken.image; + + tokens = " -> " + special + t.image; + } + + System.out.println(indentString() + node + tokens); + ++indent; + data = node.childrenAccept(this, data); + --indent; + return data; + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.SimpleNode, java.lang.Object) + */ + @Override + public Object visit(SimpleNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTprocess, java.lang.Object) + */ + @Override + public Object visit(ASTprocess node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTExpression, java.lang.Object) + */ + @Override + public Object visit(ASTExpression node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAssignment, java.lang.Object) + */ + @Override + public Object visit(ASTAssignment node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTOrNode, java.lang.Object) + */ + @Override + public Object visit(ASTOrNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAndNode, java.lang.Object) + */ + @Override + public Object visit(ASTAndNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEQNode, java.lang.Object) + */ + @Override + public Object visit(ASTEQNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTNENode, java.lang.Object) + */ + @Override + public Object visit(ASTNENode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTLTNode, java.lang.Object) + */ + @Override + public Object visit(ASTLTNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTGTNode, java.lang.Object) + */ + @Override + public Object visit(ASTGTNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTLENode, java.lang.Object) + */ + @Override + public Object visit(ASTLENode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTGENode, java.lang.Object) + */ + @Override + public Object visit(ASTGENode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAddNode, java.lang.Object) + */ + @Override + public Object visit(ASTAddNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTSubtractNode, java.lang.Object) + */ + @Override + public Object visit(ASTSubtractNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMulNode, java.lang.Object) + */ + @Override + public Object visit(ASTMulNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDivNode, java.lang.Object) + */ + @Override + public Object visit(ASTDivNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTModNode, java.lang.Object) + */ + @Override + public Object visit(ASTModNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTNotNode, java.lang.Object) + */ + @Override + public Object visit(ASTNotNode node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTFloatingPointLiteral, java.lang.Object) + */ + @Override + public Object visit(ASTFloatingPointLiteral node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIntegerLiteral, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTIntegerLiteral node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTStringLiteral, java.lang.Object) + */ + @Override + public Object visit(ASTStringLiteral node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIdentifier, java.lang.Object) + */ + @Override + public Object visit(ASTIdentifier node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMethod, java.lang.Object) + */ + @Override + public Object visit(ASTMethod node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTReference, java.lang.Object) + */ + @Override + public Object visit(ASTReference node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTTrue, java.lang.Object) + */ + @Override + public Object visit(ASTTrue node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTFalse, java.lang.Object) + */ + @Override + public Object visit(ASTFalse node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTBlock, java.lang.Object) + */ + @Override + public Object visit(ASTBlock node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTText, java.lang.Object) + */ + @Override + public Object visit(ASTText node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIfStatement, java.lang.Object) + */ + @Override + public Object visit(ASTIfStatement node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTElseStatement, java.lang.Object) + */ + @Override + public Object visit(ASTElseStatement node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTElseIfStatement, java.lang.Object) + */ + @Override + public Object visit(ASTElseIfStatement node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTObjectArray, java.lang.Object) + */ + @Override + public Object visit(ASTObjectArray node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDirective, java.lang.Object) + */ + @Override + public Object visit(ASTDirective node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTWord, java.lang.Object) + */ + @Override + public Object visit(ASTWord node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTSetDirective, java.lang.Object) + */ + @Override + public Object visit(ASTSetDirective node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEscapedDirective, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTEscapedDirective node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEscape, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTEscape node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMap, java.lang.Object) + * @since 1.5 + */ + @Override + public Object visit(ASTMap node, Object data) + { + return showNode(node,data); + } + + /** + * @see org.apache.velocity.runtime.visitor.BaseVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIntegerRange, java.lang.Object) + */ + @Override + public Object visit(ASTIntegerRange node, Object data) + { + return showNode(node,data); + } +} |