aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Vuong <akvuong@google.com>2023-02-22 21:17:27 +0000
committerAndrew Vuong <akvuong@google.com>2023-02-22 21:17:27 +0000
commit757677e8848ed434bb36206b674db6d601cbe5e5 (patch)
tree2ab78e0ae0cda6d3ac8006cd6e0c6be8dc78a873
parentbc4c7a291f1579e1b6b903dc43b707a755577565 (diff)
parentf2461dce3a7455fc0416a7b0b95ed4021eff714d (diff)
downloadapache-velocity-engine-757677e8848ed434bb36206b674db6d601cbe5e5.tar.gz
Merge of apache-velocity-engine from aosp/masteraml_tz4_332714010
Bug: 262898801 Test: mma Change-Id: I68491a2e8b89245a1bff44d86023cec475ff902d
-rw-r--r--.gitattributes8
-rw-r--r--.gitignore26
-rw-r--r--Android.bp65
-rw-r--r--LICENSE229
-rw-r--r--METADATA17
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--MODULE_LICENSE_BSD0
-rw-r--r--NOTICE11
-rw-r--r--OWNERS2
-rw-r--r--README.md82
-rw-r--r--TEST_MAPPING7
-rw-r--r--generated-sources/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java6
-rw-r--r--generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParser.java5447
-rw-r--r--generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserConstants.java286
-rw-r--r--generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserTokenManager.java8749
-rw-r--r--generated-sources/javacc/org/apache/velocity/runtime/parser/Token.java131
-rw-r--r--generated-sources/javacc/org/apache/velocity/runtime/parser/TokenMgrError.java147
-rw-r--r--generated-sources/jjtree/org/apache/velocity/runtime/parser/node/JJTStandardParserState.java125
-rw-r--r--generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserTreeConstants.java101
-rw-r--r--generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserVisitor.java53
-rw-r--r--pom.xml371
-rw-r--r--spring-velocity-support/README.md22
-rw-r--r--spring-velocity-support/pom.xml106
-rw-r--r--spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java140
-rw-r--r--spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java337
-rw-r--r--spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java79
-rw-r--r--spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java112
-rw-r--r--spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java74
-rw-r--r--spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java70
-rw-r--r--spring-velocity-support/src/test/resources/simple.vm1
-rw-r--r--spring-velocity-support/src/test/resources/velocity.properties19
-rw-r--r--src/changes/changes.xml1408
-rw-r--r--velocity-custom-parser-example/pom.xml284
-rw-r--r--velocity-custom-parser-example/src/test/java/org/apache/velocity/runtime/parser/CustomParserTestCase.java56
-rw-r--r--velocity-custom-parser-example/src/test/resources/reference/test.md13
-rw-r--r--velocity-custom-parser-example/src/test/resources/templates/test.md23
-rw-r--r--velocity-engine-core/pom.xml351
-rwxr-xr-xvelocity-engine-core/src/etc/build/findbugs-exclude.xml121
-rw-r--r--velocity-engine-core/src/main/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java6
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/Template.java441
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/VelocityContext.java196
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/FieldMethodizer.java185
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/Velocity.java387
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java431
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventCartridge.java445
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandler.java31
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandlerUtil.java279
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/IncludeEventHandler.java52
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/InvalidReferenceEventHandler.java88
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/MethodExceptionEventHandler.java52
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/ReferenceInsertionEventHandler.java53
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeHtmlReference.java59
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeJavaScriptReference.java59
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeReference.java163
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeSqlReference.java52
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeXmlReference.java58
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeNotFound.java124
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeRelativePath.java72
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/InvalidReferenceInfo.java64
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/PrintExceptions.java121
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/ReportInvalidReferences.java193
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/AbstractContext.java274
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/context/ChainedInternalContextAdapter.java284
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/Context.java79
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapter.java36
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapterImpl.java363
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextBase.java275
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/InternalEventContext.java44
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/InternalHousekeepingContext.java148
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/context/InternalWrapperContext.java66
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/ExtendedParseException.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/MacroOverflowException.java81
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/exception/MathException.java50
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/MethodInvocationException.java168
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/ParseErrorException.java274
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/ResourceNotFoundException.java93
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/TemplateInitException.java128
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/exception/VelocityException.java115
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/io/Filter.java32
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/io/UnicodeInputStream.java411
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/io/VelocityWriter.java355
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/DeprecatedRuntimeConstants.java285
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserConfiguration.java116
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPool.java53
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/ParserPoolImpl.java81
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/Renderable.java44
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeConstants.java499
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeInstance.java2025
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeServices.java498
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/RuntimeSingleton.java571
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroFactory.java639
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/VelocimacroManager.java355
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java209
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/BlockMacro.java125
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Break.java126
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Define.java121
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Directive.java264
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/DirectiveConstants.java35
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Evaluate.java240
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Foreach.java360
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/ForeachScope.java79
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Include.java319
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/InputBase.java59
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Macro.java294
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/MacroParseException.java206
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Parse.java358
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/RuntimeMacro.java371
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Scope.java359
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Stop.java115
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/StopCommand.java89
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java459
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/contrib/For.java141
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/CharStream.java128
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/LogContext.java165
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParseException.java237
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java54
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TemplateParseException.java245
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/VelocityCharStream.java526
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAddNode.java90
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAndNode.java123
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTAssignment.java69
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBinaryOperator.java47
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTBlock.java158
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComment.java109
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTComparisonNode.java177
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirective.java372
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDirectiveAssign.java53
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTDivNode.java86
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEQNode.java96
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseIfStatement.java110
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTElseStatement.java87
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscape.java90
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTEscapedDirective.java91
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTExpression.java96
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFalse.java88
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTFloatingPointLiteral.java126
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGENode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTGTNode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIdentifier.java305
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIfStatement.java216
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIncludeStatement.java70
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIndex.java224
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerLiteral.java122
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTIntegerRange.java296
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLENode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLTNode.java51
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTLogicalOperator.java30
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMap.java112
-rwxr-xr-xvelocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMathNode.java165
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMethod.java459
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTModNode.java99
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTMulNode.java81
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNENode.java50
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNegateNode.java94
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTNotNode.java96
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTObjectArray.java103
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTOrNode.java115
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTParameters.java55
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTReference.java1165
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSetDirective.java304
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTStringLiteral.java364
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTSubtractNode.java67
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTText.java122
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTextblock.java94
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTTrue.java89
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTVariable.java70
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTWord.java69
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ASTprocess.java68
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/AbstractExecutor.java83
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/BooleanPropertyExecutor.java123
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/GetExecutor.java120
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/IndentationFixer.java362
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/JJTParserState.java5
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapGetExecutor.java105
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MapSetExecutor.java84
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java539
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/Node.java232
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/NodeUtils.java162
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserTreeConstants.java24
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/ParserVisitor.java332
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PropertyExecutor.java151
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PublicFieldExecutor.java128
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/PutExecutor.java133
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetExecutor.java87
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPropertyExecutor.java138
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SetPublicFieldExecutor.java149
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/SimpleNode.java650
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ContentResource.java105
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/Resource.java320
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCache.java81
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceCacheImpl.java168
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceFactory.java59
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManager.java81
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/ResourceManagerImpl.java607
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ClasspathResourceLoader.java169
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/DataSourceResourceLoader.java550
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/FileResourceLoader.java373
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarHolder.java169
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarResourceLoader.java263
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoader.java324
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/ResourceLoaderFactory.java62
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/StringResourceLoader.java438
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/URLResourceLoader.java214
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResource.java108
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepository.java76
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/util/StringResourceRepositoryImpl.java104
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/BaseVisitor.java494
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/runtime/visitor/NodeViewMode.java436
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayIterator.java120
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayListWrapper.java67
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/ClassUtils.java271
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/ContextAware.java49
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/DeprecationAwareExtProperties.java171
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java326
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/EnumerationIterator.java80
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java2087
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/RuntimeServicesAware.java42
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/SimplePool.java137
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/StringBuilderWriter.java167
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/StringUtils.java92
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateBoolean.java38
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateNumber.java38
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateString.java38
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/AbstractChainableUberspector.java120
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ChainableUberspector.java37
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassFieldMap.java175
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java380
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java70
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java39
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/DeprecatedCheckUberspector.java111
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Info.java97
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionCacheData.java43
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java336
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java132
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java138
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java179
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/LinkingUberspector.java124
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java691
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorControl.java42
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorImpl.java166
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureUberspector.java85
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java68
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java725
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Uberspect.java74
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java796
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectPublicFields.java139
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java74
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertyGet.java55
-rw-r--r--velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertySet.java56
-rw-r--r--velocity-engine-core/src/main/parser/Parser.jjt2642
-rw-r--r--velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/directive.properties24
-rw-r--r--velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties245
-rw-r--r--velocity-engine-core/src/test/java/classloader/Foo.java45
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/io/UnicodeInputStreamTestCase.java239
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/runtime/RuntimeInstanceTest.java49
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/AbsoluteFileResourceLoaderTestCase.java150
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/AlternateValuesTestCase.java61
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ArithmeticTestCase.java244
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ArrayMethodsTestCase.java211
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java515
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/BlockMacroTestCase.java141
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/BreakDirectiveTestCase.java117
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/BuiltInEventHandlerTestCase.java592
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java169
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ClasspathResourceTestCase.java166
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/CommentsTestCase.java108
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/CommonsExtPropTestCase.java178
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ContextAutoreferenceKeyTestCase.java58
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ContextSafetyTestCase.java145
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/DefineTestCase.java120
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/EncodingTestCase.java208
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/EvaluateTestCase.java299
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/EventHandlingTestCase.java271
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ExceptionTestCase.java171
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ExpressionAsMethodArgumentTestCase.java56
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/FilteredEventHandlingTestCase.java238
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ForeachTestCase.java147
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java145
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/HyphenInIdentifiersTestCase.java50
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyNoEmptyCheckTestCase.java120
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java144
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java101
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeErrorTestCase.java119
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeEventHandlingTestCase.java250
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IndexTestCase.java171
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/InfoTestCase.java118
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java134
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectionCacheDataTestCase.java81
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java168
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java151
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java226
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/InvalidEventHandlerTestCase.java646
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MacroAutoReloadTestCase.java103
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MacroCommentsTestCase.java56
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MacroDefaultArgTestCase.java73
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MacroForwardDefineTestCase.java119
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MethodCacheKeyTestCase.java88
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MethodInvocationExceptionTestCase.java278
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MethodOverloadingTestCase.java184
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MiscTestCase.java74
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MultiLoaderTestCase.java223
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/MultipleFileResourcePathTestCase.java144
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/NumberMethodCallsTestCase.java142
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/OldPropertiesTestCase.java170
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ParseExceptionTestCase.java179
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ParseWithMacroLibsTestCase.java309
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ParserTestCase.java201
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/PropertyMethodPrecedenceTestCase.java103
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/RenderVelocityTemplateTest.java155
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceCachingTestCase.java94
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/ResourceExistsTestCase.java105
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceLoaderInstanceTestCase.java154
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java369
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/SecureIntrospectionTestCase.java179
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/SetTestCase.java144
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/SpaceGobblingTestCase.java143
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/StaticUtilityMethodsTestCase.java57
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StopDirectiveTestCase.java85
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StrictAlternateValuesTestCase.java76
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StrictCompareTestCase.java76
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StrictEscapeTestCase.java139
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/StrictForeachTestCase.java110
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/StrictMathTestCase.java86
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java257
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/StringConcatenationTestCase.java66
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderRepositoryTestCase.java214
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderTestCase.java209
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestBase.java84
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestCase.java237
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestSuite.java95
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/TestBaseTestCase.java64
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/TextblockTestCase.java163
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/URLResourceLoaderTimeoutTestCase.java82
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/UberspectorTestCase.java300
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/UnicodeEscapeTestCase.java61
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/VMLibraryTestCase.java325
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/VarargMethodsTestCase.java278
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroBCModeTestCase.java100
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroTestCase.java135
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/VelocityAppTestCase.java80
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/WrappedExceptionTestCase.java84
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler1.java75
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler2.java75
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/StackOverflow32805217TestCase.java39
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java181
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity532TestCase.java52
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity537TestCase.java121
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity544TestCase.java75
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity579TestCase.java82
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity580TestCase.java116
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity587TestCase.java64
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity589TestCase.java40
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity614TestCase.java90
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity615TestCase.java139
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity616TestCase.java70
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity625TestCase.java40
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity627TestCase.java49
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity629TestCase.java55
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity62TestCase.java63
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity631TestCase.java49
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity644TestCase.java57
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity667TestCase.java39
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity682TestCase.java63
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity689TestCase.java78
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity701TestCase.java72
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity702TestCase.java99
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity709TestCase.java54
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity727TestCase.java40
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity728TestCase.java40
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity729TestCase.java46
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity736TestCase.java76
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity742TestCase.java57
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity747TestCase.java102
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity753TestCase.java62
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity755TestCase.java41
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity758TestCase.java66
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity762TestCase.java40
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity785TestCase.java42
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity830TestCase.java58
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity855TestCase.java47
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity889TestCase.java51
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity896TestCase.java40
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity904TestCase.java116
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity919TestCase.java23
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity924TestCase.java65
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity926TestCase.java38
-rwxr-xr-xvelocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity927TestCase.java39
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingDirective.java61
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingEventHandler.java55
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java62
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/GetPutObject.java35
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestContext.java85
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestLogger.java360
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestException.java62
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestImpl.java66
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectorTestObject.java133
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/BoolObj.java46
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Child.java37
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/ForeachMethodCallHelper.java31
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NullToStringObject.java33
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NumberMethods.java70
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Person.java39
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestNumber.java47
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestProvider.java362
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/sql/BaseSQLTest.java96
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DBHelper.java123
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DataSourceResourceLoaderTestCase.java229
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/sql/TestDataSource.java105
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ChainedUberspectorsTestCase.java121
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ClassMapTestCase.java110
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java454
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/DeprecatedCheckUberspectorsTestCase.java108
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/EnumConstantConversionTestCase.java77
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java134
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/test/view/TemplateNodeView.java85
-rw-r--r--velocity-engine-core/src/test/java/org/apache/velocity/util/SimplePoolTestCase.java67
-rw-r--r--velocity-engine-core/src/test/resources/absolute/absolute.vm12
-rw-r--r--velocity-engine-core/src/test/resources/absolute/compare/absolute.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_disabled40
-rw-r--r--velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_enabled40
-rw-r--r--velocity-engine-core/src/test/resources/bc_mode/test_bc_mode.vtl39
-rw-r--r--velocity-engine-core/src/test/resources/configuration/compare/output.cmp123
-rw-r--r--velocity-engine-core/src/test/resources/configuration/include1.properties17
-rw-r--r--velocity-engine-core/src/test/resources/configuration/include2.properties17
-rw-r--r--velocity-engine-core/src/test/resources/configuration/test-config.properties99
-rw-r--r--velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp600
-rw-r--r--velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp992
-rw-r--r--velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp992
-rw-r--r--velocity-engine-core/src/test/resources/conversion/matrix.vhtml85
-rw-r--r--velocity-engine-core/src/test/resources/conversion/test_conv.vtl35
-rw-r--r--velocity-engine-core/src/test/resources/cpload/compare/test1.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/cpload/compare/test2.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/cpload/test1.jarbin0 -> 598 bytes
-rw-r--r--velocity-engine-core/src/test/resources/cpload/test2.jarbin0 -> 598 bytes
-rw-r--r--velocity-engine-core/src/test/resources/ds/create-db.sql54
-rw-r--r--velocity-engine-core/src/test/resources/ds/create-db.sql.derby54
-rw-r--r--velocity-engine-core/src/test/resources/ds/create-db.sql.mysql54
-rw-r--r--velocity-engine-core/src/test/resources/ds/create-db.sql.oracle51
-rw-r--r--velocity-engine-core/src/test/resources/ds/create-db.sql.postgresql54
-rw-r--r--velocity-engine-core/src/test/resources/ds/create-db.sql.sqlserver54
-rw-r--r--velocity-engine-core/src/test/resources/ds/templates/testTemplate1.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/ds/templates/testTemplate2.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/ds/templates/testTemplate3.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/ds/templates/testTemplate4.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/evaluate/compare/eval1.cmp8
-rw-r--r--velocity-engine-core/src/test/resources/evaluate/compare/eval2.cmp9
-rw-r--r--velocity-engine-core/src/test/resources/evaluate/compare/evalvmcontext.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/evaluate/eval1.vm20
-rw-r--r--velocity-engine-core/src/test/resources/evaluate/eval2.vm23
-rw-r--r--velocity-engine-core/src/test/resources/evaluate/evalvmcontext.vm7
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.BC14
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.LINES14
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.NONE20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.STRUCTURED14
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.BC20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.LINES20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.NONE37
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.STRUCTURED20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.BC20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.LINES20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.NONE37
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.STRUCTURED20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.BC60
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.LINES61
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.NONE243
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.STRUCTURED61
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.BC20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.LINES20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.NONE45
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.STRUCTURED20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.BC6
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.LINES5
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.NONE8
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.STRUCTURED5
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.BC3
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.LINES4
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.NONE6
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.STRUCTURED4
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.BC7
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.LINES7
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.NONE11
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.STRUCTURED7
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.BC18
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.LINES18
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.NONE18
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.STRUCTURED18
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.BC20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.LINES20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.NONE20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.STRUCTURED20
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/foreach_empty.vtl26
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/foreach_smart.vtl19
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/foreach_structured.vtl19
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/if.vtl651
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/macro.vtl41
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/macro2.vtl8
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/set.vtl6
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/structured.vtl11
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/ugly1.vtl18
-rw-r--r--velocity-engine-core/src/test/resources/gobbling/ugly2.vtl20
-rw-r--r--velocity-engine-core/src/test/resources/includeerror/haserror.txt7
-rw-r--r--velocity-engine-core/src/test/resources/includeerror/haserror2.txt10
-rw-r--r--velocity-engine-core/src/test/resources/includeerror/missinginclude.vm8
-rw-r--r--velocity-engine-core/src/test/resources/includeerror/missingparse.vm8
-rw-r--r--velocity-engine-core/src/test/resources/includeerror/parsemain.vm8
-rw-r--r--velocity-engine-core/src/test/resources/includeerror/parsemain2.vm8
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/compare/test1.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/compare/test2.cmp5
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/compare/test3.cmp2
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/compare/test4.cmp2
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/compare/test5.cmp2
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/compare/test6.cmp2
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/include-a.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/include-b.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/include-c.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/include4.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/include5.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/notfound.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/parse-a.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/parse-b.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/parse-c.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/include-b.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/include-c.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/include4.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/include5.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/notfound.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/parse-b.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/parse-c.vm1
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/subdir/test2.vm8
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/test1-cp.vm4
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/test1.vm4
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/test3.vm4
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/test4.vm2
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/test5.vm2
-rw-r--r--velocity-engine-core/src/test/resources/includeevent/test6.vm3
-rw-r--r--velocity-engine-core/src/test/resources/info/info1.vm1
-rw-r--r--velocity-engine-core/src/test/resources/info/info2.vm1
-rwxr-xr-xvelocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537.vm.cmp0
-rw-r--r--velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537b.vm.cmp1
-rwxr-xr-xvelocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537.vm3
-rw-r--r--velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537b.vm3
-rwxr-xr-xvelocity-engine-core/src/test/resources/issues/velocity-580/compare/velocity580.vm.cmp1
-rwxr-xr-xvelocity-engine-core/src/test/resources/issues/velocity-580/templates/velocity580.vm5
-rw-r--r--velocity-engine-core/src/test/resources/issues/velocity-747/one.vm1
-rw-r--r--velocity-engine-core/src/test/resources/issues/velocity-747/two.vm1
-rw-r--r--velocity-engine-core/src/test/resources/issues/velocity-747/vel.props5
-rw-r--r--velocity-engine-core/src/test/resources/macroforwarddefine/compare/velocity.log.cmp10
-rw-r--r--velocity-engine-core/src/test/resources/macroforwarddefine/macros.vm40
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/compare/vm_library.cmp8
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_duplicate.cmp6
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_global.cmp11
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_local.cmp18
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/vm_library.vm8
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/vm_library1.vm7
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/vm_library2.vm7
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/vm_library_global.vm8
-rw-r--r--velocity-engine-core/src/test/resources/macrolibs/vm_library_local.vm8
-rw-r--r--velocity-engine-core/src/test/resources/methodoverloading/compare/main.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/methodoverloading/compare/single.cmp5
-rw-r--r--velocity-engine-core/src/test/resources/methodoverloading/main.vm6
-rw-r--r--velocity-engine-core/src/test/resources/methodoverloading/single.vm7
-rw-r--r--velocity-engine-core/src/test/resources/methodoverloading/sub.vm2
-rw-r--r--velocity-engine-core/src/test/resources/misc/README.txt18
-rwxr-xr-xvelocity-engine-core/src/test/resources/misc/compile.sh27
-rwxr-xr-xvelocity-engine-core/src/test/resources/misc/dump.sh27
-rwxr-xr-xvelocity-engine-core/src/test/resources/misc/test.sh27
-rw-r--r--velocity-engine-core/src/test/resources/multi/compare/path1.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/multi/compare/path2.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/multi/path1/path1.vm12
-rw-r--r--velocity-engine-core/src/test/resources/multi/path2/path2.vm12
-rw-r--r--velocity-engine-core/src/test/resources/multiloader/compare/path1.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/multiloader/compare/test2.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/multiloader/compare/test3.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/multiloader/path1.vm12
-rw-r--r--velocity-engine-core/src/test/resources/multiloader/test1.jarbin0 -> 598 bytes
-rw-r--r--velocity-engine-core/src/test/resources/multiloader/test2.jarbin0 -> 598 bytes
-rw-r--r--velocity-engine-core/src/test/resources/oldproperties/velocity.properties264
-rw-r--r--velocity-engine-core/src/test/resources/parseexception/badtemplate.vm7
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1b.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2b.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3b.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4b.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro2.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro3.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/parseMacro1.vm4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/parseMacro2.vm3
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/parseMacro3.vm4
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/vm_library1.vm7
-rw-r--r--velocity-engine-core/src/test/resources/parsemacros/vm_library2.vm7
-rw-r--r--velocity-engine-core/src/test/resources/reload/foo.vtl1
-rw-r--r--velocity-engine-core/src/test/resources/reload/macros.vtl1
-rw-r--r--velocity-engine-core/src/test/resources/resourcecaching/include/include1.vm1
-rw-r--r--velocity-engine-core/src/test/resources/resourcecaching/testincludeparse.vm5
-rwxr-xr-xvelocity-engine-core/src/test/resources/resourceexists/testfile.vm1
-rw-r--r--velocity-engine-core/src/test/resources/resourceinstance/compare/testfile.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/resourceinstance/testfile.vm12
-rw-r--r--velocity-engine-core/src/test/resources/set/compare/set1.cmp9
-rw-r--r--velocity-engine-core/src/test/resources/set/compare/set2.cmp13
-rw-r--r--velocity-engine-core/src/test/resources/set/set1.vm17
-rw-r--r--velocity-engine-core/src/test/resources/set/set2.vm29
-rw-r--r--velocity-engine-core/src/test/resources/stop/parse.vm1
-rw-r--r--velocity-engine-core/src/test/resources/stop/stop1.vm2
-rw-r--r--velocity-engine-core/src/test/resources/stop/stop2.vm2
-rw-r--r--velocity-engine-core/src/test/resources/stop/stop3.vm1
-rw-r--r--velocity-engine-core/src/test/resources/stop/vmlib1.vm3
-rw-r--r--velocity-engine-core/src/test/resources/stringloader/compare/change1.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/stringloader/compare/change2.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/stringloader/compare/multi1.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/stringloader/compare/multi2.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/stringloader/compare/simpletemplate.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/templates/VM_global_library.vm27
-rw-r--r--velocity-engine-core/src/test/resources/templates/arithmetic.vm73
-rw-r--r--velocity-engine-core/src/test/resources/templates/array.vm48
-rw-r--r--velocity-engine-core/src/test/resources/templates/block.vm90
-rw-r--r--velocity-engine-core/src/test/resources/templates/commas.vm15
-rw-r--r--velocity-engine-core/src/test/resources/templates/comment-eof.vm3
-rw-r--r--velocity-engine-core/src/test/resources/templates/comment.vm78
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/arithmetic.cmp38
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/array.cmp25
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/block.cmp35
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/commas.cmp11
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/comment-eof.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/comment.cmp35
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/context_safety1.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/context_safety2.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/curly-directive.cmp16
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/diabolical.cmp52
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/directive.cmp5
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/encodingtest.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/encodingtest2.cmp3
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/encodingtest3.cmp19
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/encodingtest_KOI8-R.cmp104
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/equality.cmp11
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/escape.cmp44
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/escape2.cmp106
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-array.cmp33
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-introspect.cmp14
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-map.cmp5
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-method.cmp8
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-null-list.cmp12
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-type.cmp45
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/foreach-variable.cmp89
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/formal.cmp34
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/get.cmp7
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/if.cmp5
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/ifstatement.cmp29
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/include.cmp41
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/interpolation.cmp51
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/logical.cmp197
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/logical2.cmp133
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/loop.cmp6
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/map.cmp36
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/math.cmp47
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/method.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/newline.cmp20
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/parse.cmp22
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/pedantic.cmp79
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/quotes.cmp9
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/range.cmp62
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/reference.cmp75
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/sample.cmp30
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/settest.cmp17
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/shorthand.cmp1
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/stop1.cmp2
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/stop2.cmp6
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/stop3.cmp4
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/string-interpolation.cmp10
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/string.cmp30
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/subclass.cmp8
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/test.cmp162
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/velocimacro.cmp59
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/velocimacro2.cmp43
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/vm_test1.cmp8
-rw-r--r--velocity-engine-core/src/test/resources/templates/compare/vm_test2.cmp9
-rw-r--r--velocity-engine-core/src/test/resources/templates/context_safety.vm3
-rw-r--r--velocity-engine-core/src/test/resources/templates/curly-directive.vm34
-rw-r--r--velocity-engine-core/src/test/resources/templates/diabolical.vm74
-rw-r--r--velocity-engine-core/src/test/resources/templates/encodingtest.vm4
-rw-r--r--velocity-engine-core/src/test/resources/templates/encodingtest2.vm4
-rw-r--r--velocity-engine-core/src/test/resources/templates/encodingtest3.vm19
-rw-r--r--velocity-engine-core/src/test/resources/templates/encodingtest_KOI8-R.vm104
-rw-r--r--velocity-engine-core/src/test/resources/templates/equality.vm31
-rw-r--r--velocity-engine-core/src/test/resources/templates/escape.vm55
-rw-r--r--velocity-engine-core/src/test/resources/templates/escape2.vm138
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-array.vm62
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-introspect.vm13
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-map.vm14
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-method.vm16
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-null-list.vm17
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-type.vm76
-rw-r--r--velocity-engine-core/src/test/resources/templates/foreach-variable.vm44
-rw-r--r--velocity-engine-core/src/test/resources/templates/formal.vm46
-rw-r--r--velocity-engine-core/src/test/resources/templates/get.vm19
-rw-r--r--velocity-engine-core/src/test/resources/templates/if.vm22
-rw-r--r--velocity-engine-core/src/test/resources/templates/ifstatement.vm79
-rw-r--r--velocity-engine-core/src/test/resources/templates/include.vm17
-rw-r--r--velocity-engine-core/src/test/resources/templates/interpolation.vm82
-rw-r--r--velocity-engine-core/src/test/resources/templates/logical.vm468
-rw-r--r--velocity-engine-core/src/test/resources/templates/logical2.vm346
-rw-r--r--velocity-engine-core/src/test/resources/templates/loop.vm15
-rw-r--r--velocity-engine-core/src/test/resources/templates/map.vm68
-rw-r--r--velocity-engine-core/src/test/resources/templates/math.vm71
-rw-r--r--velocity-engine-core/src/test/resources/templates/mergethis.vm1
-rw-r--r--velocity-engine-core/src/test/resources/templates/method.vm13
-rw-r--r--velocity-engine-core/src/test/resources/templates/newline.vm29
-rw-r--r--velocity-engine-core/src/test/resources/templates/parse.vm18
-rw-r--r--velocity-engine-core/src/test/resources/templates/parse1.vm22
-rw-r--r--velocity-engine-core/src/test/resources/templates/parse2.vm15
-rw-r--r--velocity-engine-core/src/test/resources/templates/pedantic.vm101
-rw-r--r--velocity-engine-core/src/test/resources/templates/quotes.vm21
-rw-r--r--velocity-engine-core/src/test/resources/templates/range.vm93
-rw-r--r--velocity-engine-core/src/test/resources/templates/reference.vm104
-rw-r--r--velocity-engine-core/src/test/resources/templates/sample.vm32
-rw-r--r--velocity-engine-core/src/test/resources/templates/settest.vm22
-rw-r--r--velocity-engine-core/src/test/resources/templates/shorthand.vm1
-rw-r--r--velocity-engine-core/src/test/resources/templates/stop1.vm19
-rw-r--r--velocity-engine-core/src/test/resources/templates/stop2.vm31
-rw-r--r--velocity-engine-core/src/test/resources/templates/stop3-include.vm3
-rw-r--r--velocity-engine-core/src/test/resources/templates/stop3.vm8
-rw-r--r--velocity-engine-core/src/test/resources/templates/string.vm51
-rw-r--r--velocity-engine-core/src/test/resources/templates/subclass.vm26
-rw-r--r--velocity-engine-core/src/test/resources/templates/subdir/test.txt2
-rw-r--r--velocity-engine-core/src/test/resources/templates/templates.properties65
-rw-r--r--velocity-engine-core/src/test/resources/templates/test.txt1
-rw-r--r--velocity-engine-core/src/test/resources/templates/test.vm221
-rw-r--r--velocity-engine-core/src/test/resources/templates/testCase644.vm20
-rw-r--r--velocity-engine-core/src/test/resources/templates/velocimacro.vm88
-rw-r--r--velocity-engine-core/src/test/resources/templates/velocimacro2.vm91
-rw-r--r--velocity-engine-core/src/test/resources/templates/vm_test1.vm16
-rw-r--r--velocity-engine-core/src/test/resources/templates/vm_test2.vm24
-rw-r--r--velocity-engine-examples/README.md83
-rw-r--r--velocity-engine-examples/pom.xml111
-rw-r--r--velocity-engine-examples/src/assembly/examples.xml62
-rw-r--r--velocity-engine-examples/src/etc/build.sh40
-rwxr-xr-xvelocity-engine-examples/src/etc/dbcontexttest.sh40
-rwxr-xr-xvelocity-engine-examples/src/etc/example1.sh36
-rwxr-xr-xvelocity-engine-examples/src/etc/example2.sh36
-rwxr-xr-xvelocity-engine-examples/src/etc/xmlapp_example.sh35
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContext.java190
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContextTest.java76
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/EventExample.java564
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/Example.java126
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/Example2.java116
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/TreeMapContext.java84
-rw-r--r--velocity-engine-examples/src/main/java/org/apache/velocity/example/XMLTest.java121
-rw-r--r--velocity-engine-examples/src/main/resources/dbtest.vm18
-rw-r--r--velocity-engine-examples/src/main/resources/example1.vm32
-rw-r--r--velocity-engine-examples/src/main/resources/example2.vm17
-rw-r--r--velocity-engine-examples/src/main/resources/test.xml44
-rw-r--r--velocity-engine-examples/src/main/resources/velocity.properties20
-rw-r--r--velocity-engine-examples/src/main/resources/xml.vm42
-rw-r--r--velocity-engine-scripting/pom.xml87
-rw-r--r--velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityCompiledScript.java70
-rw-r--r--velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java338
-rw-r--r--velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java231
-rw-r--r--velocity-engine-scripting/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory1
-rw-r--r--velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/AbstractScriptTest.java31
-rw-r--r--velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/ScriptEngineTest.java97
-rw-r--r--velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/VelocityScriptContextTest.java159
-rw-r--r--velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/resources/eventtool.vm5
-rw-r--r--velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/CustomEvent.java50
-rw-r--r--velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/EventToolTest.java61
-rw-r--r--velocity-engine-scripting/src/test/resources/velocity.properties17
767 files changed, 100150 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..3bb3b5ea
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+*.java text diff=java
+*.html text diff=html
+*.css text
+*.js text
+*.sql text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..d4c08857
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,26 @@
+#maven
+target
+
+#idea
+*.iml
+.idea
+
+#eclipse
+bin
+.project
+.classpath
+.settings
+
+# OSX
+.DS_Store
+
+# Generated sources
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/JJTParserState.java
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Parser.java
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParserConstants.java
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParserTokenManager.java
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/ParserTreeConstants.java
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/Token.java
+velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/TokenMgrError.java
+
+.java-version
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 00000000..1415c075
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["external_apache_velocity_engine_license"],
+}
+
+license {
+ name: "external_apache_velocity_engine_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ ],
+ license_text: [
+ "LICENSE",
+ "NOTICE",
+ ],
+}
+
+java_library {
+ name: "apache-velocity-engine-core",
+ srcs: ["velocity-engine-core/src/main/java/**/*.java",
+ // Manually generated code as javacc is not availiable for AOSP build
+ // mvn && cp -r velocity-engine-core/target/generated-srcs .
+ "generated-sources/**/*.java"],
+ exclude_srcs: ["velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/JarResourceLoader.java",
+ "velocity-engine-core/src/main/java/org/apache/velocity/runtime/resource/loader/DataSourceResourceLoader.java"],
+ java_resource_dirs: ["velocity-engine-core/src/main/resources"],
+ sdk_version: "current",
+ min_sdk_version: "33",
+ static_libs: [
+ "apache-commons-lang",
+ "apache-commons-io",
+ "slf4j-jdk14",
+ ],
+ java_version: "1.8",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.ondevicepersonalization",
+ ],
+ visibility: [
+ "//external/apache-velocity-engine",
+ "//packages/modules/OnDevicePersonalization:__subpackages__",
+ ],
+ lint: {
+ warning_checks: ["SuspiciousIndentation"],
+ },
+ errorprone: {
+ javacflags: [
+ "-Xep:ReturnValueIgnored:OFF",
+ ],
+ },
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..98bb2340
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,229 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
+
+
+
+------------------
+
+ Files: velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine*
+
+ Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ Use is subject to license terms.
+
+ Redistribution and use in source and binary forms, with or without modification, are
+ permitted provided that the following conditions are met: Redistributions of source code
+ must retain the above copyright notice, this list of conditions and the following disclaimer.
+ Redistributions in binary form must reproduce the above copyright notice, this list of
+ conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution. Neither the name of the Sun Microsystems nor the names of
+ is contributors may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
diff --git a/METADATA b/METADATA
new file mode 100644
index 00000000..715d8def
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,17 @@
+name: "apache-velocity-engine"
+description:
+ "Apache Velocity is a general purpose template engine written in Java."
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://velocity.apache.org/engine/"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/apache/velocity-engine.git"
+ }
+ version: "dc905fdbe4b5f2ea3ac2cb157f1d13ef8dd6c0bb"
+ last_upgrade_date { year: 2023 month: 02 day: 13 }
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/MODULE_LICENSE_BSD
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 00000000..85f11aa9
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,11 @@
+Apache Velocity
+
+Copyright (C) 2000-2007 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+The files
+ - velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java
+ - velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java
+are Copyright 2006 Sun Microsystems, Inc., and licenced under a BSD-like licence.
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 00000000..3f20f546
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+akvuong@google.com
+karthikmahesh@google.com
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..ea45099f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,82 @@
+Title: Apache Velocity Engine
+
+# Apache Velocity
+
+Welcome to Apache Velocity Engine! Apache Velocity is a general purpose
+template engine written in Java. For more information about Velocity,
+please look at the HTML documentation on the [Velocity web site](http://velocity.apache.org/index.html).
+
+Here's a description of the top level directories:
+
+ velocity-engine-core/ The Velocity Engine core module
+ velocity-engine-examples/ Several simple examples
+ velocity-engine-scripting/ JSR-223 implementation for Velocity scripting
+ spring-velocity-support Velocity Engine factory bean for Spring framework
+ src/ Source for parent modules, mainly changelog
+
+## REQUIREMENTS
+
+Apache Velocity 2.2 will run with any Java runtime engine v1.8 or greater.
+
+Building from source requires Java development kit v1.8 or greater and Maven 3 (3.0.5+).
+
+At compile time, Maven should fetch all engine needed dependencies, which are:
+
+* commons-lang v3.9
+* slf4j-api v1.7.30
+
+plus the following ones, needed for the integrated tests:
+
+* slf4j-simple v1.7.30
+* junit v4.13
+* hsqldb v2.5.0
+* commons-io 2.8.0
+
+At runtime, Velocity only needs:
+
+* commons-lang v3.9+
+* slf4j-api and an slf4j binding, v1.7.30+
+
+## BUILDING APACHE VELOCITY
+
+In order to use the latest version of Apache Velocity, you may want to
+build it.
+
+Building is easy. All components necessary to build are included or
+get downloaded from the internet during the build, except for the Java
+ SDK and the Maven build tool. You can find details online on [how to build
+Velocity](http://velocity.apache.org/engine/devel/build.html).
+
+*IMPORTANT* As the Apache Velocity build process wants to download a
+number of jars from the internet, you must be online when you are
+building for the first time.
+
+To build Velocity's jar, just run maven using the command:
+
+ mvn
+
+This will create a target/ directory containing the Velocity .jar
+file in each sub-module directory.
+
+Be sure to update your classpath to include Velocity's .jar
+file, or when using a modern servlet container, put it in the
+WEB-INF/lib directory.
+
+## CUSTOMIZING THE PARSER
+
+Since 2.2, it's possible to [build a custom parser](http://velocity.apache.org/engine/2.2/developer-guide.html#customizing-the-vtl-parser), to change some of the characters used by in the VTL syntax: `*`, `@`, `$` and `#`.
+
+Let's say you want to merge some templatized jQuery code full of `$` characters, you can for instance build you own parser which will use the `§` character as references prefix instead of `$`.
+
+## TRYING THE EXAMPLES
+
+After building Velocity, you can also build the examples that are
+included with the Velocity distribution. These examples show how to
+use Velocity in your Java applications.
+
+For more information, please see the [examples README](velocity-engine-examples) in the `velocity-engine-examples` directory.
+
+___
+
+- The Apache Velocity Team
+
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 00000000..44c538d3
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "OnDevicePersonalizationManagingServicesTests"
+ }
+ ]
+}
diff --git a/generated-sources/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java b/generated-sources/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java
new file mode 100644
index 00000000..55d9caca
--- /dev/null
+++ b/generated-sources/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java
@@ -0,0 +1,6 @@
+package org.apache.velocity.runtime;
+
+public class VelocityEngineVersion
+{
+ public static final String VERSION = "2.4-SNAPSHOT";
+}
diff --git a/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParser.java b/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParser.java
new file mode 100644
index 00000000..1e857d83
--- /dev/null
+++ b/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParser.java
@@ -0,0 +1,5447 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. StandardParser.java */
+package org.apache.velocity.runtime.parser;
+import org.apache.velocity.runtime.parser.node.*;
+
+
+import java.io.*;
+import java.util.*;
+import org.apache.velocity.Template;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.parser.*;
+import org.apache.velocity.runtime.parser.node.*;
+import org.apache.velocity.runtime.directive.*;
+import org.apache.velocity.runtime.directive.MacroParseException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import static org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+
+import org.slf4j.Logger;
+
+/**
+ * This class is responsible for parsing a Velocity
+ * template. This class was generated by JavaCC using
+ * the JJTree extension to produce an Abstract
+ * Syntax Tree (AST) of the template.
+ *
+ * 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="hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id$
+*/
+public class StandardParser implements/*@bgen(jjtree)*/ StandardParserTreeConstants,Parser, StandardParserConstants {/*@bgen(jjtree)*/
+ protected JJTStandardParserState jjtree = new JJTStandardParserState();/**
+ * Parser debugging flag.
+ * When debug is active, javacc Parser will contain (among other things)
+ * a trace_call() method. So we use the presence of this method to
+ * initialize our flag.
+ */
+ private static boolean debugParser;
+ static
+ {
+ try
+ {
+ StandardParser.class.getDeclaredMethod("trace_call", String.class);
+ debugParser = true;
+ }
+ catch(NoSuchMethodException nsfe)
+ {
+ debugParser = false;
+ }
+ }
+
+ /**
+ * Our own trace method. Use sparsingly in production, since each
+ * and every call will introduce an execution branch and slow down parsing.
+ */
+ public static void trace(String message)
+ {
+ if (debugParser) System.out.println(message);
+ }
+
+ /**
+ * Keep track of defined macros, used for escape processing
+ */
+ private Map macroNames = new HashMap();
+
+ /**
+ * Current template we are parsing. Passed to us in parse()
+ */
+ public Template currentTemplate = null;
+
+ /**
+ * Set to true if the property
+ * RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE is set to true
+ */
+ public boolean strictEscape = false;
+
+ /**
+ * Set to true if the propoerty
+ * RuntimeConstants.PARSER_HYPHEN_ALLOWED is set to true
+ */
+ public boolean hyphenAllowedInIdentifiers = false;
+
+ VelocityCharStream velcharstream = null;
+
+ private RuntimeServices rsvc = null;
+
+ @Override
+ public RuntimeServices getRuntimeServices()
+ {
+ return rsvc;
+ }
+
+ private Logger log = null;
+
+ /**
+ * This constructor was added to allow the re-use of parsers.
+ * The normal constructor takes a single argument which
+ * an InputStream. This simply creates a re-usable parser
+ * object, we satisfy the requirement of an InputStream
+ * by using a newline character as an input stream.
+ */
+ public StandardParser( RuntimeServices rs)
+ {
+ /*
+ * need to call the CTOR first thing.
+ */
+
+ this( new VelocityCharStream(
+ new ByteArrayInputStream("\u005cn".getBytes()), 1, 1 ));
+
+ /*
+ * then initialize logger
+ */
+
+ log = rs.getLog("parser");
+
+
+ /*
+ * now setup a VCS for later use
+ */
+ velcharstream = new VelocityCharStream(
+ new ByteArrayInputStream("\u005cn".getBytes()), 1, 1 );
+
+
+ strictEscape =
+ rs.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, false);
+
+ hyphenAllowedInIdentifiers =
+ rs.getBoolean(RuntimeConstants.PARSER_HYPHEN_ALLOWED, false);
+
+ /*
+ * and save the RuntimeServices
+ */
+ rsvc = rs;
+
+ /*
+ * then initialize customizable characters
+ */
+ dollar = '$';
+ hash = '#';
+ at = '@';
+ asterisk = '*';
+ }
+
+ /**
+ * This was also added to allow parsers to be
+ * re-usable. Normal JavaCC use entails passing an
+ * input stream to the constructor and the parsing
+ * process is carried out once. We want to be able
+ * to re-use parsers: we do this by adding this
+ * method and re-initializing the lexer with
+ * the new stream that we want parsed.
+ */
+ @Override
+ public SimpleNode parse( Reader reader, Template template )
+ throws ParseException
+ {
+ SimpleNode sn = null;
+
+ currentTemplate = template;
+
+ try
+ {
+ token_source.clearStateVars();
+
+ /*
+ * reinitialize the VelocityCharStream
+ * with the new reader
+ */
+ velcharstream.ReInit( reader, 1, 1 );
+
+ /*
+ * now reinit the Parser with this CharStream
+ */
+ ReInit( velcharstream );
+
+ /*
+ * do that voodoo...
+ */
+ sn = process();
+ }
+ catch (MacroParseException mee)
+ {
+ /*
+ * thrown by the Macro class when something is amiss in the
+ * Macro specification
+ */
+ log.error("{}: {}", template.getName(), mee.getMessage(), mee);
+ throw mee;
+ }
+ catch (ParseException pe)
+ {
+ log.error("{}: {}", currentTemplate.getName(), pe.getMessage());
+ throw new TemplateParseException (pe.currentToken,
+ pe.expectedTokenSequences, pe.tokenImage, currentTemplate.getName());
+ }
+ catch (TokenMgrError tme)
+ {
+ throw new ParseException("Lexical error: " + tme.toString());
+ }
+ catch (Exception e)
+ {
+ String msg = template.getName() + ": " + e.getMessage();
+ log.error(msg, e);
+ throw new VelocityException(msg, e, getRuntimeServices().getLogContext().getStackTrace());
+ }
+
+ currentTemplate = null;
+
+ return sn;
+ }
+
+ /**
+ * This method gets a Directive from the directives Hashtable
+ */
+ @Override
+ public Directive getDirective(String directive)
+ {
+ return (Directive) rsvc.getDirective(directive);
+ }
+
+ /**
+ * This method finds out of the directive exists in the directives Map.
+ */
+ @Override
+ public boolean isDirective(String directive)
+ {
+ return rsvc.getDirective(directive) != null;
+ }
+
+
+ /**
+ * Produces a processed output for an escaped control or
+ * pluggable directive
+ */
+ private String escapedDirective( String strImage )
+ {
+ int iLast = strImage.lastIndexOf("\u005c\u005c");
+
+ String strDirective = strImage.substring(iLast + 1);
+
+ boolean bRecognizedDirective = false;
+
+ // we don't have to call substring method all the time in this method
+ String dirTag = strDirective.substring(1);
+ if (dirTag.charAt(0) == '{')
+ {
+ dirTag = dirTag.substring(1, dirTag.length() - 1);
+ }
+
+ /*
+ * If this is a predefined derective or if we detect
+ * a macro definition (this is aproximate at best) then
+ * we absorb the forward slash. If in strict reference
+ * mode then we always absord the forward slash regardless
+ * if the derective is defined or not.
+ */
+
+ if (strictEscape
+ || isDirective(dirTag)
+ || macroNames.containsKey(dirTag)
+ || rsvc.isVelocimacro(dirTag, currentTemplate))
+ {
+ bRecognizedDirective = true;
+ }
+ else
+ {
+ /* order for speed? */
+
+ if ( dirTag.equals("if")
+ || dirTag.equals("end")
+ || dirTag.equals("set")
+ || dirTag.equals("else")
+ || dirTag.equals("elseif")
+ )
+ {
+ bRecognizedDirective = true;
+ }
+ }
+
+ /*
+ * if so, make the proper prefix string (let the escapes do their thing..)
+ * otherwise, just return what it is..
+ */
+
+ if (bRecognizedDirective)
+ return ( strImage.substring(0,iLast/2) + strDirective);
+ else
+ return ( strImage );
+ }
+
+ /**
+ * Check whether there is a left parenthesis with leading optional
+ * whitespaces. This method is used in the semantic look ahead of
+ * Directive method. This is done in code instead of as a production
+ * for simplicity and efficiency.
+ */
+ private boolean isLeftParenthesis()
+ {
+ char c;
+ int no = 0;
+ try {
+ while(true)
+ {
+ /**
+ * Read a character
+ */
+ c = velcharstream.readChar();
+ no++;
+ if (c == '(')
+ {
+ return true;
+ }
+ /**
+ * if not a white space return
+ */
+ else if (c != ' ' && c != '\u005cn' && c != '\u005cr' && c != '\u005ct')
+ {
+ return false;
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ }
+ finally
+ {
+ /**
+ * Backup the stream to the initial state
+ */
+ velcharstream.backup(no);
+ }
+ return false;
+ }
+
+ /**
+ * Check whether there is a right parenthesis with leading optional
+ * whitespaces. This method is used in the semantic look ahead of
+ * Directive method. This is done in code instead of as a production
+ * for simplicity and efficiency.
+ */
+ private boolean isRightParenthesis()
+ {
+ char c;
+ int no = -1;
+ try {
+ while(true)
+ {
+ /**
+ * Read a character
+ */
+ if (no == -1)
+ {
+ switch (getToken(1).kind)
+ {
+ case RPAREN:
+ return true;
+ case WHITESPACE:
+ case NEWLINE:
+ no = 0;
+ break;
+ default:
+ return false;
+ }
+ }
+ c = velcharstream.readChar();
+ no++;
+ if (c == ')')
+ {
+ return true;
+ }
+ /**
+ * if not a white space return
+ */
+ else if (c != ' ' && c != '\u005cn' && c != '\u005cr' && c != '\u005ct')
+ {
+ return false;
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ }
+ finally
+ {
+ /**
+ * Backup the stream to the initial state
+ */
+ if (no > 0) velcharstream.backup(no);
+ }
+ return false;
+ }
+
+ /**
+ * We use this method in a lookahead to determine if we are in a macro
+ * default value assignment. The standard lookahead is not smart enough.
+ * here we look for the equals after the reference.
+ */
+ private boolean isAssignment()
+ {
+ // Basically if the last character read was not '$' then false
+ if (token_source.getCurrentLexicalState() != REFERENCE) return false;
+
+ char c = ' ';
+ int backup = 0;
+ try
+ {
+ // Read through any white space
+ while(Character.isWhitespace(c))
+ {
+ c = velcharstream.readChar();
+ backup++;
+ }
+
+ // This is what we are ultimately looking for
+ if (c != '=') return false;
+ }
+ catch (IOException e)
+ {
+ }
+ finally
+ {
+ velcharstream.backup(backup);
+ }
+
+ return true;
+ }
+
+ @Override
+ public Template getCurrentTemplate()
+ {
+ return currentTemplate;
+ }
+
+ @Override
+ public void resetCurrentTemplate()
+ {
+ currentTemplate = null;
+ }
+
+ @Override
+ public char dollar()
+ {
+ return dollar;
+ }
+
+ @Override
+ public char hash()
+ {
+ return hash;
+ }
+
+ @Override
+ public char at()
+ {
+ return at;
+ }
+
+ @Override
+ public char asterisk()
+ {
+ return asterisk;
+ }
+
+ private char dollar = '$';
+ private char hash = '#';
+ private char at = '@';
+ private char asterisk = '*';
+
+/**
+ * This method is what starts the whole parsing
+ * process. After the parsing is complete and
+ * the template has been turned into an AST,
+ * this method returns the root of AST which
+ * can subsequently be traversed by a visitor
+ * which implements the ParserVisitor interface
+ * which is generated automatically by JavaCC
+ */
+ final public SimpleNode process() throws ParseException {
+ /*@bgen(jjtree) process */
+ ASTprocess jjtn000 = new ASTprocess(this, JJTPROCESS);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);boolean afterNewline = true;
+ try {
+ label_1:
+ while (true) {
+ if (getToken(1).kind != EOF) {
+ ;
+ } else {
+ break label_1;
+ }
+ afterNewline = Statement(afterNewline);
+ }
+ jj_consume_token(0);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return jjtn000;}
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+/**
+ * These are the types of statements that
+ * are acceptable in Velocity templates.
+ */
+ final public boolean Statement(boolean afterNewline) throws ParseException {
+ if (getToken(1).kind == IF_DIRECTIVE || afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == IF_DIRECTIVE) {
+ afterNewline = IfStatement(afterNewline);
+ {if (true) return afterNewline;}
+ } else if (jj_2_1(2)) {
+ Reference();
+ {if (true) return false;}
+ } else if (jj_2_2(2)) {
+ afterNewline = Comment();
+ {if (true) return afterNewline;}
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case TEXTBLOCK:
+ Textblock();
+ {if (true) return false;}
+ break;
+ default:
+ jj_la1[1] = jj_gen;
+ if (getToken(1).kind == SET_DIRECTIVE || afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == SET_DIRECTIVE) {
+ afterNewline = SetDirective(afterNewline);
+ {if (true) return afterNewline;}
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case ESCAPE_DIRECTIVE:
+ EscapedDirective();
+ {if (true) return false;}
+ break;
+ case DOUBLE_ESCAPE:
+ Escape();
+ {if (true) return false;}
+ break;
+ default:
+ jj_la1[2] = jj_gen;
+ if (getToken(1).kind == WORD || getToken(1).kind == BRACKETED_WORD || afterNewline && getToken(1).kind == WHITESPACE && ( getToken(2).kind == WORD || getToken(2).kind == BRACKETED_WORD )) {
+ afterNewline = Directive(afterNewline);
+ {if (true) return afterNewline;}
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LONE_SYMBOL:
+ case PIPE:
+ case LPAREN:
+ case RPAREN:
+ case STRING_LITERAL:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case DOT:
+ case LCURLY:
+ case RCURLY:
+ case ESCAPE:
+ case TEXT:
+ case EMPTY_INDEX:
+ afterNewline = Text();
+ {if (true) return afterNewline;}
+ break;
+ case NEWLINE:
+ ASTText jjtn001 = new ASTText(this, JJTTEXT);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ jj_consume_token(NEWLINE);
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, true);
+ }
+ }
+ {if (true) return true;}
+ break;
+ case INLINE_TEXT:
+ ASTText jjtn002 = new ASTText(this, JJTTEXT);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ jj_consume_token(INLINE_TEXT);
+ afterNewline = false;
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case TEXT:
+ jj_consume_token(TEXT);
+ afterNewline = true;
+ break;
+ default:
+ jj_la1[0] = jj_gen;
+ ;
+ }
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, true);
+ }
+ }
+ {if (true) return afterNewline;}
+ break;
+ case WHITESPACE:
+ ASTText jjtn003 = new ASTText(this, JJTTEXT);
+ boolean jjtc003 = true;
+ jjtree.openNodeScope(jjtn003);
+ try {
+ jj_consume_token(WHITESPACE);
+ } finally {
+ if (jjtc003) {
+ jjtree.closeNodeScope(jjtn003, true);
+ }
+ }
+ {if (true) return false;}
+ break;
+ case SUFFIX:
+ ASTText jjtn004 = new ASTText(this, JJTTEXT);
+ boolean jjtc004 = true;
+ jjtree.openNodeScope(jjtn004);
+ try {
+ jj_consume_token(SUFFIX);
+ } finally {
+ if (jjtc004) {
+ jjtree.closeNodeScope(jjtn004, true);
+ }
+ }
+ {if (true) return true;}
+ break;
+ default:
+ jj_la1[3] = jj_gen;
+ if (jj_2_3(2)) {
+ EndingZeroWidthWhitespace();
+ {if (true) return afterNewline;}
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_OR_2:
+ ASTText jjtn005 = new ASTText(this, JJTTEXT);
+ boolean jjtc005 = true;
+ jjtree.openNodeScope(jjtn005);
+ try {
+ jj_consume_token(LOGICAL_OR_2);
+ } finally {
+ if (jjtc005) {
+ jjtree.closeNodeScope(jjtn005, true);
+ }
+ }
+ {if (true) return afterNewline;}
+ break;
+ case ZERO_WIDTH_WHITESPACE:
+ ASTText jjtn006 = new ASTText(this, JJTTEXT);
+ boolean jjtc006 = true;
+ jjtree.openNodeScope(jjtn006);
+ try {
+ jj_consume_token(ZERO_WIDTH_WHITESPACE);
+ } finally {
+ if (jjtc006) {
+ jjtree.closeNodeScope(jjtn006, true);
+ }
+ }
+ afterNewline = !afterNewline; {if (true) return false;}
+ break;
+ default:
+ jj_la1[4] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+ final public void EndingZeroWidthWhitespace() throws ParseException {
+ jj_consume_token(ZERO_WIDTH_WHITESPACE);
+ jj_consume_token(0);
+
+ }
+
+/**
+ * used to separate the notion of a valid directive that has been
+ * escaped, versus something that looks like a directive and
+ * is just schmoo. This is important to do as a separate production
+ * that creates a node, because we want this, in either case, to stop
+ * the further parsing of the Directive() tree.
+ */
+ final public void EscapedDirective() throws ParseException {
+ /*@bgen(jjtree) EscapedDirective */
+ ASTEscapedDirective jjtn000 = new ASTEscapedDirective(this, JJTESCAPEDDIRECTIVE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ Token t = null;
+ t = jj_consume_token(ESCAPE_DIRECTIVE);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ /*
+ * churn and burn..
+ */
+ t.image = escapedDirective( t.image );
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * Used to catch and process escape sequences in grammatical constructs
+ * as escapes outside of VTL are just characters. Right now we have both
+ * this and the EscapeDirective() construction because in the EscapeDirective()
+ * case, we want to suck in the #&lt;directive&gt; and here we don't. We just want
+ * the escapes to render correctly
+ */
+ final public void Escape() throws ParseException {
+ /*@bgen(jjtree) Escape */
+ ASTEscape jjtn000 = new ASTEscape(this, JJTESCAPE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ Token t = null;
+ int count = 0;
+ boolean control = false;
+ label_2:
+ while (true) {
+ t = jj_consume_token(DOUBLE_ESCAPE);
+ count++;
+ if (jj_2_4(2)) {
+ ;
+ } else {
+ break label_2;
+ }
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ /*
+ * first, check to see if we have a control directive
+ */
+ switch(t.next.kind ) {
+ case IF_DIRECTIVE :
+ case ELSE :
+ case ELSEIF :
+ case END :
+ control = true;
+ break;
+ }
+
+ /*
+ * if that failed, lets lookahead to see if we matched a PD or a VM
+ */
+ String nTag = t.next.image.substring(1);
+ if (strictEscape
+ || isDirective(nTag)
+ || macroNames.containsKey(nTag)
+ || rsvc.isVelocimacro(nTag, currentTemplate))
+ {
+ control = true;
+ }
+
+ jjtn000.val = "";
+
+ for( int i = 0; i < count; i++)
+ jjtn000.val += ( control ? "\u005c\u005c" : "\u005c\u005c\u005c\u005c");
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public boolean Comment() throws ParseException {
+ /*@bgen(jjtree) Comment */
+ ASTComment jjtn000 = new ASTComment(this, JJTCOMMENT);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SINGLE_LINE_COMMENT_START:
+ jj_consume_token(SINGLE_LINE_COMMENT_START);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SINGLE_LINE_COMMENT:
+ jj_consume_token(SINGLE_LINE_COMMENT);
+ break;
+ default:
+ jj_la1[5] = jj_gen;
+ ;
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return true;}
+ break;
+ case MULTI_LINE_COMMENT:
+ jj_consume_token(MULTI_LINE_COMMENT);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case FORMAL_COMMENT:
+ jj_consume_token(FORMAL_COMMENT);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ default:
+ jj_la1[6] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+ final public void Textblock() throws ParseException {
+ /*@bgen(jjtree) Textblock */
+ ASTTextblock jjtn000 = new ASTTextblock(this, JJTTEXTBLOCK);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(TEXTBLOCK);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void FloatingPointLiteral() throws ParseException {
+ /*@bgen(jjtree) FloatingPointLiteral */
+ ASTFloatingPointLiteral jjtn000 = new ASTFloatingPointLiteral(this, JJTFLOATINGPOINTLITERAL);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(FLOATING_POINT_LITERAL);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void IntegerLiteral() throws ParseException {
+ /*@bgen(jjtree) IntegerLiteral */
+ ASTIntegerLiteral jjtn000 = new ASTIntegerLiteral(this, JJTINTEGERLITERAL);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(INTEGER_LITERAL);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void StringLiteral() throws ParseException {
+ /*@bgen(jjtree) StringLiteral */
+ ASTStringLiteral jjtn000 = new ASTStringLiteral(this, JJTSTRINGLITERAL);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(STRING_LITERAL);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * This method corresponds to variable
+ * references in Velocity templates.
+ * The following are examples of variable
+ * references that may be found in a
+ * template:
+ *
+ * $foo
+ * $bar
+ *
+ */
+ final public void Identifier() throws ParseException {
+ /*@bgen(jjtree) Identifier */
+ ASTIdentifier jjtn000 = new ASTIdentifier(this, JJTIDENTIFIER);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ jj_consume_token(IDENTIFIER);
+ break;
+ case OLD_IDENTIFIER:
+ jj_consume_token(OLD_IDENTIFIER);
+ break;
+ default:
+ jj_la1[7] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void Word() throws ParseException {
+ /*@bgen(jjtree) Word */
+ ASTWord jjtn000 = new ASTWord(this, JJTWORD);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(WORD);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * Supports the arguments for the Pluggable Directives
+ */
+ final public int DirectiveArg() throws ParseException {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Reference();
+ {if (true) return ParserTreeConstants.JJTREFERENCE;}
+ break;
+ case WORD:
+ Word();
+ {if (true) return ParserTreeConstants.JJTWORD;}
+ break;
+ case STRING_LITERAL:
+ StringLiteral();
+ {if (true) return ParserTreeConstants.JJTSTRINGLITERAL;}
+ break;
+ case INTEGER_LITERAL:
+ IntegerLiteral();
+ {if (true) return ParserTreeConstants.JJTINTEGERLITERAL;}
+ break;
+ default:
+ jj_la1[8] = jj_gen;
+ if (jj_2_5(2147483647)) {
+ IntegerRange();
+ {if (true) return ParserTreeConstants.JJTINTEGERRANGE;}
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case FLOATING_POINT_LITERAL:
+ FloatingPointLiteral();
+ {if (true) return ParserTreeConstants.JJTFLOATINGPOINTLITERAL;}
+ break;
+ case LEFT_CURLEY:
+ Map();
+ {if (true) return ParserTreeConstants.JJTMAP;}
+ break;
+ case LBRACKET:
+ ObjectArray();
+ {if (true) return ParserTreeConstants.JJTOBJECTARRAY;}
+ break;
+ case TRUE:
+ True();
+ {if (true) return ParserTreeConstants.JJTTRUE;}
+ break;
+ case FALSE:
+ False();
+ {if (true) return ParserTreeConstants.JJTFALSE;}
+ break;
+ default:
+ jj_la1[9] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+ final public void DirectiveAssign() throws ParseException {
+ /*@bgen(jjtree) DirectiveAssign */
+ ASTDirectiveAssign jjtn000 = new ASTDirectiveAssign(this, JJTDIRECTIVEASSIGN);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ Reference();
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * Supports the Pluggable Directives
+ * #foo( arg+ )
+ * @return true if ends with a newline
+ */
+ final public boolean Directive(boolean afterNewline) throws ParseException {
+ /*@bgen(jjtree) Directive */
+ ASTDirective jjtn000 = new ASTDirective(this, JJTDIRECTIVE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);Token id = null, t = null, u = null, end = null, _else = null;
+ int argType;
+ int argPos = 0;
+ Directive d;
+ int directiveType;
+ boolean isVM = false;
+ boolean isMacro = false;
+ ArrayList argtypes = new ArrayList(4);
+ String blockPrefix = "";
+ ASTBlock block = null, elseBlock = null;
+ boolean hasParentheses = false;
+ boolean newlineAtStart = afterNewline;
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ // only possible if not after new line
+ jjtn000.setPrefix(t.image);
+ t = null;
+ break;
+ default:
+ jj_la1[10] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WORD:
+ id = jj_consume_token(WORD);
+ break;
+ case BRACKETED_WORD:
+ id = jj_consume_token(BRACKETED_WORD);
+ break;
+ default:
+ jj_la1[11] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ String directiveName;
+ int p = id.image.lastIndexOf(hash);
+ if (id.kind == StandardParserConstants.BRACKETED_WORD)
+ {
+ directiveName = id.image.substring(p + 2, id.image.length() - 1);
+ }
+ else
+ {
+ directiveName = id.image.substring(p + 1);
+ }
+
+ d = getDirective(directiveName);
+
+ /*
+ * Velocimacro support : if the directive is macro directive
+ * then set the flag so after the block parsing, we add the VM
+ * right then. (So available if used w/in the current template )
+ */
+
+ if (directiveName.equals("macro"))
+ {
+ isMacro = true;
+ }
+
+ /*
+ * set the directive name from here. No reason for the thing to know
+ * about parser tokens
+ */
+
+ jjtn000.setDirectiveName(directiveName);
+
+ if ( d == null)
+ {
+ if( directiveName.charAt(0) == at )
+ {
+ // block macro call of type: #@foobar($arg1 $arg2) astBody #end
+ directiveType = Directive.BLOCK;
+ }
+ else
+ {
+ /*
+ * if null, then not a real directive, but maybe a Velocimacro
+ */
+ isVM = rsvc.isVelocimacro(directiveName, currentTemplate);
+
+ directiveType = Directive.LINE;
+ }
+ }
+ else
+ {
+ directiveType = d.getType();
+ }
+
+ /*
+ * now, switch us out of PRE_DIRECTIVE
+ */
+
+ token_source.switchTo(DIRECTIVE);
+ argPos = 0;
+ if (isLeftParenthesis()) {
+ label_3:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[12] = jj_gen;
+ break label_3;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[13] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(LPAREN);
+ label_4:
+ while (true) {
+ if (!isRightParenthesis()) {
+ ;
+ } else {
+ break label_4;
+ }
+ label_5:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[14] = jj_gen;
+ break label_5;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[15] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ jj_consume_token(COMMA);
+ label_6:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[16] = jj_gen;
+ break label_6;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[17] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ break;
+ default:
+ jj_la1[18] = jj_gen;
+ ;
+ }
+ if (jj_2_6(1)) {
+ if (isMacro && isAssignment()) {
+ DirectiveAssign();
+ label_7:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[19] = jj_gen;
+ break label_7;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[20] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(EQUALS);
+ label_8:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[21] = jj_gen;
+ break label_8;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[22] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ argtypes.add(ParserTreeConstants.JJTDIRECTIVEASSIGN);
+ } else {
+ ;
+ }
+ if (!isRightParenthesis()) {
+
+ } else {
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ argType = DirectiveArg();
+ argtypes.add(argType);
+ if (d == null && argType == ParserTreeConstants.JJTWORD)
+ {
+ if (isVM)
+ {
+ {if (true) throw new MacroParseException("Invalid argument "
+ + (argPos+1) + " in macro call " + id.image, currentTemplate.getName(), id);}
+ }
+ }
+ argPos++;
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SINGLE_LINE_COMMENT_START:
+ if (!isMacro)
+ {
+ // We only allow line comments in macro definitions for now
+ {if (true) throw new MacroParseException("A Line comment is not allowed in " + id.image
+ + " arguments", currentTemplate.getName(), id);}
+ }
+ jj_consume_token(SINGLE_LINE_COMMENT_START);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case SINGLE_LINE_COMMENT:
+ jj_consume_token(SINGLE_LINE_COMMENT);
+ break;
+ default:
+ jj_la1[23] = jj_gen;
+ ;
+ }
+ break;
+ default:
+ jj_la1[24] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ label_9:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[25] = jj_gen;
+ break label_9;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[26] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(RPAREN);
+ hasParentheses = true;
+ } else {
+ token_source.stateStackPop();
+ }
+ afterNewline = false;
+ if (jj_2_7(2) && (directiveType != Directive.LINE || newlineAtStart && rsvc.getSpaceGobbling() != SpaceGobbling.BC || rsvc.getSpaceGobbling() == SpaceGobbling.BC && hasParentheses || d != null && (d instanceof Include || d instanceof Parse))) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[27] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ afterNewline = true;
+ if (directiveType == Directive.LINE)
+ {
+ jjtn000.setPostfix(t == null ? u.image : t.image + u.image);
+ }
+ else
+ {
+ blockPrefix = (t == null ? u.image : t.image + u.image);
+ }
+ t = u = null;
+ } else {
+ ;
+ }
+ if (d != null)
+ {
+ d.checkArgs(argtypes, id, currentTemplate.getName());
+ }
+ if (directiveType == Directive.LINE)
+ {
+ {if (true) return afterNewline;}
+ }
+ ASTBlock jjtn001 = new ASTBlock(this, JJTBLOCK);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ label_10:
+ while (true) {
+ if (getToken(1).kind != END && getToken(1).kind != ELSE && ( !afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END && getToken(2).kind != ELSE )) {
+ ;
+ } else {
+ break label_10;
+ }
+ afterNewline = Statement(afterNewline);
+ }
+ jjtree.closeNodeScope(jjtn001, true);
+ jjtc001 = false;
+ block = jjtn001;
+ block.setPrefix(blockPrefix);
+ blockPrefix = "";
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, true);
+ }
+ }
+ if (jj_2_8(1) && (afterNewline)) {
+ t = jj_consume_token(WHITESPACE);
+ block.setPostfix(t.image);
+ t = null;
+ } else {
+ ;
+ }
+ if (d != null && (d instanceof Foreach) && getToken(1).kind == ELSE) {
+ _else = jj_consume_token(ELSE);
+ ASTBlock jjtn002 = new ASTBlock(this, JJTBLOCK);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ if (jj_2_9(2)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[28] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn002.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ } else {
+ ;
+ }
+ label_11:
+ while (true) {
+ if (getToken(1).kind != END && (!afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END)) {
+ ;
+ } else {
+ break label_11;
+ }
+ afterNewline = Statement(afterNewline);
+ }
+ jjtree.closeNodeScope(jjtn002, true);
+ jjtc002 = false;
+ elseBlock = jjtn002;
+ } catch (Throwable jjte002) {
+ if (jjtc002) {
+ jjtree.clearNodeScope(jjtn002);
+ jjtc002 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte002 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte002;}
+ }
+ if (jjte002 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte002;}
+ }
+ {if (true) throw (Error)jjte002;}
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, true);
+ }
+ }
+ int pos = _else.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ block.setMorePostfix(_else.image.substring(0, pos));
+ }
+ block = elseBlock;
+ } else {
+ ;
+ }
+ if (jj_2_10(1) && (afterNewline)) {
+ t = jj_consume_token(WHITESPACE);
+ block.setPostfix(t.image);
+ t = null;
+ afterNewline = false;
+ } else {
+ ;
+ }
+ end = jj_consume_token(END);
+ afterNewline = false;
+ if (jj_2_11(2) && (newlineAtStart || rsvc.getSpaceGobbling() == SpaceGobbling.BC)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[29] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn000.setPostfix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ } else {
+ ;
+ }
+ int pos = end.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ block.setMorePostfix(end.image.substring(0, pos));
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ /*
+ * VM : if we are processing a #macro directive, we need to
+ * process the block. In truth, I can just register the name
+ * and do the work later when init-ing. That would work
+ * as long as things were always defined before use. This way
+ * we don't have to worry about forward references and such...
+ */
+ if (isMacro)
+ {
+ // Add the macro name so that we can peform escape processing
+ // on defined macros
+ String macroName = jjtn000.jjtGetChild(0).getFirstToken().image;
+ macroNames.put(macroName, macroName);
+ }
+ if (d != null)
+ {
+ d.checkArgs(argtypes, id, currentTemplate.getName());
+ }
+ /*
+ * VM : end
+ */
+ {if (true) return afterNewline;}
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+/**
+ * for creating a map in a #set
+ *
+ * #set($foo = {$foo : $bar, $blargh : $thingy})
+ */
+ final public void Map() throws ParseException {
+ /*@bgen(jjtree) Map */
+ ASTMap jjtn000 = new ASTMap(this, JJTMAP);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(LEFT_CURLEY);
+ if (jj_2_12(2147483647)) {
+ Parameter();
+ jj_consume_token(COLON);
+ Parameter();
+ label_12:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[30] = jj_gen;
+ break label_12;
+ }
+ jj_consume_token(COMMA);
+ Parameter();
+ jj_consume_token(COLON);
+ Parameter();
+ }
+ } else {
+ label_13:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[31] = jj_gen;
+ break label_13;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[32] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case RIGHT_CURLEY:
+ jj_consume_token(RIGHT_CURLEY);
+ break;
+ case RCURLY:
+ jj_consume_token(RCURLY);
+ break;
+ default:
+ jj_la1[33] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void ObjectArray() throws ParseException {
+ /*@bgen(jjtree) ObjectArray */
+ ASTObjectArray jjtn000 = new ASTObjectArray(this, JJTOBJECTARRAY);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(LBRACKET);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACKET:
+ case LEFT_CURLEY:
+ case WHITESPACE:
+ case NEWLINE:
+ case STRING_LITERAL:
+ case TRUE:
+ case FALSE:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Parameter();
+ label_14:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[34] = jj_gen;
+ break label_14;
+ }
+ jj_consume_token(COMMA);
+ Parameter();
+ }
+ break;
+ default:
+ jj_la1[35] = jj_gen;
+ ;
+ }
+ jj_consume_token(RBRACKET);
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * supports the [n..m] vector generator for use in
+ * the #foreach() to generate measured ranges w/o
+ * needing explicit support from the app/servlet
+ */
+ final public void IntegerRange() throws ParseException {
+ /*@bgen(jjtree) IntegerRange */
+ ASTIntegerRange jjtn000 = new ASTIntegerRange(this, JJTINTEGERRANGE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(LBRACKET);
+ label_15:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[36] = jj_gen;
+ break label_15;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[37] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Reference();
+ break;
+ case INTEGER_LITERAL:
+ IntegerLiteral();
+ break;
+ default:
+ jj_la1[38] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ label_16:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[39] = jj_gen;
+ break label_16;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[40] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(DOUBLEDOT);
+ label_17:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[41] = jj_gen;
+ break label_17;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[42] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Reference();
+ break;
+ case INTEGER_LITERAL:
+ IntegerLiteral();
+ break;
+ default:
+ jj_la1[43] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ label_18:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[44] = jj_gen;
+ break label_18;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[45] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(RBRACKET);
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * A Simplified parameter more suitable for an index position: $foo[$index]
+ */
+ final public void IndexParameter() throws ParseException {
+ label_19:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[46] = jj_gen;
+ break label_19;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[47] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ Expression();
+ label_20:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[48] = jj_gen;
+ break label_20;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[49] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+/**
+ * This method has yet to be fully implemented
+ * but will allow arbitrarily nested method
+ * calls
+ */
+ final public void Parameter() throws ParseException {
+ label_21:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[50] = jj_gen;
+ break label_21;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[51] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STRING_LITERAL:
+ StringLiteral();
+ break;
+ case INTEGER_LITERAL:
+ IntegerLiteral();
+ break;
+ default:
+ jj_la1[52] = jj_gen;
+ if (jj_2_13(2147483647)) {
+ IntegerRange();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LEFT_CURLEY:
+ Map();
+ break;
+ case LBRACKET:
+ ObjectArray();
+ break;
+ case TRUE:
+ True();
+ break;
+ case FALSE:
+ False();
+ break;
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Reference();
+ break;
+ case FLOATING_POINT_LITERAL:
+ FloatingPointLiteral();
+ break;
+ default:
+ jj_la1[53] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ label_22:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[54] = jj_gen;
+ break label_22;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[55] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+/**
+ * This method has yet to be fully implemented
+ * but will allow arbitrarily nested method
+ * calls
+ */
+ final public void Method() throws ParseException {
+ /*@bgen(jjtree) Method */
+ ASTMethod jjtn000 = new ASTMethod(this, JJTMETHOD);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ Identifier();
+ jj_consume_token(LPAREN);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LBRACKET:
+ case LEFT_CURLEY:
+ case LPAREN:
+ case WHITESPACE:
+ case NEWLINE:
+ case STRING_LITERAL:
+ case TRUE:
+ case FALSE:
+ case MINUS:
+ case LOGICAL_NOT:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Expression();
+ label_23:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case COMMA:
+ ;
+ break;
+ default:
+ jj_la1[56] = jj_gen;
+ break label_23;
+ }
+ jj_consume_token(COMMA);
+ Expression();
+ }
+ break;
+ default:
+ jj_la1[57] = jj_gen;
+ ;
+ }
+ jj_consume_token(REFMOD2_RPAREN);
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void Index() throws ParseException {
+ /*@bgen(jjtree) Index */
+ ASTIndex jjtn000 = new ASTIndex(this, JJTINDEX);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(INDEX_LBRACKET);
+ IndexParameter();
+ jj_consume_token(INDEX_RBRACKET);
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void Reference() throws ParseException {
+ /*@bgen(jjtree) Reference */
+ ASTReference jjtn000 = new ASTReference(this, JJTREFERENCE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ jj_consume_token(IDENTIFIER);
+ break;
+ case OLD_IDENTIFIER:
+ jj_consume_token(OLD_IDENTIFIER);
+ break;
+ default:
+ jj_la1[58] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ label_24:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INDEX_LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[59] = jj_gen;
+ break label_24;
+ }
+ Index();
+ }
+ label_25:
+ while (true) {
+ if (jj_2_14(2)) {
+ ;
+ } else {
+ break label_25;
+ }
+ jj_consume_token(DOT);
+ if (jj_2_15(3)) {
+ Method();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ Identifier();
+ break;
+ default:
+ jj_la1[60] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ label_26:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INDEX_LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[61] = jj_gen;
+ break label_26;
+ }
+ Index();
+ }
+ }
+ break;
+ case LCURLY:
+ jj_consume_token(LCURLY);
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ jj_consume_token(IDENTIFIER);
+ break;
+ case OLD_IDENTIFIER:
+ jj_consume_token(OLD_IDENTIFIER);
+ break;
+ default:
+ jj_la1[62] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ label_27:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INDEX_LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[63] = jj_gen;
+ break label_27;
+ }
+ Index();
+ }
+ label_28:
+ while (true) {
+ if (jj_2_16(2)) {
+ ;
+ } else {
+ break label_28;
+ }
+ jj_consume_token(DOT);
+ if (jj_2_17(3)) {
+ Method();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ Identifier();
+ break;
+ default:
+ jj_la1[64] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ label_29:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case INDEX_LBRACKET:
+ ;
+ break;
+ default:
+ jj_la1[65] = jj_gen;
+ break label_29;
+ }
+ Index();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PIPE:
+ jj_consume_token(PIPE);
+ Expression();
+ break;
+ default:
+ jj_la1[66] = jj_gen;
+ ;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case RCURLY:
+ jj_consume_token(RCURLY);
+ break;
+ case RIGHT_CURLEY:
+ jj_consume_token(RIGHT_CURLEY);
+ break;
+ default:
+ jj_la1[67] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ break;
+ default:
+ jj_la1[68] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void True() throws ParseException {
+ /*@bgen(jjtree) True */
+ ASTTrue jjtn000 = new ASTTrue(this, JJTTRUE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(TRUE);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void False() throws ParseException {
+ /*@bgen(jjtree) False */
+ ASTFalse jjtn000 = new ASTFalse(this, JJTFALSE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ jj_consume_token(FALSE);
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+/**
+ * This method is responsible for allowing
+ * all non-grammar text to pass through
+ * unscathed.
+ * @return true if last read token was a newline
+ */
+ final public boolean Text() throws ParseException {
+ /*@bgen(jjtree) Text */
+ ASTText jjtn000 = new ASTText(this, JJTTEXT);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);Token t = null;
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case TEXT:
+ jj_consume_token(TEXT);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return true;}
+ break;
+ case DOT:
+ jj_consume_token(DOT);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case RPAREN:
+ jj_consume_token(RPAREN);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case LPAREN:
+ jj_consume_token(LPAREN);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case INTEGER_LITERAL:
+ jj_consume_token(INTEGER_LITERAL);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case FLOATING_POINT_LITERAL:
+ jj_consume_token(FLOATING_POINT_LITERAL);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case STRING_LITERAL:
+ jj_consume_token(STRING_LITERAL);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case ESCAPE:
+ jj_consume_token(ESCAPE);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case LCURLY:
+ jj_consume_token(LCURLY);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case RCURLY:
+ jj_consume_token(RCURLY);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case EMPTY_INDEX:
+ jj_consume_token(EMPTY_INDEX);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case PIPE:
+ jj_consume_token(PIPE);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return false;}
+ break;
+ case LONE_SYMBOL:
+ t = jj_consume_token(LONE_SYMBOL);
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ /* Drop the ending zero-width whitespace */
+ t.image = t.image.substring(0, t.image.length() - 1); {if (true) return false;}
+ break;
+ default:
+ jj_la1[69] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+/* -----------------------------------------------------------------------
+ *
+ * Defined Directive Syntax
+ *
+ * ----------------------------------------------------------------------*/
+ final public boolean IfStatement(boolean afterNewline) throws ParseException {
+ /*@bgen(jjtree) IfStatement */
+ ASTIfStatement jjtn000 = new ASTIfStatement(this, JJTIFSTATEMENT);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);Token t = null, u = null, end = null;
+ ASTBlock lastBlock = null;
+ boolean newlineAtStart = afterNewline;
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ // only possible if not after new line
+ jjtn000.setPrefix(t.image);
+ t = null;
+ break;
+ default:
+ jj_la1[70] = jj_gen;
+ ;
+ }
+ jj_consume_token(IF_DIRECTIVE);
+ label_30:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[71] = jj_gen;
+ break label_30;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[72] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(LPAREN);
+ Expression();
+ jj_consume_token(RPAREN);
+ ASTBlock jjtn001 = new ASTBlock(this, JJTBLOCK);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ if (jj_2_18(2)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[73] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn001.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ } else {
+ ;
+ }
+ label_31:
+ while (true) {
+ if ((getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && getToken(1).kind != END) &&
+ (!afterNewline || getToken(1).kind != WHITESPACE || (getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != END))) {
+ ;
+ } else {
+ break label_31;
+ }
+ afterNewline = Statement(afterNewline);
+ }
+ jjtree.closeNodeScope(jjtn001, true);
+ jjtc001 = false;
+ lastBlock = jjtn001;
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, true);
+ }
+ }
+ if (getToken(1).kind == ELSEIF || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF)) {
+ label_32:
+ while (true) {
+ lastBlock = ElseIfStatement(lastBlock, afterNewline);
+ afterNewline = lastBlock.endsWithNewline;
+ if (getToken(1).kind == ELSEIF || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF)) {
+ ;
+ } else {
+ break label_32;
+ }
+ }
+ } else {
+ ;
+ }
+ if (getToken(1).kind == ELSE || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSE)) {
+ lastBlock = ElseStatement(lastBlock, afterNewline);
+ afterNewline = lastBlock.endsWithNewline;
+ } else {
+ ;
+ }
+ if (jj_2_19(1) && (afterNewline)) {
+ t = jj_consume_token(WHITESPACE);
+ lastBlock.setPostfix(t.image);
+ t = null;
+ } else {
+ ;
+ }
+ end = jj_consume_token(END);
+ afterNewline = false;
+ if (jj_2_20(2) && (newlineAtStart || rsvc.getSpaceGobbling() == SpaceGobbling.BC)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[74] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn000.setPostfix(t == null ? u.image : t.image + u.image);
+ afterNewline = true;
+ } else {
+ ;
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ int pos = end.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ lastBlock.setMorePostfix(end.image.substring(0, pos));
+ }
+ {if (true) return afterNewline;}
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ASTBlock ElseStatement(ASTBlock previousBlock, boolean afterNewline) throws ParseException {
+ /*@bgen(jjtree) ElseStatement */
+ ASTElseStatement jjtn000 = new ASTElseStatement(this, JJTELSESTATEMENT);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);Token t = null, u = null, _else = null;
+ ASTBlock block = null;
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ previousBlock.setPostfix(t.image);
+ t = null;
+ break;
+ default:
+ jj_la1[75] = jj_gen;
+ ;
+ }
+ _else = jj_consume_token(ELSE);
+ ASTBlock jjtn001 = new ASTBlock(this, JJTBLOCK);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ if (jj_2_21(2)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[76] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn001.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ } else {
+ ;
+ }
+ label_33:
+ while (true) {
+ if (getToken(1).kind != END && (!afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END)) {
+ ;
+ } else {
+ break label_33;
+ }
+ afterNewline = Statement(afterNewline);
+ }
+ jjtree.closeNodeScope(jjtn001, true);
+ jjtc001 = false;
+ block = jjtn001;
+ block.endsWithNewline = afterNewline;
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, true);
+ }
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ int pos = _else.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ previousBlock.setMorePostfix(_else.image.substring(0, pos));
+ }
+ {if (true) return block;}
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+ final public ASTBlock ElseIfStatement(ASTBlock previousBlock, boolean afterNewline) throws ParseException {
+ /*@bgen(jjtree) ElseIfStatement */
+ ASTElseIfStatement jjtn000 = new ASTElseIfStatement(this, JJTELSEIFSTATEMENT);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);Token t = null, u = null, elseif = null;
+ ASTBlock block = null;
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ previousBlock.setPostfix(t.image);
+ t = null;
+ break;
+ default:
+ jj_la1[77] = jj_gen;
+ ;
+ }
+ elseif = jj_consume_token(ELSEIF);
+ label_34:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[78] = jj_gen;
+ break label_34;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[79] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(LPAREN);
+ Expression();
+ jj_consume_token(RPAREN);
+ ASTBlock jjtn001 = new ASTBlock(this, JJTBLOCK);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ if (jj_2_22(2)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[80] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn001.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ } else {
+ ;
+ }
+ label_35:
+ while (true) {
+ if ((getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && getToken(1).kind != END) && (!afterNewline || getToken(1).kind != WHITESPACE || (getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != END))) {
+ ;
+ } else {
+ break label_35;
+ }
+ afterNewline = Statement(afterNewline);
+ }
+ jjtree.closeNodeScope(jjtn001, true);
+ jjtc001 = false;
+ block = jjtn001;
+ block.endsWithNewline = afterNewline;
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, true);
+ }
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ int pos = elseif.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ previousBlock.setMorePostfix(elseif.image.substring(0, pos));
+ }
+ {if (true) return block;}
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+/**
+ * Currently support both types of set :
+ * #set( expr )
+ * #set expr
+ */
+ final public boolean SetDirective(boolean afterNewline) throws ParseException {
+ /*@bgen(jjtree) SetDirective */
+ ASTSetDirective jjtn000 = new ASTSetDirective(this, JJTSETDIRECTIVE);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);Token t = null, u = null;
+ boolean endsWithNewline = false;
+ try {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ // only possible after new line
+ jjtn000.setPrefix(t.image);
+ t = null;
+ break;
+ default:
+ jj_la1[81] = jj_gen;
+ ;
+ }
+ jj_consume_token(SET_DIRECTIVE);
+ label_36:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[82] = jj_gen;
+ break label_36;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[83] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ Reference();
+ label_37:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[84] = jj_gen;
+ break label_37;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[85] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ jj_consume_token(EQUALS);
+ Expression();
+ jj_consume_token(RPAREN);
+ /*
+ * ensure that inSet is false. Leads to some amusing bugs...
+ */
+
+ token_source.setInSet(false);
+ if (jj_2_23(2) && (afterNewline || rsvc.getSpaceGobbling() == SpaceGobbling.BC)) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ t = jj_consume_token(WHITESPACE);
+ break;
+ default:
+ jj_la1[86] = jj_gen;
+ ;
+ }
+ u = jj_consume_token(NEWLINE);
+ jjtn000.setPostfix(t == null ? u.image : t.image + u.image);
+ endsWithNewline = true;
+ } else {
+ ;
+ }
+ jjtree.closeNodeScope(jjtn000, true);
+ jjtc000 = false;
+ {if (true) return endsWithNewline;}
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ throw new Error("Missing return statement in function");
+ }
+
+/* -----------------------------------------------------------------------
+ *
+ * Expression Syntax
+ *
+ * ----------------------------------------------------------------------*/
+ final public void Expression() throws ParseException {
+ /*@bgen(jjtree) Expression */
+ ASTExpression jjtn000 = new ASTExpression(this, JJTEXPRESSION);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ ConditionalOrExpression();
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, true);
+ }
+ }
+ }
+
+ final public void Assignment() throws ParseException {
+ /*@bgen(jjtree) #Assignment( 2) */
+ ASTAssignment jjtn000 = new ASTAssignment(this, JJTASSIGNMENT);
+ boolean jjtc000 = true;
+ jjtree.openNodeScope(jjtn000);
+ try {
+ PrimaryExpression();
+ jj_consume_token(EQUALS);
+ Expression();
+ } catch (Throwable jjte000) {
+ if (jjtc000) {
+ jjtree.clearNodeScope(jjtn000);
+ jjtc000 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte000 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte000;}
+ }
+ if (jjte000 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte000;}
+ }
+ {if (true) throw (Error)jjte000;}
+ } finally {
+ if (jjtc000) {
+ jjtree.closeNodeScope(jjtn000, 2);
+ }
+ }
+ }
+
+ final public void ConditionalOrExpression() throws ParseException {
+ ConditionalAndExpression();
+ label_38:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_OR_2:
+ case LOGICAL_OR:
+ ;
+ break;
+ default:
+ jj_la1[87] = jj_gen;
+ break label_38;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_OR:
+ jj_consume_token(LOGICAL_OR);
+ break;
+ case LOGICAL_OR_2:
+ jj_consume_token(LOGICAL_OR_2);
+ break;
+ default:
+ jj_la1[88] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ ASTOrNode jjtn001 = new ASTOrNode(this, JJTORNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ ConditionalAndExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 2);
+ }
+ }
+ }
+ }
+
+ final public void ConditionalAndExpression() throws ParseException {
+ EqualityExpression();
+ label_39:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_AND:
+ ;
+ break;
+ default:
+ jj_la1[89] = jj_gen;
+ break label_39;
+ }
+ jj_consume_token(LOGICAL_AND);
+ ASTAndNode jjtn001 = new ASTAndNode(this, JJTANDNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ EqualityExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 2);
+ }
+ }
+ }
+ }
+
+ final public void EqualityExpression() throws ParseException {
+ RelationalExpression();
+ label_40:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_EQUALS:
+ case LOGICAL_NOT_EQUALS:
+ ;
+ break;
+ default:
+ jj_la1[90] = jj_gen;
+ break label_40;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_EQUALS:
+ jj_consume_token(LOGICAL_EQUALS);
+ ASTEQNode jjtn001 = new ASTEQNode(this, JJTEQNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ RelationalExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 2);
+ }
+ }
+ break;
+ case LOGICAL_NOT_EQUALS:
+ jj_consume_token(LOGICAL_NOT_EQUALS);
+ ASTNENode jjtn002 = new ASTNENode(this, JJTNENODE);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ RelationalExpression();
+ } catch (Throwable jjte002) {
+ if (jjtc002) {
+ jjtree.clearNodeScope(jjtn002);
+ jjtc002 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte002 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte002;}
+ }
+ if (jjte002 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte002;}
+ }
+ {if (true) throw (Error)jjte002;}
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, 2);
+ }
+ }
+ break;
+ default:
+ jj_la1[91] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+ final public void RelationalExpression() throws ParseException {
+ AdditiveExpression();
+ label_41:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_LT:
+ case LOGICAL_LE:
+ case LOGICAL_GT:
+ case LOGICAL_GE:
+ ;
+ break;
+ default:
+ jj_la1[92] = jj_gen;
+ break label_41;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_LT:
+ jj_consume_token(LOGICAL_LT);
+ ASTLTNode jjtn001 = new ASTLTNode(this, JJTLTNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ AdditiveExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 2);
+ }
+ }
+ break;
+ case LOGICAL_GT:
+ jj_consume_token(LOGICAL_GT);
+ ASTGTNode jjtn002 = new ASTGTNode(this, JJTGTNODE);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ AdditiveExpression();
+ } catch (Throwable jjte002) {
+ if (jjtc002) {
+ jjtree.clearNodeScope(jjtn002);
+ jjtc002 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte002 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte002;}
+ }
+ if (jjte002 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte002;}
+ }
+ {if (true) throw (Error)jjte002;}
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, 2);
+ }
+ }
+ break;
+ case LOGICAL_LE:
+ jj_consume_token(LOGICAL_LE);
+ ASTLENode jjtn003 = new ASTLENode(this, JJTLENODE);
+ boolean jjtc003 = true;
+ jjtree.openNodeScope(jjtn003);
+ try {
+ AdditiveExpression();
+ } catch (Throwable jjte003) {
+ if (jjtc003) {
+ jjtree.clearNodeScope(jjtn003);
+ jjtc003 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte003 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte003;}
+ }
+ if (jjte003 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte003;}
+ }
+ {if (true) throw (Error)jjte003;}
+ } finally {
+ if (jjtc003) {
+ jjtree.closeNodeScope(jjtn003, 2);
+ }
+ }
+ break;
+ case LOGICAL_GE:
+ jj_consume_token(LOGICAL_GE);
+ ASTGENode jjtn004 = new ASTGENode(this, JJTGENODE);
+ boolean jjtc004 = true;
+ jjtree.openNodeScope(jjtn004);
+ try {
+ AdditiveExpression();
+ } catch (Throwable jjte004) {
+ if (jjtc004) {
+ jjtree.clearNodeScope(jjtn004);
+ jjtc004 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte004 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte004;}
+ }
+ if (jjte004 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte004;}
+ }
+ {if (true) throw (Error)jjte004;}
+ } finally {
+ if (jjtc004) {
+ jjtree.closeNodeScope(jjtn004, 2);
+ }
+ }
+ break;
+ default:
+ jj_la1[93] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+ final public void AdditiveExpression() throws ParseException {
+ MultiplicativeExpression();
+ label_42:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case MINUS:
+ case PLUS:
+ ;
+ break;
+ default:
+ jj_la1[94] = jj_gen;
+ break label_42;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case PLUS:
+ jj_consume_token(PLUS);
+ ASTAddNode jjtn001 = new ASTAddNode(this, JJTADDNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ MultiplicativeExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 2);
+ }
+ }
+ break;
+ case MINUS:
+ jj_consume_token(MINUS);
+ ASTSubtractNode jjtn002 = new ASTSubtractNode(this, JJTSUBTRACTNODE);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ MultiplicativeExpression();
+ } catch (Throwable jjte002) {
+ if (jjtc002) {
+ jjtree.clearNodeScope(jjtn002);
+ jjtc002 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte002 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte002;}
+ }
+ if (jjte002 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte002;}
+ }
+ {if (true) throw (Error)jjte002;}
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, 2);
+ }
+ }
+ break;
+ default:
+ jj_la1[95] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+ final public void MultiplicativeExpression() throws ParseException {
+ UnaryExpression();
+ label_43:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case MULTIPLY:
+ case DIVIDE:
+ case MODULUS:
+ ;
+ break;
+ default:
+ jj_la1[96] = jj_gen;
+ break label_43;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case MULTIPLY:
+ jj_consume_token(MULTIPLY);
+ ASTMulNode jjtn001 = new ASTMulNode(this, JJTMULNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ UnaryExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 2);
+ }
+ }
+ break;
+ case DIVIDE:
+ jj_consume_token(DIVIDE);
+ ASTDivNode jjtn002 = new ASTDivNode(this, JJTDIVNODE);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ UnaryExpression();
+ } catch (Throwable jjte002) {
+ if (jjtc002) {
+ jjtree.clearNodeScope(jjtn002);
+ jjtc002 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte002 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte002;}
+ }
+ if (jjte002 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte002;}
+ }
+ {if (true) throw (Error)jjte002;}
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, 2);
+ }
+ }
+ break;
+ case MODULUS:
+ jj_consume_token(MODULUS);
+ ASTModNode jjtn003 = new ASTModNode(this, JJTMODNODE);
+ boolean jjtc003 = true;
+ jjtree.openNodeScope(jjtn003);
+ try {
+ UnaryExpression();
+ } catch (Throwable jjte003) {
+ if (jjtc003) {
+ jjtree.clearNodeScope(jjtn003);
+ jjtc003 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte003 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte003;}
+ }
+ if (jjte003 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte003;}
+ }
+ {if (true) throw (Error)jjte003;}
+ } finally {
+ if (jjtc003) {
+ jjtree.closeNodeScope(jjtn003, 2);
+ }
+ }
+ break;
+ default:
+ jj_la1[97] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+ final public void UnaryExpression() throws ParseException {
+ label_44:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[98] = jj_gen;
+ break label_44;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[99] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case LOGICAL_NOT:
+ jj_consume_token(LOGICAL_NOT);
+ ASTNotNode jjtn001 = new ASTNotNode(this, JJTNOTNODE);
+ boolean jjtc001 = true;
+ jjtree.openNodeScope(jjtn001);
+ try {
+ UnaryExpression();
+ } catch (Throwable jjte001) {
+ if (jjtc001) {
+ jjtree.clearNodeScope(jjtn001);
+ jjtc001 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte001 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte001;}
+ }
+ if (jjte001 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte001;}
+ }
+ {if (true) throw (Error)jjte001;}
+ } finally {
+ if (jjtc001) {
+ jjtree.closeNodeScope(jjtn001, 1);
+ }
+ }
+ break;
+ case MINUS:
+ jj_consume_token(MINUS);
+ ASTNegateNode jjtn002 = new ASTNegateNode(this, JJTNEGATENODE);
+ boolean jjtc002 = true;
+ jjtree.openNodeScope(jjtn002);
+ try {
+ PrimaryExpression();
+ } catch (Throwable jjte002) {
+ if (jjtc002) {
+ jjtree.clearNodeScope(jjtn002);
+ jjtc002 = false;
+ } else {
+ jjtree.popNode();
+ }
+ if (jjte002 instanceof RuntimeException) {
+ {if (true) throw (RuntimeException)jjte002;}
+ }
+ if (jjte002 instanceof ParseException) {
+ {if (true) throw (ParseException)jjte002;}
+ }
+ {if (true) throw (Error)jjte002;}
+ } finally {
+ if (jjtc002) {
+ jjtree.closeNodeScope(jjtn002, 1);
+ }
+ }
+ break;
+ case LBRACKET:
+ case LEFT_CURLEY:
+ case LPAREN:
+ case WHITESPACE:
+ case NEWLINE:
+ case STRING_LITERAL:
+ case TRUE:
+ case FALSE:
+ case INTEGER_LITERAL:
+ case FLOATING_POINT_LITERAL:
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ PrimaryExpression();
+ break;
+ default:
+ jj_la1[100] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+
+ final public void PrimaryExpression() throws ParseException {
+ label_45:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[101] = jj_gen;
+ break label_45;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[102] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case STRING_LITERAL:
+ StringLiteral();
+ break;
+ case IDENTIFIER:
+ case OLD_IDENTIFIER:
+ case LCURLY:
+ Reference();
+ break;
+ case INTEGER_LITERAL:
+ IntegerLiteral();
+ break;
+ default:
+ jj_la1[103] = jj_gen;
+ if (jj_2_24(2147483647)) {
+ IntegerRange();
+ } else {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case FLOATING_POINT_LITERAL:
+ FloatingPointLiteral();
+ break;
+ case LEFT_CURLEY:
+ Map();
+ break;
+ case LBRACKET:
+ ObjectArray();
+ break;
+ case TRUE:
+ True();
+ break;
+ case FALSE:
+ False();
+ break;
+ case LPAREN:
+ jj_consume_token(LPAREN);
+ Expression();
+ jj_consume_token(RPAREN);
+ break;
+ default:
+ jj_la1[104] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+ label_46:
+ while (true) {
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ case NEWLINE:
+ ;
+ break;
+ default:
+ jj_la1[105] = jj_gen;
+ break label_46;
+ }
+ switch ((jj_ntk==-1)?jj_ntk():jj_ntk) {
+ case WHITESPACE:
+ jj_consume_token(WHITESPACE);
+ break;
+ case NEWLINE:
+ jj_consume_token(NEWLINE);
+ break;
+ default:
+ jj_la1[106] = jj_gen;
+ jj_consume_token(-1);
+ throw new ParseException();
+ }
+ }
+ }
+
+ private boolean jj_2_1(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_1(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(0, xla); }
+ }
+
+ private boolean jj_2_2(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_2(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(1, xla); }
+ }
+
+ private boolean jj_2_3(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_3(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(2, xla); }
+ }
+
+ private boolean jj_2_4(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_4(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(3, xla); }
+ }
+
+ private boolean jj_2_5(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_5(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(4, xla); }
+ }
+
+ private boolean jj_2_6(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_6(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(5, xla); }
+ }
+
+ private boolean jj_2_7(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_7(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(6, xla); }
+ }
+
+ private boolean jj_2_8(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_8(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(7, xla); }
+ }
+
+ private boolean jj_2_9(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_9(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(8, xla); }
+ }
+
+ private boolean jj_2_10(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_10(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(9, xla); }
+ }
+
+ private boolean jj_2_11(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_11(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(10, xla); }
+ }
+
+ private boolean jj_2_12(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_12(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(11, xla); }
+ }
+
+ private boolean jj_2_13(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_13(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(12, xla); }
+ }
+
+ private boolean jj_2_14(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_14(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(13, xla); }
+ }
+
+ private boolean jj_2_15(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_15(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(14, xla); }
+ }
+
+ private boolean jj_2_16(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_16(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(15, xla); }
+ }
+
+ private boolean jj_2_17(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_17(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(16, xla); }
+ }
+
+ private boolean jj_2_18(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_18(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(17, xla); }
+ }
+
+ private boolean jj_2_19(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_19(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(18, xla); }
+ }
+
+ private boolean jj_2_20(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_20(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(19, xla); }
+ }
+
+ private boolean jj_2_21(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_21(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(20, xla); }
+ }
+
+ private boolean jj_2_22(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_22(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(21, xla); }
+ }
+
+ private boolean jj_2_23(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_23(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(22, xla); }
+ }
+
+ private boolean jj_2_24(int xla) {
+ jj_la = xla; jj_lastpos = jj_scanpos = token;
+ try { return !jj_3_24(); }
+ catch(LookaheadSuccess ls) { return true; }
+ finally { jj_save(23, xla); }
+ }
+
+ private boolean jj_3_20() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_19() {
+ if (jj_scan_token(WHITESPACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_55() {
+ return false;
+ }
+
+ private boolean jj_3R_54() {
+ if (jj_3R_76()) return true;
+ return false;
+ }
+
+ private boolean jj_3_6() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_54()) jj_scanpos = xsp;
+ jj_lookingAhead = true;
+ jj_semLA = !isRightParenthesis();
+ jj_lookingAhead = false;
+ if (!jj_semLA || jj_3R_55()) return true;
+ if (jj_3R_56()) return true;
+ return false;
+ }
+
+ private boolean jj_3_18() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_126() {
+ if (jj_3R_110()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_62() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_125() {
+ if (jj_3R_110()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_65() {
+ if (jj_3R_98()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_69() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_63() {
+ if (jj_3R_98()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_111() {
+ if (jj_3R_110()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_53() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_61() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_100() {
+ if (jj_3R_110()) return true;
+ return false;
+ }
+
+ private boolean jj_3_17() {
+ if (jj_3R_64()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_68() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_60() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3_15() {
+ if (jj_3R_64()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_137() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_109()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_52() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_108() {
+ if (jj_scan_token(FALSE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_67() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_107() {
+ if (jj_scan_token(TRUE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_112() {
+ if (jj_scan_token(PIPE)) return true;
+ if (jj_3R_109()) return true;
+ return false;
+ }
+
+ private boolean jj_3_16() {
+ if (jj_scan_token(DOT)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_17()) {
+ jj_scanpos = xsp;
+ if (jj_3R_65()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_126()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_127() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_58()) return true;
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_58()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_51() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3_14() {
+ if (jj_scan_token(DOT)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3_15()) {
+ jj_scanpos = xsp;
+ if (jj_3R_63()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_125()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_99() {
+ if (jj_3R_109()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_137()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_71() {
+ if (jj_scan_token(LCURLY)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(67)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(70)) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_111()) { jj_scanpos = xsp; break; }
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_16()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_112()) jj_scanpos = xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(73)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(13)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_70() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(67)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(70)) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_100()) { jj_scanpos = xsp; break; }
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3_14()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_59() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_47() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_70()) {
+ jj_scanpos = xsp;
+ if (jj_3R_71()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_110() {
+ if (jj_scan_token(INDEX_LBRACKET)) return true;
+ if (jj_3R_131()) return true;
+ if (jj_scan_token(INDEX_RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_66() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3_13() {
+ if (jj_scan_token(LBRACKET)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_59()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_60()) {
+ jj_scanpos = xsp;
+ if (jj_3R_61()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_62()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(DOUBLEDOT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_64() {
+ if (jj_3R_98()) return true;
+ if (jj_scan_token(LPAREN)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_99()) jj_scanpos = xsp;
+ if (jj_scan_token(REFMOD2_RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_76() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_50() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_96() {
+ if (jj_3R_104()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_95() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_94() {
+ if (jj_3R_108()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_93() {
+ if (jj_3R_107()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_97() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_117() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_86() {
+ if (jj_3R_108()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_92() {
+ if (jj_3R_106()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_91() {
+ if (jj_3R_105()) return true;
+ return false;
+ }
+
+ private boolean jj_3_24() {
+ if (jj_scan_token(LBRACKET)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_66()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_67()) {
+ jj_scanpos = xsp;
+ if (jj_3R_68()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_69()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(DOUBLEDOT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_90() {
+ if (jj_3R_103()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_89() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_85() {
+ if (jj_3R_107()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_173() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_88() {
+ if (jj_3R_102()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_172() {
+ if (jj_scan_token(LPAREN)) return true;
+ if (jj_3R_109()) return true;
+ if (jj_scan_token(RPAREN)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_171() {
+ if (jj_3R_108()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_84() {
+ if (jj_3R_106()) return true;
+ return false;
+ }
+
+ private boolean jj_3_5() {
+ if (jj_scan_token(LBRACKET)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_50()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_51()) {
+ jj_scanpos = xsp;
+ if (jj_3R_52()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_53()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(DOUBLEDOT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_170() {
+ if (jj_3R_107()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_169() {
+ if (jj_3R_106()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_168() {
+ if (jj_3R_105()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_87() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_167() {
+ if (jj_3R_104()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_83() {
+ if (jj_3R_105()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_58() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_87()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_88()) {
+ jj_scanpos = xsp;
+ if (jj_3R_89()) {
+ jj_scanpos = xsp;
+ if (jj_3R_90()) {
+ jj_scanpos = xsp;
+ if (jj_3R_91()) {
+ jj_scanpos = xsp;
+ if (jj_3R_92()) {
+ jj_scanpos = xsp;
+ if (jj_3R_93()) {
+ jj_scanpos = xsp;
+ if (jj_3R_94()) {
+ jj_scanpos = xsp;
+ if (jj_3R_95()) {
+ jj_scanpos = xsp;
+ if (jj_3R_96()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_97()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_166() {
+ if (jj_3R_103()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_165() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_164() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_82() {
+ if (jj_3R_104()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_163() {
+ if (jj_3R_102()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_162() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_158() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_162()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_163()) {
+ jj_scanpos = xsp;
+ if (jj_3R_164()) {
+ jj_scanpos = xsp;
+ if (jj_3R_165()) {
+ jj_scanpos = xsp;
+ if (jj_3R_166()) {
+ jj_scanpos = xsp;
+ if (jj_3R_167()) {
+ jj_scanpos = xsp;
+ if (jj_3R_168()) {
+ jj_scanpos = xsp;
+ if (jj_3R_169()) {
+ jj_scanpos = xsp;
+ if (jj_3R_170()) {
+ jj_scanpos = xsp;
+ if (jj_3R_171()) {
+ jj_scanpos = xsp;
+ if (jj_3R_172()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_173()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_154() {
+ if (jj_3R_158()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_81() {
+ if (jj_3R_103()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_153() {
+ if (jj_scan_token(MINUS)) return true;
+ if (jj_3R_158()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_152() {
+ if (jj_scan_token(LOGICAL_NOT)) return true;
+ if (jj_3R_145()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_119() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_134() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_115() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_129() {
+ if (jj_scan_token(COMMA)) return true;
+ if (jj_3R_58()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_133() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_80() {
+ if (jj_3R_75()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_131() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_133()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_3R_109()) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_134()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_161() {
+ if (jj_scan_token(MODULUS)) return true;
+ if (jj_3R_145()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_113() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_151() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_160() {
+ if (jj_scan_token(DIVIDE)) return true;
+ if (jj_3R_145()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_159() {
+ if (jj_scan_token(MULTIPLY)) return true;
+ if (jj_3R_145()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_155() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_159()) {
+ jj_scanpos = xsp;
+ if (jj_3R_160()) {
+ jj_scanpos = xsp;
+ if (jj_3R_161()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_145() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_151()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_152()) {
+ jj_scanpos = xsp;
+ if (jj_3R_153()) {
+ jj_scanpos = xsp;
+ if (jj_3R_154()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_79() {
+ if (jj_3R_102()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_120() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_78() {
+ if (jj_3R_101()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_118() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_114() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_116() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_123() {
+ if (jj_3R_58()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_129()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_77() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_56() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_77()) {
+ jj_scanpos = xsp;
+ if (jj_3R_78()) {
+ jj_scanpos = xsp;
+ if (jj_3R_79()) {
+ jj_scanpos = xsp;
+ if (jj_3R_80()) {
+ jj_scanpos = xsp;
+ if (jj_3R_81()) {
+ jj_scanpos = xsp;
+ if (jj_3R_82()) {
+ jj_scanpos = xsp;
+ if (jj_3R_83()) {
+ jj_scanpos = xsp;
+ if (jj_3R_84()) {
+ jj_scanpos = xsp;
+ if (jj_3R_85()) {
+ jj_scanpos = xsp;
+ if (jj_3R_86()) return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_103() {
+ if (jj_scan_token(LBRACKET)) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_113()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_114()) {
+ jj_scanpos = xsp;
+ if (jj_3R_115()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_116()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(DOUBLEDOT)) return true;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_117()) { jj_scanpos = xsp; break; }
+ }
+ xsp = jj_scanpos;
+ if (jj_3R_118()) {
+ jj_scanpos = xsp;
+ if (jj_3R_119()) return true;
+ }
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_120()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_141() {
+ if (jj_3R_145()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_155()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_157() {
+ if (jj_scan_token(MINUS)) return true;
+ if (jj_3R_141()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_156() {
+ if (jj_scan_token(PLUS)) return true;
+ if (jj_3R_141()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_146() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_156()) {
+ jj_scanpos = xsp;
+ if (jj_3R_157()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_101() {
+ if (jj_scan_token(WORD)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_57() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_139() {
+ if (jj_3R_141()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_146()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3_12() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_57()) { jj_scanpos = xsp; break; }
+ }
+ if (jj_3R_58()) return true;
+ if (jj_scan_token(COLON)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_106() {
+ if (jj_scan_token(LBRACKET)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_123()) jj_scanpos = xsp;
+ if (jj_scan_token(RBRACKET)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_150() {
+ if (jj_scan_token(LOGICAL_GE)) return true;
+ if (jj_3R_139()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_149() {
+ if (jj_scan_token(LOGICAL_LE)) return true;
+ if (jj_3R_139()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_98() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(67)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(70)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_148() {
+ if (jj_scan_token(LOGICAL_GT)) return true;
+ if (jj_3R_139()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_142() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_147()) {
+ jj_scanpos = xsp;
+ if (jj_3R_148()) {
+ jj_scanpos = xsp;
+ if (jj_3R_149()) {
+ jj_scanpos = xsp;
+ if (jj_3R_150()) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_147() {
+ if (jj_scan_token(LOGICAL_LT)) return true;
+ if (jj_3R_139()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_128() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(33)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_122() {
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_128()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_121() {
+ if (jj_3R_58()) return true;
+ if (jj_scan_token(COLON)) return true;
+ if (jj_3R_58()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_127()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_136() {
+ if (jj_3R_139()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_142()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_144() {
+ if (jj_scan_token(LOGICAL_NOT_EQUALS)) return true;
+ if (jj_3R_136()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_105() {
+ if (jj_scan_token(LEFT_CURLEY)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_121()) {
+ jj_scanpos = xsp;
+ if (jj_3R_122()) return true;
+ }
+ xsp = jj_scanpos;
+ if (jj_scan_token(13)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(73)) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_140() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_143()) {
+ jj_scanpos = xsp;
+ if (jj_3R_144()) return true;
+ }
+ return false;
+ }
+
+ private boolean jj_3R_143() {
+ if (jj_scan_token(LOGICAL_EQUALS)) return true;
+ if (jj_3R_136()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_102() {
+ if (jj_scan_token(STRING_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_132() {
+ if (jj_3R_136()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_140()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_75() {
+ if (jj_scan_token(INTEGER_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_138() {
+ if (jj_scan_token(LOGICAL_AND)) return true;
+ if (jj_3R_132()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_130() {
+ if (jj_3R_132()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_138()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_104() {
+ if (jj_scan_token(FLOATING_POINT_LITERAL)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_135() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(44)) {
+ jj_scanpos = xsp;
+ if (jj_scan_token(4)) return true;
+ }
+ if (jj_3R_130()) return true;
+ return false;
+ }
+
+ private boolean jj_3R_124() {
+ if (jj_3R_130()) return true;
+ Token xsp;
+ while (true) {
+ xsp = jj_scanpos;
+ if (jj_3R_135()) { jj_scanpos = xsp; break; }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_72() {
+ if (jj_scan_token(SINGLE_LINE_COMMENT_START)) return true;
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(26)) jj_scanpos = xsp;
+ return false;
+ }
+
+ private boolean jj_3R_48() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_3R_72()) {
+ jj_scanpos = xsp;
+ if (jj_3R_73()) {
+ jj_scanpos = xsp;
+ if (jj_3R_74()) return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean jj_3R_74() {
+ if (jj_scan_token(FORMAL_COMMENT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_73() {
+ if (jj_scan_token(MULTI_LINE_COMMENT)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_109() {
+ if (jj_3R_124()) return true;
+ return false;
+ }
+
+ private boolean jj_3_11() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_23() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_10() {
+ if (jj_scan_token(WHITESPACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_4() {
+ if (jj_scan_token(DOUBLE_ESCAPE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_9() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_22() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_8() {
+ if (jj_scan_token(WHITESPACE)) return true;
+ return false;
+ }
+
+ private boolean jj_3R_49() {
+ if (jj_scan_token(ZERO_WIDTH_WHITESPACE)) return true;
+ if (jj_scan_token(0)) return true;
+ return false;
+ }
+
+ private boolean jj_3_3() {
+ if (jj_3R_49()) return true;
+ return false;
+ }
+
+ private boolean jj_3_2() {
+ if (jj_3R_48()) return true;
+ return false;
+ }
+
+ private boolean jj_3_1() {
+ if (jj_3R_47()) return true;
+ return false;
+ }
+
+ private boolean jj_3_21() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ private boolean jj_3_7() {
+ Token xsp;
+ xsp = jj_scanpos;
+ if (jj_scan_token(32)) jj_scanpos = xsp;
+ if (jj_scan_token(NEWLINE)) return true;
+ return false;
+ }
+
+ /** Generated Token Manager. */
+ public StandardParserTokenManager token_source;
+ /** Current token. */
+ public Token token;
+ /** Next token. */
+ public Token jj_nt;
+ private int jj_ntk;
+ private Token jj_scanpos, jj_lastpos;
+ private int jj_la;
+ /** Whether we are looking ahead. */
+ private boolean jj_lookingAhead = false;
+ private boolean jj_semLA;
+ private int jj_gen;
+ final private int[] jj_la1 = new int[107];
+ static private int[] jj_la1_0;
+ static private int[] jj_la1_1;
+ static private int[] jj_la1_2;
+ static {
+ jj_la1_init_0();
+ jj_la1_init_1();
+ jj_la1_init_2();
+ }
+ private static void jj_la1_init_0() {
+ jj_la1_0 = new int[] {0x0,0x20000000,0x20000,0xc022,0x14,0x4000000,0x1a000000,0x0,0x0,0x1080,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x0,0x0,0x0,0x4000000,0x2000000,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x0,0x2000,0x200,0x1080,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1080,0x0,0x0,0x200,0x5080,0x0,0x8,0x0,0x8,0x0,0x8,0x0,0x8,0x20,0x2000,0x0,0xc022,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x5080,0x0,0x0,0x0,0x5080,0x0,0x0,};
+ }
+ private static void jj_la1_init_1() {
+ jj_la1_1 = new int[] {0x0,0x0,0x0,0xc00000f,0x0,0x0,0x0,0x0,0x84000008,0x8000030,0x1,0x80000000,0x3,0x3,0x3,0x3,0x3,0x3,0x0,0x3,0x3,0x3,0x3,0x0,0x0,0x3,0x3,0x1,0x1,0x1,0x0,0x3,0x3,0x0,0x0,0xc00003b,0x3,0x3,0x4000000,0x3,0x3,0x3,0x3,0x4000000,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4000008,0x8000030,0x3,0x3,0x0,0xc08007b,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000008,0x1,0x3,0x3,0x1,0x1,0x1,0x1,0x1,0x3,0x3,0x1,0x1,0x3,0x3,0x3,0x3,0x1,0x1000,0x1000,0x800,0x60000,0x60000,0x1e000,0x1e000,0xc0,0xc0,0x700,0x700,0x3,0x3,0xc08007b,0x3,0x3,0x4000008,0x8000030,0x3,0x3,};
+ }
+ private static void jj_la1_init_2() {
+ jj_la1_2 = new int[] {0x4000,0x0,0x1000,0x1e380,0x0,0x0,0x0,0x48,0x148,0x0,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x148,0x0,0x0,0x148,0x0,0x0,0x0,0x0,0x148,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x148,0x0,0x0,0x0,0x148,0x48,0x0,0x48,0x0,0x48,0x0,0x48,0x0,0x0,0x200,0x148,0x16380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x148,0x0,0x0,0x148,0x0,0x0,0x0,};
+ }
+ final private JJCalls[] jj_2_rtns = new JJCalls[24];
+ private boolean jj_rescan = false;
+ private int jj_gc = 0;
+
+ /** Constructor with user supplied CharStream. */
+ public StandardParser(CharStream stream) {
+ token_source = new StandardParserTokenManager(this, stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 107; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Reinitialise. */
+ public void ReInit(CharStream stream) {
+ token_source.ReInit(stream);
+ token = new Token();
+ jj_ntk = -1;
+ jj_lookingAhead = false;
+ jjtree.reset();
+ jj_gen = 0;
+ for (int i = 0; i < 107; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Constructor with generated Token Manager. */
+ public StandardParser(StandardParserTokenManager tm) {
+ token_source = tm;
+ token = new Token();
+ jj_ntk = -1;
+ jj_gen = 0;
+ for (int i = 0; i < 107; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ /** Reinitialise. */
+ public void ReInit(StandardParserTokenManager tm) {
+ token_source = tm;
+ token = new Token();
+ jj_ntk = -1;
+ jjtree.reset();
+ jj_gen = 0;
+ for (int i = 0; i < 107; i++) jj_la1[i] = -1;
+ for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls();
+ }
+
+ private Token jj_consume_token(int kind) throws ParseException {
+ Token oldToken;
+ if ((oldToken = token).next != null) token = token.next;
+ else token = token.next = token_source.getNextToken();
+ jj_ntk = -1;
+ if (token.kind == kind) {
+ jj_gen++;
+ if (++jj_gc > 100) {
+ jj_gc = 0;
+ for (int i = 0; i < jj_2_rtns.length; i++) {
+ JJCalls c = jj_2_rtns[i];
+ while (c != null) {
+ if (c.gen < jj_gen) c.first = null;
+ c = c.next;
+ }
+ }
+ }
+ return token;
+ }
+ token = oldToken;
+ jj_kind = kind;
+ throw generateParseException();
+ }
+
+ static private final class LookaheadSuccess extends java.lang.Error { }
+ final private LookaheadSuccess jj_ls = new LookaheadSuccess();
+ private boolean jj_scan_token(int kind) {
+ if (jj_scanpos == jj_lastpos) {
+ jj_la--;
+ if (jj_scanpos.next == null) {
+ jj_lastpos = jj_scanpos = jj_scanpos.next = token_source.getNextToken();
+ } else {
+ jj_lastpos = jj_scanpos = jj_scanpos.next;
+ }
+ } else {
+ jj_scanpos = jj_scanpos.next;
+ }
+ if (jj_rescan) {
+ int i = 0; Token tok = token;
+ while (tok != null && tok != jj_scanpos) { i++; tok = tok.next; }
+ if (tok != null) jj_add_error_token(kind, i);
+ }
+ if (jj_scanpos.kind != kind) return true;
+ if (jj_la == 0 && jj_scanpos == jj_lastpos) throw jj_ls;
+ return false;
+ }
+
+
+/** Get the next Token. */
+ final public Token getNextToken() {
+ if (token.next != null) token = token.next;
+ else token = token.next = token_source.getNextToken();
+ jj_ntk = -1;
+ jj_gen++;
+ return token;
+ }
+
+/** Get the specific Token. */
+ final public Token getToken(int index) {
+ Token t = jj_lookingAhead ? jj_scanpos : token;
+ for (int i = 0; i < index; i++) {
+ if (t.next != null) t = t.next;
+ else t = t.next = token_source.getNextToken();
+ }
+ return t;
+ }
+
+ private int jj_ntk() {
+ if ((jj_nt=token.next) == null)
+ return (jj_ntk = (token.next=token_source.getNextToken()).kind);
+ else
+ return (jj_ntk = jj_nt.kind);
+ }
+
+ private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();
+ private int[] jj_expentry;
+ private int jj_kind = -1;
+ private int[] jj_lasttokens = new int[100];
+ private int jj_endpos;
+
+ private void jj_add_error_token(int kind, int pos) {
+ if (pos >= 100) return;
+ if (pos == jj_endpos + 1) {
+ jj_lasttokens[jj_endpos++] = kind;
+ } else if (jj_endpos != 0) {
+ jj_expentry = new int[jj_endpos];
+ for (int i = 0; i < jj_endpos; i++) {
+ jj_expentry[i] = jj_lasttokens[i];
+ }
+ jj_entries_loop: for (java.util.Iterator<?> it = jj_expentries.iterator(); it.hasNext();) {
+ int[] oldentry = (int[])(it.next());
+ if (oldentry.length == jj_expentry.length) {
+ for (int i = 0; i < jj_expentry.length; i++) {
+ if (oldentry[i] != jj_expentry[i]) {
+ continue jj_entries_loop;
+ }
+ }
+ jj_expentries.add(jj_expentry);
+ break jj_entries_loop;
+ }
+ }
+ if (pos != 0) jj_lasttokens[(jj_endpos = pos) - 1] = kind;
+ }
+ }
+
+ /** Generate ParseException. */
+ public ParseException generateParseException() {
+ jj_expentries.clear();
+ boolean[] la1tokens = new boolean[81];
+ if (jj_kind >= 0) {
+ la1tokens[jj_kind] = true;
+ jj_kind = -1;
+ }
+ for (int i = 0; i < 107; i++) {
+ if (jj_la1[i] == jj_gen) {
+ for (int j = 0; j < 32; j++) {
+ if ((jj_la1_0[i] & (1<<j)) != 0) {
+ la1tokens[j] = true;
+ }
+ if ((jj_la1_1[i] & (1<<j)) != 0) {
+ la1tokens[32+j] = true;
+ }
+ if ((jj_la1_2[i] & (1<<j)) != 0) {
+ la1tokens[64+j] = true;
+ }
+ }
+ }
+ }
+ for (int i = 0; i < 81; i++) {
+ if (la1tokens[i]) {
+ jj_expentry = new int[1];
+ jj_expentry[0] = i;
+ jj_expentries.add(jj_expentry);
+ }
+ }
+ jj_endpos = 0;
+ jj_rescan_token();
+ jj_add_error_token(0, 0);
+ int[][] exptokseq = new int[jj_expentries.size()][];
+ for (int i = 0; i < jj_expentries.size(); i++) {
+ exptokseq[i] = jj_expentries.get(i);
+ }
+ return new ParseException(token, exptokseq, tokenImage);
+ }
+
+ /** Enable tracing. */
+ final public void enable_tracing() {
+ }
+
+ /** Disable tracing. */
+ final public void disable_tracing() {
+ }
+
+ private void jj_rescan_token() {
+ jj_rescan = true;
+ for (int i = 0; i < 24; i++) {
+ try {
+ JJCalls p = jj_2_rtns[i];
+ do {
+ if (p.gen > jj_gen) {
+ jj_la = p.arg; jj_lastpos = jj_scanpos = p.first;
+ switch (i) {
+ case 0: jj_3_1(); break;
+ case 1: jj_3_2(); break;
+ case 2: jj_3_3(); break;
+ case 3: jj_3_4(); break;
+ case 4: jj_3_5(); break;
+ case 5: jj_3_6(); break;
+ case 6: jj_3_7(); break;
+ case 7: jj_3_8(); break;
+ case 8: jj_3_9(); break;
+ case 9: jj_3_10(); break;
+ case 10: jj_3_11(); break;
+ case 11: jj_3_12(); break;
+ case 12: jj_3_13(); break;
+ case 13: jj_3_14(); break;
+ case 14: jj_3_15(); break;
+ case 15: jj_3_16(); break;
+ case 16: jj_3_17(); break;
+ case 17: jj_3_18(); break;
+ case 18: jj_3_19(); break;
+ case 19: jj_3_20(); break;
+ case 20: jj_3_21(); break;
+ case 21: jj_3_22(); break;
+ case 22: jj_3_23(); break;
+ case 23: jj_3_24(); break;
+ }
+ }
+ p = p.next;
+ } while (p != null);
+ } catch(LookaheadSuccess ls) { }
+ }
+ jj_rescan = false;
+ }
+
+ private void jj_save(int index, int xla) {
+ JJCalls p = jj_2_rtns[index];
+ while (p.gen > jj_gen) {
+ if (p.next == null) { p = p.next = new JJCalls(); break; }
+ p = p.next;
+ }
+ p.gen = jj_gen + xla - jj_la; p.first = token; p.arg = xla;
+ }
+
+ static final class JJCalls {
+ int gen;
+ Token first;
+ int arg;
+ JJCalls next;
+ }
+
+}
diff --git a/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserConstants.java b/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserConstants.java
new file mode 100644
index 00000000..32e97712
--- /dev/null
+++ b/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserConstants.java
@@ -0,0 +1,286 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. StandardParserConstants.java */
+package org.apache.velocity.runtime.parser;
+
+
+/**
+ * Token literal values and constants.
+ * Generated by org.javacc.parser.OtherFilesGen#start()
+ */
+public interface StandardParserConstants {
+
+ /** End of File. */
+ int EOF = 0;
+ /** RegularExpression Id. */
+ int LONE_SYMBOL = 1;
+ /** RegularExpression Id. */
+ int ZERO_WIDTH_WHITESPACE = 2;
+ /** RegularExpression Id. */
+ int INDEX_LBRACKET = 3;
+ /** RegularExpression Id. */
+ int LOGICAL_OR_2 = 4;
+ /** RegularExpression Id. */
+ int PIPE = 5;
+ /** RegularExpression Id. */
+ int INDEX_RBRACKET = 6;
+ /** RegularExpression Id. */
+ int LBRACKET = 7;
+ /** RegularExpression Id. */
+ int RBRACKET = 8;
+ /** RegularExpression Id. */
+ int COMMA = 9;
+ /** RegularExpression Id. */
+ int DOUBLEDOT = 10;
+ /** RegularExpression Id. */
+ int COLON = 11;
+ /** RegularExpression Id. */
+ int LEFT_CURLEY = 12;
+ /** RegularExpression Id. */
+ int RIGHT_CURLEY = 13;
+ /** RegularExpression Id. */
+ int LPAREN = 14;
+ /** RegularExpression Id. */
+ int RPAREN = 15;
+ /** RegularExpression Id. */
+ int REFMOD2_RPAREN = 16;
+ /** RegularExpression Id. */
+ int ESCAPE_DIRECTIVE = 17;
+ /** RegularExpression Id. */
+ int SET_DIRECTIVE = 18;
+ /** RegularExpression Id. */
+ int DOLLAR = 19;
+ /** RegularExpression Id. */
+ int DOLLARBANG = 20;
+ /** RegularExpression Id. */
+ int HASH = 24;
+ /** RegularExpression Id. */
+ int SINGLE_LINE_COMMENT_START = 25;
+ /** RegularExpression Id. */
+ int SINGLE_LINE_COMMENT = 26;
+ /** RegularExpression Id. */
+ int FORMAL_COMMENT = 27;
+ /** RegularExpression Id. */
+ int MULTI_LINE_COMMENT = 28;
+ /** RegularExpression Id. */
+ int TEXTBLOCK = 29;
+ /** RegularExpression Id. */
+ int WHITESPACE = 32;
+ /** RegularExpression Id. */
+ int NEWLINE = 33;
+ /** RegularExpression Id. */
+ int SUFFIX = 34;
+ /** RegularExpression Id. */
+ int STRING_LITERAL = 35;
+ /** RegularExpression Id. */
+ int TRUE = 36;
+ /** RegularExpression Id. */
+ int FALSE = 37;
+ /** RegularExpression Id. */
+ int MINUS = 38;
+ /** RegularExpression Id. */
+ int PLUS = 39;
+ /** RegularExpression Id. */
+ int MULTIPLY = 40;
+ /** RegularExpression Id. */
+ int DIVIDE = 41;
+ /** RegularExpression Id. */
+ int MODULUS = 42;
+ /** RegularExpression Id. */
+ int LOGICAL_AND = 43;
+ /** RegularExpression Id. */
+ int LOGICAL_OR = 44;
+ /** RegularExpression Id. */
+ int LOGICAL_LT = 45;
+ /** RegularExpression Id. */
+ int LOGICAL_LE = 46;
+ /** RegularExpression Id. */
+ int LOGICAL_GT = 47;
+ /** RegularExpression Id. */
+ int LOGICAL_GE = 48;
+ /** RegularExpression Id. */
+ int LOGICAL_EQUALS = 49;
+ /** RegularExpression Id. */
+ int LOGICAL_NOT_EQUALS = 50;
+ /** RegularExpression Id. */
+ int LOGICAL_NOT = 51;
+ /** RegularExpression Id. */
+ int EQUALS = 52;
+ /** RegularExpression Id. */
+ int END = 53;
+ /** RegularExpression Id. */
+ int IF_DIRECTIVE = 54;
+ /** RegularExpression Id. */
+ int ELSEIF = 55;
+ /** RegularExpression Id. */
+ int ELSE = 56;
+ /** RegularExpression Id. */
+ int DIGIT = 57;
+ /** RegularExpression Id. */
+ int INTEGER_LITERAL = 58;
+ /** RegularExpression Id. */
+ int FLOATING_POINT_LITERAL = 59;
+ /** RegularExpression Id. */
+ int EXPONENT = 60;
+ /** RegularExpression Id. */
+ int LETTER = 61;
+ /** RegularExpression Id. */
+ int DIRECTIVE_CHAR = 62;
+ /** RegularExpression Id. */
+ int WORD = 63;
+ /** RegularExpression Id. */
+ int BRACKETED_WORD = 64;
+ /** RegularExpression Id. */
+ int ALPHA_CHAR = 65;
+ /** RegularExpression Id. */
+ int IDENTIFIER_CHAR = 66;
+ /** RegularExpression Id. */
+ int IDENTIFIER = 67;
+ /** RegularExpression Id. */
+ int OLD_ALPHA_CHAR = 68;
+ /** RegularExpression Id. */
+ int OLD_IDENTIFIER_CHAR = 69;
+ /** RegularExpression Id. */
+ int OLD_IDENTIFIER = 70;
+ /** RegularExpression Id. */
+ int DOT = 71;
+ /** RegularExpression Id. */
+ int LCURLY = 72;
+ /** RegularExpression Id. */
+ int RCURLY = 73;
+ /** RegularExpression Id. */
+ int REFERENCE_TERMINATOR = 74;
+ /** RegularExpression Id. */
+ int DIRECTIVE_TERMINATOR = 75;
+ /** RegularExpression Id. */
+ int DOUBLE_ESCAPE = 76;
+ /** RegularExpression Id. */
+ int ESCAPE = 77;
+ /** RegularExpression Id. */
+ int TEXT = 78;
+ /** RegularExpression Id. */
+ int INLINE_TEXT = 79;
+ /** RegularExpression Id. */
+ int EMPTY_INDEX = 80;
+
+ /** Lexical state. */
+ int PRE_DIRECTIVE = 0;
+ /** Lexical state. */
+ int PRE_REFERENCE = 1;
+ /** Lexical state. */
+ int PRE_OLD_REFERENCE = 2;
+ /** Lexical state. */
+ int REFERENCE = 3;
+ /** Lexical state. */
+ int REFMODIFIER = 4;
+ /** Lexical state. */
+ int OLD_REFMODIFIER = 5;
+ /** Lexical state. */
+ int REFMOD3 = 6;
+ /** Lexical state. */
+ int REFINDEX = 7;
+ /** Lexical state. */
+ int DIRECTIVE = 8;
+ /** Lexical state. */
+ int REFMOD2 = 9;
+ /** Lexical state. */
+ int DEFAULT = 10;
+ /** Lexical state. */
+ int REFMOD = 11;
+ /** Lexical state. */
+ int IN_TEXTBLOCK = 12;
+ /** Lexical state. */
+ int IN_MULTILINE_COMMENT = 13;
+ /** Lexical state. */
+ int IN_FORMAL_COMMENT = 14;
+ /** Lexical state. */
+ int IN_SINGLE_LINE_COMMENT = 15;
+ /** Lexical state. */
+ int ALT_VAL = 16;
+ /** Lexical state. */
+ int IN_MULTI_LINE_COMMENT = 17;
+
+ /** Literal token values. */
+ String[] tokenImage = {
+ "<EOF>",
+ "\"\\u001c\"",
+ "\"\\u001c\"",
+ "\"[\"",
+ "\"||\"",
+ "\"|\"",
+ "\"]\"",
+ "\"[\"",
+ "\"]\"",
+ "\",\"",
+ "\"..\"",
+ "\":\"",
+ "\"{\"",
+ "\"}\"",
+ "\"(\"",
+ "\")\"",
+ "\")\"",
+ "<ESCAPE_DIRECTIVE>",
+ "<SET_DIRECTIVE>",
+ "<DOLLAR>",
+ "<DOLLARBANG>",
+ "\"#[[\"",
+ "<token of kind 22>",
+ "\"#*\"",
+ "\"#\"",
+ "\"##\"",
+ "<SINGLE_LINE_COMMENT>",
+ "\"*#\"",
+ "\"*#\"",
+ "\"]]#\"",
+ "<token of kind 30>",
+ "<token of kind 31>",
+ "<WHITESPACE>",
+ "<NEWLINE>",
+ "<SUFFIX>",
+ "<STRING_LITERAL>",
+ "\"true\"",
+ "\"false\"",
+ "\"-\"",
+ "\"+\"",
+ "\"*\"",
+ "\"/\"",
+ "\"%\"",
+ "<LOGICAL_AND>",
+ "<LOGICAL_OR>",
+ "<LOGICAL_LT>",
+ "<LOGICAL_LE>",
+ "<LOGICAL_GT>",
+ "<LOGICAL_GE>",
+ "<LOGICAL_EQUALS>",
+ "<LOGICAL_NOT_EQUALS>",
+ "<LOGICAL_NOT>",
+ "\"=\"",
+ "<END>",
+ "<IF_DIRECTIVE>",
+ "<ELSEIF>",
+ "<ELSE>",
+ "<DIGIT>",
+ "<INTEGER_LITERAL>",
+ "<FLOATING_POINT_LITERAL>",
+ "<EXPONENT>",
+ "<LETTER>",
+ "<DIRECTIVE_CHAR>",
+ "<WORD>",
+ "<BRACKETED_WORD>",
+ "<ALPHA_CHAR>",
+ "<IDENTIFIER_CHAR>",
+ "<IDENTIFIER>",
+ "<OLD_ALPHA_CHAR>",
+ "<OLD_IDENTIFIER_CHAR>",
+ "<OLD_IDENTIFIER>",
+ "<DOT>",
+ "\"{\"",
+ "\"}\"",
+ "<REFERENCE_TERMINATOR>",
+ "<DIRECTIVE_TERMINATOR>",
+ "\"\\\\\\\\\"",
+ "\"\\\\\"",
+ "<TEXT>",
+ "<INLINE_TEXT>",
+ "<EMPTY_INDEX>",
+ };
+
+}
diff --git a/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserTokenManager.java b/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserTokenManager.java
new file mode 100644
index 00000000..82761fef
--- /dev/null
+++ b/generated-sources/javacc/org/apache/velocity/runtime/parser/StandardParserTokenManager.java
@@ -0,0 +1,8749 @@
+/* Generated By:JJTree&JavaCC: Do not edit this line. StandardParserTokenManager.java */
+package org.apache.velocity.runtime.parser;
+import org.apache.velocity.runtime.parser.node.*;
+import java.io.*;
+import java.util.*;
+import org.apache.velocity.Template;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.parser.*;
+import org.apache.velocity.runtime.parser.node.*;
+import org.apache.velocity.runtime.directive.*;
+import org.apache.velocity.runtime.directive.MacroParseException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import static org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+import org.slf4j.Logger;
+
+/** Token Manager. */
+public class StandardParserTokenManager implements StandardParserConstants
+{
+ private int fileDepth = 0;
+
+ private int lparen = 0;
+ private int rparen = 0;
+ private int curlyLevel = 0;
+ List stateStack = new ArrayList(50);
+
+ private boolean inComment;
+ private boolean inSet;
+
+ /**
+ * Our own trace method. Use sparsingly in production, since each
+ * and every call will introduce an execution branch and slow down parsing.
+ */
+ public static void trace(String message)
+ {
+ StandardParser.trace(message);
+ }
+
+ /**
+ * Switches to a new state (add some log to the default method)
+ */
+ public void switchTo(int lexState)
+ {
+ trace(" switch to " + lexStateNames[lexState]);
+ SwitchTo(lexState);
+ }
+
+ public int getCurrentLexicalState()
+ {
+ return curLexState;
+ }
+
+ /**
+ * pops a state off the stack, and restores paren counts
+ *
+ * @return boolean : success of operation
+ */
+ public boolean stateStackPop()
+ {
+ ParserState s;
+ try
+ {
+ s = (ParserState) stateStack.remove(stateStack.size() - 1); // stack.pop
+ }
+ catch(IndexOutOfBoundsException e)
+ {
+ // empty stack
+ lparen=0;
+ switchTo(DEFAULT);
+ return false;
+ }
+
+ trace(" stack pop (" + stateStack.size() + ")");
+ lparen = s.lparen;
+ rparen = s.rparen;
+ curlyLevel = s.curlyLevel;
+
+ switchTo(s.lexstate);
+
+ return true;
+ }
+
+ /**
+ * pushes the current state onto the 'state stack',
+ * and maintains the parens counts
+ * public because we need it in PD &amp; VM handling
+ *
+ * @return boolean : success. It can fail if the state machine
+ * gets messed up (do don't mess it up :)
+ */
+ public boolean stateStackPush()
+ {
+ trace(" (" + stateStack.size() + ") pushing cur state : " + lexStateNames[curLexState] );
+
+ ParserState s = new ParserState();
+ s.lparen = lparen;
+ s.rparen = rparen;
+ s.curlyLevel = curlyLevel;
+ s.lexstate = curLexState;
+
+ stateStack.add(s); // stack.push
+
+ lparen = 0;
+ curlyLevel = 0;
+
+ return true;
+ }
+
+ /**
+ * Clears all state variables, resets to
+ * start values, clears stateStack. Call
+ * before parsing.
+ */
+ public void clearStateVars()
+ {
+ stateStack.clear();
+
+ lparen = 0;
+ rparen = 0;
+ curlyLevel = 0;
+ inComment = false;
+ inSet = false;
+
+ return;
+ }
+
+ public void setInSet(boolean value)
+ {
+ inSet = value;
+ }
+
+ public boolean isInSet()
+ {
+ return inSet;
+ }
+
+ /**
+ * Holds the state of the parsing process.
+ */
+ private static class ParserState
+ {
+ int lparen;
+ int rparen;
+ int curlyLevel;
+ int lexstate;
+ }
+
+ /**
+ * handles the dropdown logic when encountering a RPAREN
+ */
+ private void RPARENHandler()
+ {
+ /*
+ * Ultimately, we want to drop down to the state below
+ * the one that has an open (if we hit bottom (DEFAULT),
+ * that's fine. It's just text schmoo.
+ */
+
+ boolean closed = false;
+
+ if (inComment)
+ closed = true;
+
+ while( !closed )
+ {
+ /*
+ * look at current state. If we haven't seen a lparen
+ * in this state then we drop a state, because this
+ * lparen clearly closes our state
+ */
+
+ if( lparen > 0)
+ {
+ /*
+ * if rparen + 1 == lparen, then this state is closed.
+ * Otherwise, increment and keep parsing
+ */
+
+ if( lparen == rparen + 1)
+ {
+ stateStackPop();
+ }
+ else
+ {
+ rparen++;
+ }
+
+ closed = true;
+ }
+ else
+ {
+ /*
+ * now, drop a state
+ */
+
+ if(!stateStackPop())
+ break;
+ }
+ }
+ }
+
+ /** Debug output. */
+ public java.io.PrintStream debugStream = System.out;
+ /** Set debug output. */
+ public void setDebugStream(java.io.PrintStream ds) { debugStream = ds; }
+
+ /** The parser. */
+ public StandardParser parser = null;
+private final int jjStopStringLiteralDfa_3(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 15;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 21;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_3(int pos, long active0, long active1)
+{
+ return jjMoveNfa_3(jjStopStringLiteralDfa_3(pos, active0, active1), pos + 1);
+}
+private int jjStopAtPos(int pos, int kind)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ return pos + 1;
+}
+private int jjMoveStringLiteralDfa0_3()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_3(0x2a00000L);
+ case 91:
+ return jjStopAtPos(0, 3);
+ case 102:
+ return jjMoveStringLiteralDfa1_3(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_3(0x1000000000L);
+ case 123:
+ return jjStopAtPos(0, 72);
+ case 124:
+ jjmatchedKind = 5;
+ return jjMoveStringLiteralDfa1_3(0x10L);
+ case 125:
+ return jjStopAtPos(0, 73);
+ default :
+ return jjMoveNfa_3(0, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_3(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_3(0, active0, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_3(1, 23, 21);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_3(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_3(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_3(active0, 0x1000000000L);
+ case 124:
+ if ((active0 & 0x10L) != 0L)
+ return jjStopAtPos(1, 4);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_3(0, active0, 0L);
+}
+private int jjMoveStringLiteralDfa2_3(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_3(0, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_3(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_3(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_3(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_3(1, active0, 0L);
+}
+private int jjMoveStringLiteralDfa3_3(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_3(1, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_3(2, active0, 0L);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStopAtPos(3, 36);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_3(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_3(2, active0, 0L);
+}
+private int jjMoveStringLiteralDfa4_3(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_3(2, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_3(3, active0, 0L);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStopAtPos(4, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_3(3, active0, 0L);
+}
+private int jjStartNfaWithStates_3(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_3(state, pos + 1);
+}
+static final long[] jjbitVec0 = {
+ 0xfffffffffffffffeL, 0xffffffffffffffffL, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+static final long[] jjbitVec2 = {
+ 0x0L, 0x0L, 0xffffffffffffffffL, 0xffffffffffffffffL
+};
+private int jjMoveNfa_3(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 24;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (curChar == 35)
+ jjAddStates(0, 2);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(7, 8);
+ }
+ else if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 15:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 4:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 6:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 8:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 9:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 10:
+ if (curChar == 35)
+ jjAddStates(0, 2);
+ break;
+ case 12:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(3, 4);
+ break;
+ case 13:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 21:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (curChar == 92)
+ jjCheckNAddStates(5, 8);
+ break;
+ case 15:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 19;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 14;
+ break;
+ case 1:
+ if ((0x7fffffe87fffffeL & l) != 0L && kind > 71)
+ kind = 71;
+ break;
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(3, 4);
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjAddStates(9, 10);
+ break;
+ case 11:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(12, 13);
+ break;
+ case 14:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 11;
+ break;
+ case 16:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(12, 13);
+ break;
+ case 17:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 18:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 19:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 20:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 22:
+ if (kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 22:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 24 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_0(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 2;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_0(int pos, long active0)
+{
+ return jjMoveNfa_0(jjStopStringLiteralDfa_0(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_0()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 1);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_0(0x2a00000L);
+ default :
+ return jjMoveNfa_0(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_0(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_0(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_0(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_0(0, active0);
+}
+private int jjMoveStringLiteralDfa2_0(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_0(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_0(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_0(1, active0);
+}
+private int jjStartNfaWithStates_0(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_0(state, pos + 1);
+}
+private int jjMoveNfa_0(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 75;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(11, 16);
+ }
+ else if ((0x2400L & l) != 0L)
+ {
+ if (kind > 34)
+ kind = 34;
+ }
+ else if ((0x100000200L & l) != 0L)
+ jjCheckNAddStates(17, 19);
+ else if (curChar == 45)
+ jjCheckNAddStates(20, 23);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(26, 27);
+ }
+ else if (curChar == 46)
+ jjCheckNAdd(11);
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 4:
+ if ((0x100000200L & l) != 0L)
+ jjCheckNAddStates(17, 19);
+ break;
+ case 5:
+ if ((0x2400L & l) != 0L && kind > 34)
+ kind = 34;
+ break;
+ case 6:
+ if (curChar == 10 && kind > 34)
+ kind = 34;
+ break;
+ case 7:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 10:
+ if (curChar == 46)
+ jjCheckNAdd(11);
+ break;
+ case 11:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(11, 12);
+ break;
+ case 13:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(14);
+ break;
+ case 14:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(14);
+ break;
+ case 16:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 63)
+ kind = 63;
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 19:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjAddStates(24, 25);
+ break;
+ case 23:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 25:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(26, 27);
+ break;
+ case 27:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 28:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(26, 27);
+ break;
+ case 60:
+ if (curChar == 45)
+ jjCheckNAddStates(20, 23);
+ break;
+ case 61:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(61, 63);
+ break;
+ case 62:
+ if (curChar == 46 && kind > 58)
+ kind = 58;
+ break;
+ case 63:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 62;
+ break;
+ case 64:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(64, 65);
+ break;
+ case 65:
+ if (curChar != 46)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(66, 67);
+ break;
+ case 66:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(66, 67);
+ break;
+ case 68:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(69);
+ break;
+ case 69:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(69);
+ break;
+ case 70:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(70, 71);
+ break;
+ case 72:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(73);
+ break;
+ case 73:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(73);
+ break;
+ case 74:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(11, 16);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if ((0x7fffffe87ffffffL & l) != 0L)
+ {
+ if (kind > 63)
+ kind = 63;
+ jjCheckNAdd(16);
+ }
+ else if (curChar == 123)
+ jjAddStates(26, 29);
+ else if (curChar == 92)
+ jjCheckNAddStates(30, 33);
+ if (curChar == 101)
+ jjAddStates(34, 36);
+ else if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 18;
+ else if (curChar == 105)
+ jjstateSet[jjnewStateCnt++] = 8;
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 8:
+ if (curChar == 102 && kind > 54)
+ kind = 54;
+ break;
+ case 9:
+ if (curChar == 105)
+ jjstateSet[jjnewStateCnt++] = 8;
+ break;
+ case 12:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(37, 38);
+ break;
+ case 15:
+ if ((0x7fffffe87ffffffL & l) == 0L)
+ break;
+ if (kind > 63)
+ kind = 63;
+ jjCheckNAdd(16);
+ break;
+ case 16:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 63)
+ kind = 63;
+ jjCheckNAdd(16);
+ break;
+ case 17:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0x7fffffe87ffffffL & l) != 0L)
+ jjCheckNAddTwoStates(19, 20);
+ break;
+ case 19:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ jjCheckNAddTwoStates(19, 20);
+ break;
+ case 20:
+ if (curChar == 125 && kind > 64)
+ kind = 64;
+ break;
+ case 21:
+ if (curChar == 92)
+ jjCheckNAddStates(30, 33);
+ break;
+ case 22:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(22, 23);
+ break;
+ case 24:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(24, 25);
+ break;
+ case 26:
+ if (curChar == 92)
+ jjAddStates(39, 40);
+ break;
+ case 29:
+ if (curChar == 101)
+ jjAddStates(34, 36);
+ break;
+ case 30:
+ if (curChar == 100 && kind > 53)
+ kind = 53;
+ break;
+ case 31:
+ if (curChar == 110)
+ jjstateSet[jjnewStateCnt++] = 30;
+ break;
+ case 32:
+ if (curChar == 102 && kind > 55)
+ kind = 55;
+ break;
+ case 33:
+ if (curChar == 105)
+ jjstateSet[jjnewStateCnt++] = 32;
+ break;
+ case 34:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 33;
+ break;
+ case 35:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 34;
+ break;
+ case 36:
+ if (curChar == 108)
+ jjstateSet[jjnewStateCnt++] = 35;
+ break;
+ case 37:
+ if (curChar == 101 && kind > 56)
+ kind = 56;
+ break;
+ case 38:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 39:
+ if (curChar == 108)
+ jjstateSet[jjnewStateCnt++] = 38;
+ break;
+ case 40:
+ if (curChar == 123)
+ jjAddStates(26, 29);
+ break;
+ case 41:
+ if (curChar == 125 && kind > 53)
+ kind = 53;
+ break;
+ case 42:
+ if (curChar == 100)
+ jjstateSet[jjnewStateCnt++] = 41;
+ break;
+ case 43:
+ if (curChar == 110)
+ jjstateSet[jjnewStateCnt++] = 42;
+ break;
+ case 44:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 43;
+ break;
+ case 45:
+ if (curChar == 125 && kind > 54)
+ kind = 54;
+ break;
+ case 46:
+ if (curChar == 102)
+ jjstateSet[jjnewStateCnt++] = 45;
+ break;
+ case 47:
+ if (curChar == 105)
+ jjstateSet[jjnewStateCnt++] = 46;
+ break;
+ case 48:
+ if (curChar == 125 && kind > 55)
+ kind = 55;
+ break;
+ case 49:
+ if (curChar == 102)
+ jjstateSet[jjnewStateCnt++] = 48;
+ break;
+ case 50:
+ if (curChar == 105)
+ jjstateSet[jjnewStateCnt++] = 49;
+ break;
+ case 51:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 50;
+ break;
+ case 52:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 51;
+ break;
+ case 53:
+ if (curChar == 108)
+ jjstateSet[jjnewStateCnt++] = 52;
+ break;
+ case 54:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 53;
+ break;
+ case 55:
+ if (curChar == 125 && kind > 56)
+ kind = 56;
+ break;
+ case 56:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 55;
+ break;
+ case 57:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 56;
+ break;
+ case 58:
+ if (curChar == 108)
+ jjstateSet[jjnewStateCnt++] = 57;
+ break;
+ case 59:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 58;
+ break;
+ case 67:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(41, 42);
+ break;
+ case 71:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(43, 44);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 75 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_16(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x4000000000L) != 0L)
+ return 94;
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ if ((active0 & 0x400L) != 0L)
+ return 57;
+ if ((active0 & 0x10000000000000L) != 0L)
+ return 49;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_16(int pos, long active0)
+{
+ return jjMoveNfa_16(jjStopStringLiteralDfa_16(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_16()
+{
+ switch(curChar)
+ {
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_16(0xa00000L);
+ case 37:
+ return jjStopAtPos(0, 42);
+ case 42:
+ return jjStopAtPos(0, 40);
+ case 43:
+ return jjStopAtPos(0, 39);
+ case 44:
+ return jjStopAtPos(0, 9);
+ case 45:
+ return jjStartNfaWithStates_16(0, 38, 94);
+ case 46:
+ return jjMoveStringLiteralDfa1_16(0x400L);
+ case 47:
+ return jjStopAtPos(0, 41);
+ case 58:
+ return jjStopAtPos(0, 11);
+ case 61:
+ return jjStartNfaWithStates_16(0, 52, 49);
+ case 91:
+ return jjStopAtPos(0, 7);
+ case 93:
+ return jjStopAtPos(0, 8);
+ case 102:
+ return jjMoveStringLiteralDfa1_16(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_16(0x1000000000L);
+ case 123:
+ return jjStopAtPos(0, 12);
+ case 125:
+ return jjStopAtPos(0, 13);
+ default :
+ return jjMoveNfa_16(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_16(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_16(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_16(1, 23, 0);
+ break;
+ case 46:
+ if ((active0 & 0x400L) != 0L)
+ return jjStopAtPos(1, 10);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_16(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_16(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_16(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_16(0, active0);
+}
+private int jjMoveStringLiteralDfa2_16(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_16(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_16(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_16(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_16(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_16(1, active0);
+}
+private int jjMoveStringLiteralDfa3_16(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_16(1, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_16(2, active0);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStopAtPos(3, 36);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_16(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_16(2, active0);
+}
+private int jjMoveStringLiteralDfa4_16(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_16(2, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_16(3, active0);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStopAtPos(4, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_16(3, active0);
+}
+private int jjStartNfaWithStates_16(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_16(state, pos + 1);
+}
+private int jjMoveNfa_16(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 94;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 94:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(89, 90);
+ else if (curChar == 46)
+ jjCheckNAdd(57);
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(83, 84);
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(80, 82);
+ }
+ break;
+ case 3:
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(45, 50);
+ }
+ else if ((0x2400L & l) != 0L)
+ {
+ if (kind > 33)
+ kind = 33;
+ }
+ else if ((0x100000200L & l) != 0L)
+ {
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(4);
+ }
+ else if (curChar == 45)
+ jjCheckNAddStates(51, 54);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(66, 67);
+ }
+ else if (curChar == 46)
+ jjCheckNAdd(57);
+ else if (curChar == 33)
+ {
+ if (kind > 51)
+ kind = 51;
+ }
+ else if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 49;
+ else if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 47;
+ else if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 44;
+ else if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 34;
+ else if (curChar == 39)
+ jjCheckNAddStates(55, 58);
+ else if (curChar == 34)
+ jjCheckNAddStates(59, 62);
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 53;
+ else if (curChar == 62)
+ {
+ if (kind > 47)
+ kind = 47;
+ }
+ else if (curChar == 60)
+ {
+ if (kind > 45)
+ kind = 45;
+ }
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 4:
+ if ((0x100000200L & l) == 0L)
+ break;
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(4);
+ break;
+ case 5:
+ if ((0x2400L & l) != 0L && kind > 33)
+ kind = 33;
+ break;
+ case 6:
+ if (curChar == 10 && kind > 33)
+ kind = 33;
+ break;
+ case 7:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 8:
+ case 10:
+ if (curChar == 34)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 9:
+ if ((0xfffffffbefffffffL & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 11:
+ if (curChar == 34)
+ jjstateSet[jjnewStateCnt++] = 10;
+ break;
+ case 12:
+ if (curChar == 34 && kind > 35)
+ kind = 35;
+ break;
+ case 15:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(63, 67);
+ break;
+ case 16:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 17:
+ if ((0xf000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(16);
+ break;
+ case 20:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 21:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 24:
+ if (curChar == 32)
+ jjAddStates(68, 69);
+ break;
+ case 25:
+ if (curChar == 10)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 26:
+ case 28:
+ if (curChar == 39)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 27:
+ if ((0xffffff7fefffffffL & l) != 0L)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 29:
+ if (curChar == 39)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 31:
+ if (curChar == 32)
+ jjAddStates(70, 71);
+ break;
+ case 32:
+ if (curChar == 10)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 33:
+ if (curChar == 39 && kind > 35)
+ kind = 35;
+ break;
+ case 34:
+ if (curChar == 38 && kind > 43)
+ kind = 43;
+ break;
+ case 35:
+ if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 34;
+ break;
+ case 43:
+ if (curChar == 60 && kind > 45)
+ kind = 45;
+ break;
+ case 44:
+ if (curChar == 61 && kind > 46)
+ kind = 46;
+ break;
+ case 45:
+ if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 44;
+ break;
+ case 46:
+ if (curChar == 62 && kind > 47)
+ kind = 47;
+ break;
+ case 47:
+ if (curChar == 61 && kind > 48)
+ kind = 48;
+ break;
+ case 48:
+ if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 47;
+ break;
+ case 49:
+ if (curChar == 61 && kind > 49)
+ kind = 49;
+ break;
+ case 50:
+ if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 49;
+ break;
+ case 53:
+ if (curChar == 61 && kind > 50)
+ kind = 50;
+ break;
+ case 54:
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 53;
+ break;
+ case 55:
+ if (curChar == 33 && kind > 51)
+ kind = 51;
+ break;
+ case 56:
+ if (curChar == 46)
+ jjCheckNAdd(57);
+ break;
+ case 57:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(57, 58);
+ break;
+ case 59:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(60);
+ break;
+ case 60:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(60);
+ break;
+ case 63:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 65:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(66, 67);
+ break;
+ case 67:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 68:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(66, 67);
+ break;
+ case 79:
+ if (curChar == 45)
+ jjCheckNAddStates(51, 54);
+ break;
+ case 80:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(80, 82);
+ break;
+ case 81:
+ if (curChar == 46 && kind > 58)
+ kind = 58;
+ break;
+ case 82:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 81;
+ break;
+ case 83:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(83, 84);
+ break;
+ case 84:
+ if (curChar != 46)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(85, 86);
+ break;
+ case 85:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(85, 86);
+ break;
+ case 87:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(88);
+ break;
+ case 88:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(88);
+ break;
+ case 89:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(89, 90);
+ break;
+ case 91:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(92);
+ break;
+ case 92:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(92);
+ break;
+ case 93:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(45, 50);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 110)
+ jjAddStates(72, 73);
+ else if (curChar == 103)
+ jjAddStates(74, 75);
+ else if (curChar == 108)
+ jjAddStates(76, 77);
+ else if (curChar == 92)
+ jjCheckNAddStates(78, 81);
+ else if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 51;
+ else if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 41;
+ else if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 39;
+ else if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 9:
+ jjCheckNAddStates(59, 62);
+ break;
+ case 13:
+ if (curChar == 92)
+ jjAddStates(82, 87);
+ break;
+ case 14:
+ if ((0x14404400000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 19:
+ if (curChar == 117)
+ jjstateSet[jjnewStateCnt++] = 20;
+ break;
+ case 20:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 21:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 27:
+ jjAddStates(55, 58);
+ break;
+ case 30:
+ if (curChar == 92)
+ jjAddStates(70, 71);
+ break;
+ case 36:
+ if (curChar == 100 && kind > 43)
+ kind = 43;
+ break;
+ case 37:
+ if (curChar == 110)
+ jjstateSet[jjnewStateCnt++] = 36;
+ break;
+ case 38:
+ if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 39:
+ if (curChar == 124 && kind > 44)
+ kind = 44;
+ break;
+ case 40:
+ if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 39;
+ break;
+ case 41:
+ if (curChar == 114 && kind > 44)
+ kind = 44;
+ break;
+ case 42:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 41;
+ break;
+ case 51:
+ if (curChar == 113 && kind > 49)
+ kind = 49;
+ break;
+ case 52:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 51;
+ break;
+ case 58:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(88, 89);
+ break;
+ case 61:
+ if (curChar == 92)
+ jjCheckNAddStates(78, 81);
+ break;
+ case 62:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(62, 63);
+ break;
+ case 64:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(64, 65);
+ break;
+ case 66:
+ if (curChar == 92)
+ jjAddStates(90, 91);
+ break;
+ case 69:
+ if (curChar == 108)
+ jjAddStates(76, 77);
+ break;
+ case 70:
+ if (curChar == 116 && kind > 45)
+ kind = 45;
+ break;
+ case 71:
+ if (curChar == 101 && kind > 46)
+ kind = 46;
+ break;
+ case 72:
+ if (curChar == 103)
+ jjAddStates(74, 75);
+ break;
+ case 73:
+ if (curChar == 116 && kind > 47)
+ kind = 47;
+ break;
+ case 74:
+ if (curChar == 101 && kind > 48)
+ kind = 48;
+ break;
+ case 75:
+ if (curChar == 110)
+ jjAddStates(72, 73);
+ break;
+ case 76:
+ if (curChar == 101 && kind > 50)
+ kind = 50;
+ break;
+ case 77:
+ if (curChar == 116 && kind > 51)
+ kind = 51;
+ break;
+ case 78:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 77;
+ break;
+ case 86:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(92, 93);
+ break;
+ case 90:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(94, 95);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ case 9:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(59, 62);
+ break;
+ case 27:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(55, 58);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 94 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_12(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ if ((active0 & 0x20000000L) != 0L)
+ {
+ jjmatchedKind = 31;
+ return -1;
+ }
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ if ((active0 & 0x20000000L) != 0L)
+ {
+ if (jjmatchedPos == 0)
+ {
+ jjmatchedKind = 31;
+ jjmatchedPos = 0;
+ }
+ return -1;
+ }
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_12(int pos, long active0)
+{
+ return jjMoveNfa_12(jjStopStringLiteralDfa_12(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_12()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_12(0xa00000L);
+ case 93:
+ return jjMoveStringLiteralDfa1_12(0x20000000L);
+ default :
+ return jjMoveNfa_12(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_12(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_12(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_12(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_12(active0, 0x200000L);
+ case 93:
+ return jjMoveStringLiteralDfa2_12(active0, 0x20000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_12(0, active0);
+}
+private int jjMoveStringLiteralDfa2_12(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_12(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_12(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x20000000L) != 0L)
+ return jjStopAtPos(2, 29);
+ break;
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_12(1, active0);
+}
+private int jjStartNfaWithStates_12(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_12(state, pos + 1);
+}
+private int jjMoveNfa_12(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 13;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if ((0xffffffffefffffffL & l) != 0L)
+ {
+ if (kind > 31)
+ kind = 31;
+ }
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(10, 11);
+ }
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 4:
+ if ((0xffffffffefffffffL & l) != 0L && kind > 31)
+ kind = 31;
+ break;
+ case 7:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 9:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(10, 11);
+ break;
+ case 11:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 12:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(10, 11);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (kind > 31)
+ kind = 31;
+ if (curChar == 92)
+ jjCheckNAddStates(96, 99);
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 4:
+ if (kind > 31)
+ kind = 31;
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddStates(96, 99);
+ break;
+ case 6:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(6, 7);
+ break;
+ case 8:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(8, 9);
+ break;
+ case 10:
+ if (curChar == 92)
+ jjAddStates(100, 101);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 31)
+ kind = 31;
+ break;
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 13 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_15(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_15(int pos, long active0)
+{
+ return jjMoveNfa_15(jjStopStringLiteralDfa_15(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_15()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_15(0xa00000L);
+ default :
+ return jjMoveNfa_15(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_15(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_15(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_15(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_15(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_15(0, active0);
+}
+private int jjMoveStringLiteralDfa2_15(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_15(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_15(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_15(1, active0);
+}
+private int jjStartNfaWithStates_15(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_15(state, pos + 1);
+}
+private int jjMoveNfa_15(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 15;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if ((0x2400L & l) != 0L)
+ {
+ if (kind > 26)
+ kind = 26;
+ }
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(12, 13);
+ }
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 5;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 4:
+ if ((0x2400L & l) != 0L && kind > 26)
+ kind = 26;
+ break;
+ case 5:
+ if (curChar == 10 && kind > 26)
+ kind = 26;
+ break;
+ case 6:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 5;
+ break;
+ case 9:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 11:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(12, 13);
+ break;
+ case 13:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 14:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(12, 13);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddStates(102, 105);
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 8:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(8, 9);
+ break;
+ case 10:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(10, 11);
+ break;
+ case 12:
+ if (curChar == 92)
+ jjAddStates(3, 4);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 15 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_5(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 70;
+ return 1;
+ }
+ if ((active0 & 0x3a00000L) != 0L)
+ return 17;
+ return -1;
+ case 1:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 70;
+ jjmatchedPos = 1;
+ return 1;
+ }
+ if ((active0 & 0x800000L) != 0L)
+ return 23;
+ return -1;
+ case 2:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 70;
+ jjmatchedPos = 2;
+ return 1;
+ }
+ return -1;
+ case 3:
+ if ((active0 & 0x2000000000L) != 0L)
+ {
+ jjmatchedKind = 70;
+ jjmatchedPos = 3;
+ return 1;
+ }
+ if ((active0 & 0x1000000000L) != 0L)
+ return 1;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_5(int pos, long active0, long active1)
+{
+ return jjMoveNfa_5(jjStopStringLiteralDfa_5(pos, active0, active1), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_5()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_5(0x2a00000L);
+ case 40:
+ return jjStopAtPos(0, 14);
+ case 91:
+ return jjStopAtPos(0, 3);
+ case 102:
+ return jjMoveStringLiteralDfa1_5(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_5(0x1000000000L);
+ case 123:
+ return jjStopAtPos(0, 72);
+ case 124:
+ jjmatchedKind = 5;
+ return jjMoveStringLiteralDfa1_5(0x10L);
+ case 125:
+ return jjStopAtPos(0, 73);
+ default :
+ return jjMoveNfa_5(0, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_5(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_5(0, active0, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_5(1, 23, 23);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_5(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_5(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_5(active0, 0x1000000000L);
+ case 124:
+ if ((active0 & 0x10L) != 0L)
+ return jjStopAtPos(1, 4);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_5(0, active0, 0L);
+}
+private int jjMoveStringLiteralDfa2_5(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_5(0, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_5(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_5(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_5(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_5(1, active0, 0L);
+}
+private int jjMoveStringLiteralDfa3_5(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_5(1, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_5(2, active0, 0L);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStartNfaWithStates_5(3, 36, 1);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_5(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_5(2, active0, 0L);
+}
+private int jjMoveStringLiteralDfa4_5(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_5(2, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_5(3, active0, 0L);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStartNfaWithStates_5(4, 37, 1);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_5(3, active0, 0L);
+}
+private int jjStartNfaWithStates_5(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_5(state, pos + 1);
+}
+private int jjMoveNfa_5(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 26;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (curChar == 35)
+ jjAddStates(106, 108);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ }
+ else if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 3;
+ break;
+ case 17:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 1:
+ if ((0x3ff200000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 2:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 3;
+ break;
+ case 6:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 8:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 10:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 11:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 12:
+ if (curChar == 35)
+ jjAddStates(106, 108);
+ break;
+ case 14:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(109, 110);
+ break;
+ case 15:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 23:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 24;
+ break;
+ case 24:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ {
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAdd(1);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 17:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 21;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 1:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAdd(1);
+ break;
+ case 3:
+ if ((0x7fffffe87fffffeL & l) != 0L && kind > 71)
+ kind = 71;
+ break;
+ case 4:
+ if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(115, 116);
+ break;
+ case 13:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(14, 15);
+ break;
+ case 16:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 13;
+ break;
+ case 18:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(14, 15);
+ break;
+ case 19:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 20:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 21:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 20;
+ break;
+ case 22:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 24:
+ if (kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 24:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 26 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_8(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 2;
+ if ((active0 & 0x400L) != 0L)
+ return 57;
+ if ((active0 & 0x4000000000L) != 0L)
+ return 100;
+ if ((active0 & 0x10000000000000L) != 0L)
+ return 49;
+ if ((active0 & 0x1000L) != 0L)
+ return 64;
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 63;
+ return 62;
+ }
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 63;
+ jjmatchedPos = 1;
+ return 62;
+ }
+ return -1;
+ case 2:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 63;
+ jjmatchedPos = 2;
+ return 62;
+ }
+ return -1;
+ case 3:
+ if ((active0 & 0x1000000000L) != 0L)
+ return 62;
+ if ((active0 & 0x2000000000L) != 0L)
+ {
+ jjmatchedKind = 63;
+ jjmatchedPos = 3;
+ return 62;
+ }
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_8(int pos, long active0)
+{
+ return jjMoveNfa_8(jjStopStringLiteralDfa_8(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_8()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_8(0x2a00000L);
+ case 37:
+ return jjStopAtPos(0, 42);
+ case 40:
+ return jjStopAtPos(0, 14);
+ case 41:
+ return jjStopAtPos(0, 15);
+ case 42:
+ return jjStopAtPos(0, 40);
+ case 43:
+ return jjStopAtPos(0, 39);
+ case 44:
+ return jjStopAtPos(0, 9);
+ case 45:
+ return jjStartNfaWithStates_8(0, 38, 100);
+ case 46:
+ return jjMoveStringLiteralDfa1_8(0x400L);
+ case 47:
+ return jjStopAtPos(0, 41);
+ case 58:
+ return jjStopAtPos(0, 11);
+ case 61:
+ return jjStartNfaWithStates_8(0, 52, 49);
+ case 91:
+ return jjStopAtPos(0, 7);
+ case 93:
+ return jjStopAtPos(0, 8);
+ case 102:
+ return jjMoveStringLiteralDfa1_8(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_8(0x1000000000L);
+ case 123:
+ return jjStartNfaWithStates_8(0, 12, 64);
+ case 125:
+ return jjStopAtPos(0, 13);
+ default :
+ return jjMoveNfa_8(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_8(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_8(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_8(1, 23, 0);
+ break;
+ case 46:
+ if ((active0 & 0x400L) != 0L)
+ return jjStopAtPos(1, 10);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_8(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_8(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_8(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_8(0, active0);
+}
+private int jjMoveStringLiteralDfa2_8(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_8(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_8(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_8(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_8(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_8(1, active0);
+}
+private int jjMoveStringLiteralDfa3_8(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_8(1, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_8(2, active0);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStartNfaWithStates_8(3, 36, 62);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_8(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_8(2, active0);
+}
+private int jjMoveStringLiteralDfa4_8(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_8(2, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_8(3, active0);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStartNfaWithStates_8(4, 37, 62);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_8(3, active0);
+}
+private int jjStartNfaWithStates_8(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_8(state, pos + 1);
+}
+private int jjMoveNfa_8(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 100;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(117, 122);
+ }
+ else if ((0x2400L & l) != 0L)
+ {
+ if (kind > 33)
+ kind = 33;
+ }
+ else if ((0x100000200L & l) != 0L)
+ {
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(4);
+ }
+ else if (curChar == 45)
+ jjCheckNAddStates(123, 126);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(72, 73);
+ }
+ else if (curChar == 46)
+ jjCheckNAdd(57);
+ else if (curChar == 33)
+ {
+ if (kind > 51)
+ kind = 51;
+ }
+ else if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 49;
+ else if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 47;
+ else if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 44;
+ else if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 34;
+ else if (curChar == 39)
+ jjCheckNAddStates(55, 58);
+ else if (curChar == 34)
+ jjCheckNAddStates(59, 62);
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 53;
+ else if (curChar == 62)
+ {
+ if (kind > 47)
+ kind = 47;
+ }
+ else if (curChar == 60)
+ {
+ if (kind > 45)
+ kind = 45;
+ }
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 100:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(95, 96);
+ else if (curChar == 46)
+ jjCheckNAdd(57);
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(89, 90);
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(86, 88);
+ }
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 4:
+ if ((0x100000200L & l) == 0L)
+ break;
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(4);
+ break;
+ case 5:
+ if ((0x2400L & l) != 0L && kind > 33)
+ kind = 33;
+ break;
+ case 6:
+ if (curChar == 10 && kind > 33)
+ kind = 33;
+ break;
+ case 7:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 8:
+ case 10:
+ if (curChar == 34)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 9:
+ if ((0xfffffffbefffffffL & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 11:
+ if (curChar == 34)
+ jjstateSet[jjnewStateCnt++] = 10;
+ break;
+ case 12:
+ if (curChar == 34 && kind > 35)
+ kind = 35;
+ break;
+ case 15:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(63, 67);
+ break;
+ case 16:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 17:
+ if ((0xf000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(16);
+ break;
+ case 20:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 21:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 24:
+ if (curChar == 32)
+ jjAddStates(68, 69);
+ break;
+ case 25:
+ if (curChar == 10)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 26:
+ case 28:
+ if (curChar == 39)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 27:
+ if ((0xffffff7fefffffffL & l) != 0L)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 29:
+ if (curChar == 39)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 31:
+ if (curChar == 32)
+ jjAddStates(70, 71);
+ break;
+ case 32:
+ if (curChar == 10)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 33:
+ if (curChar == 39 && kind > 35)
+ kind = 35;
+ break;
+ case 34:
+ if (curChar == 38 && kind > 43)
+ kind = 43;
+ break;
+ case 35:
+ if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 34;
+ break;
+ case 43:
+ if (curChar == 60 && kind > 45)
+ kind = 45;
+ break;
+ case 44:
+ if (curChar == 61 && kind > 46)
+ kind = 46;
+ break;
+ case 45:
+ if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 44;
+ break;
+ case 46:
+ if (curChar == 62 && kind > 47)
+ kind = 47;
+ break;
+ case 47:
+ if (curChar == 61 && kind > 48)
+ kind = 48;
+ break;
+ case 48:
+ if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 47;
+ break;
+ case 49:
+ if (curChar == 61 && kind > 49)
+ kind = 49;
+ break;
+ case 50:
+ if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 49;
+ break;
+ case 53:
+ if (curChar == 61 && kind > 50)
+ kind = 50;
+ break;
+ case 54:
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 53;
+ break;
+ case 55:
+ if (curChar == 33 && kind > 51)
+ kind = 51;
+ break;
+ case 56:
+ if (curChar == 46)
+ jjCheckNAdd(57);
+ break;
+ case 57:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(57, 58);
+ break;
+ case 59:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(60);
+ break;
+ case 60:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(60);
+ break;
+ case 62:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 63)
+ kind = 63;
+ jjstateSet[jjnewStateCnt++] = 62;
+ break;
+ case 65:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjAddStates(127, 128);
+ break;
+ case 69:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 71:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(72, 73);
+ break;
+ case 73:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 74:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(72, 73);
+ break;
+ case 85:
+ if (curChar == 45)
+ jjCheckNAddStates(123, 126);
+ break;
+ case 86:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(86, 88);
+ break;
+ case 87:
+ if (curChar == 46 && kind > 58)
+ kind = 58;
+ break;
+ case 88:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 87;
+ break;
+ case 89:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(89, 90);
+ break;
+ case 90:
+ if (curChar != 46)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(91, 92);
+ break;
+ case 91:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(91, 92);
+ break;
+ case 93:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(94);
+ break;
+ case 94:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(94);
+ break;
+ case 95:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(95, 96);
+ break;
+ case 97:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(98);
+ break;
+ case 98:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(98);
+ break;
+ case 99:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(117, 122);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if ((0x7fffffe87ffffffL & l) != 0L)
+ {
+ if (kind > 63)
+ kind = 63;
+ jjCheckNAdd(62);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(129, 132);
+ else if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 64;
+ else if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 39;
+ if (curChar == 110)
+ jjAddStates(133, 134);
+ else if (curChar == 103)
+ jjAddStates(135, 136);
+ else if (curChar == 108)
+ jjAddStates(137, 138);
+ else if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 51;
+ else if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 41;
+ else if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 9:
+ jjCheckNAddStates(59, 62);
+ break;
+ case 13:
+ if (curChar == 92)
+ jjAddStates(82, 87);
+ break;
+ case 14:
+ if ((0x14404400000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 19:
+ if (curChar == 117)
+ jjstateSet[jjnewStateCnt++] = 20;
+ break;
+ case 20:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 21:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 27:
+ jjAddStates(55, 58);
+ break;
+ case 30:
+ if (curChar == 92)
+ jjAddStates(70, 71);
+ break;
+ case 36:
+ if (curChar == 100 && kind > 43)
+ kind = 43;
+ break;
+ case 37:
+ if (curChar == 110)
+ jjstateSet[jjnewStateCnt++] = 36;
+ break;
+ case 38:
+ if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 39:
+ if (curChar == 124 && kind > 44)
+ kind = 44;
+ break;
+ case 40:
+ if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 39;
+ break;
+ case 41:
+ if (curChar == 114 && kind > 44)
+ kind = 44;
+ break;
+ case 42:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 41;
+ break;
+ case 51:
+ if (curChar == 113 && kind > 49)
+ kind = 49;
+ break;
+ case 52:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 51;
+ break;
+ case 58:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(88, 89);
+ break;
+ case 61:
+ if ((0x7fffffe87ffffffL & l) == 0L)
+ break;
+ if (kind > 63)
+ kind = 63;
+ jjCheckNAdd(62);
+ break;
+ case 62:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 63)
+ kind = 63;
+ jjCheckNAdd(62);
+ break;
+ case 63:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 64;
+ break;
+ case 64:
+ if ((0x7fffffe87ffffffL & l) != 0L)
+ jjCheckNAddTwoStates(65, 66);
+ break;
+ case 65:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ jjCheckNAddTwoStates(65, 66);
+ break;
+ case 66:
+ if (curChar == 125 && kind > 64)
+ kind = 64;
+ break;
+ case 67:
+ if (curChar == 92)
+ jjCheckNAddStates(129, 132);
+ break;
+ case 68:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(68, 69);
+ break;
+ case 70:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(70, 71);
+ break;
+ case 72:
+ if (curChar == 92)
+ jjAddStates(43, 44);
+ break;
+ case 75:
+ if (curChar == 108)
+ jjAddStates(137, 138);
+ break;
+ case 76:
+ if (curChar == 116 && kind > 45)
+ kind = 45;
+ break;
+ case 77:
+ if (curChar == 101 && kind > 46)
+ kind = 46;
+ break;
+ case 78:
+ if (curChar == 103)
+ jjAddStates(135, 136);
+ break;
+ case 79:
+ if (curChar == 116 && kind > 47)
+ kind = 47;
+ break;
+ case 80:
+ if (curChar == 101 && kind > 48)
+ kind = 48;
+ break;
+ case 81:
+ if (curChar == 110)
+ jjAddStates(133, 134);
+ break;
+ case 82:
+ if (curChar == 101 && kind > 50)
+ kind = 50;
+ break;
+ case 83:
+ if (curChar == 116 && kind > 51)
+ kind = 51;
+ break;
+ case 84:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 83;
+ break;
+ case 92:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(139, 140);
+ break;
+ case 96:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(141, 142);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ case 9:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(59, 62);
+ break;
+ case 27:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(55, 58);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 100 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_17(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_17(int pos, long active0)
+{
+ return jjMoveNfa_17(jjStopStringLiteralDfa_17(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_17()
+{
+ switch(curChar)
+ {
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_17(0xa00000L);
+ case 42:
+ return jjMoveStringLiteralDfa1_17(0x10000000L);
+ default :
+ return jjMoveNfa_17(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_17(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_17(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x10000000L) != 0L)
+ return jjStopAtPos(1, 28);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_17(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_17(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_17(0, active0);
+}
+private int jjMoveStringLiteralDfa2_17(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_17(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_17(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_17(1, active0);
+}
+private int jjStartNfaWithStates_17(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_17(state, pos + 1);
+}
+private int jjMoveNfa_17(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 12;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ }
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 6:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 8:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 10:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 11:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(115, 116);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 12 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_7(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x4000000000L) != 0L)
+ return 94;
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ if ((active0 & 0x10000000000000L) != 0L)
+ return 49;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_7(int pos, long active0)
+{
+ return jjMoveNfa_7(jjStopStringLiteralDfa_7(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_7()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_7(0xa00000L);
+ case 37:
+ return jjStopAtPos(0, 42);
+ case 42:
+ return jjStopAtPos(0, 40);
+ case 43:
+ return jjStopAtPos(0, 39);
+ case 45:
+ return jjStartNfaWithStates_7(0, 38, 94);
+ case 47:
+ return jjStopAtPos(0, 41);
+ case 61:
+ return jjStartNfaWithStates_7(0, 52, 49);
+ case 93:
+ return jjStopAtPos(0, 6);
+ case 102:
+ return jjMoveStringLiteralDfa1_7(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_7(0x1000000000L);
+ default :
+ return jjMoveNfa_7(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_7(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_7(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_7(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_7(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_7(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_7(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_7(0, active0);
+}
+private int jjMoveStringLiteralDfa2_7(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_7(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_7(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_7(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_7(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_7(1, active0);
+}
+private int jjMoveStringLiteralDfa3_7(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_7(1, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_7(2, active0);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStopAtPos(3, 36);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_7(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_7(2, active0);
+}
+private int jjMoveStringLiteralDfa4_7(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_7(2, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_7(3, active0);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStopAtPos(4, 37);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_7(3, active0);
+}
+private int jjStartNfaWithStates_7(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_7(state, pos + 1);
+}
+private int jjMoveNfa_7(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 94;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 94:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(89, 90);
+ else if (curChar == 46)
+ jjCheckNAdd(57);
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(83, 84);
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(80, 82);
+ }
+ break;
+ case 3:
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(45, 50);
+ }
+ else if ((0x2400L & l) != 0L)
+ {
+ if (kind > 33)
+ kind = 33;
+ }
+ else if ((0x100000200L & l) != 0L)
+ {
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(4);
+ }
+ else if (curChar == 45)
+ jjCheckNAddStates(51, 54);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(66, 67);
+ }
+ else if (curChar == 46)
+ jjCheckNAdd(57);
+ else if (curChar == 33)
+ {
+ if (kind > 51)
+ kind = 51;
+ }
+ else if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 49;
+ else if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 47;
+ else if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 44;
+ else if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 34;
+ else if (curChar == 39)
+ jjCheckNAddStates(55, 58);
+ else if (curChar == 34)
+ jjCheckNAddStates(59, 62);
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 53;
+ else if (curChar == 62)
+ {
+ if (kind > 47)
+ kind = 47;
+ }
+ else if (curChar == 60)
+ {
+ if (kind > 45)
+ kind = 45;
+ }
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 4:
+ if ((0x100000200L & l) == 0L)
+ break;
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(4);
+ break;
+ case 5:
+ if ((0x2400L & l) != 0L && kind > 33)
+ kind = 33;
+ break;
+ case 6:
+ if (curChar == 10 && kind > 33)
+ kind = 33;
+ break;
+ case 7:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 8:
+ case 10:
+ if (curChar == 34)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 9:
+ if ((0xfffffffbefffffffL & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 11:
+ if (curChar == 34)
+ jjstateSet[jjnewStateCnt++] = 10;
+ break;
+ case 12:
+ if (curChar == 34 && kind > 35)
+ kind = 35;
+ break;
+ case 15:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(63, 67);
+ break;
+ case 16:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 17:
+ if ((0xf000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(16);
+ break;
+ case 20:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 21:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 24:
+ if (curChar == 32)
+ jjAddStates(68, 69);
+ break;
+ case 25:
+ if (curChar == 10)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 26:
+ case 28:
+ if (curChar == 39)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 27:
+ if ((0xffffff7fefffffffL & l) != 0L)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 29:
+ if (curChar == 39)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 31:
+ if (curChar == 32)
+ jjAddStates(70, 71);
+ break;
+ case 32:
+ if (curChar == 10)
+ jjCheckNAddStates(55, 58);
+ break;
+ case 33:
+ if (curChar == 39 && kind > 35)
+ kind = 35;
+ break;
+ case 34:
+ if (curChar == 38 && kind > 43)
+ kind = 43;
+ break;
+ case 35:
+ if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 34;
+ break;
+ case 43:
+ if (curChar == 60 && kind > 45)
+ kind = 45;
+ break;
+ case 44:
+ if (curChar == 61 && kind > 46)
+ kind = 46;
+ break;
+ case 45:
+ if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 44;
+ break;
+ case 46:
+ if (curChar == 62 && kind > 47)
+ kind = 47;
+ break;
+ case 47:
+ if (curChar == 61 && kind > 48)
+ kind = 48;
+ break;
+ case 48:
+ if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 47;
+ break;
+ case 49:
+ if (curChar == 61 && kind > 49)
+ kind = 49;
+ break;
+ case 50:
+ if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 49;
+ break;
+ case 53:
+ if (curChar == 61 && kind > 50)
+ kind = 50;
+ break;
+ case 54:
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 53;
+ break;
+ case 55:
+ if (curChar == 33 && kind > 51)
+ kind = 51;
+ break;
+ case 56:
+ if (curChar == 46)
+ jjCheckNAdd(57);
+ break;
+ case 57:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(57, 58);
+ break;
+ case 59:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(60);
+ break;
+ case 60:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(60);
+ break;
+ case 63:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 65:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(66, 67);
+ break;
+ case 67:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 68:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(66, 67);
+ break;
+ case 79:
+ if (curChar == 45)
+ jjCheckNAddStates(51, 54);
+ break;
+ case 80:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(80, 82);
+ break;
+ case 81:
+ if (curChar == 46 && kind > 58)
+ kind = 58;
+ break;
+ case 82:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 81;
+ break;
+ case 83:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(83, 84);
+ break;
+ case 84:
+ if (curChar != 46)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(85, 86);
+ break;
+ case 85:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(85, 86);
+ break;
+ case 87:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(88);
+ break;
+ case 88:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(88);
+ break;
+ case 89:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(89, 90);
+ break;
+ case 91:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(92);
+ break;
+ case 92:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(92);
+ break;
+ case 93:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(45, 50);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 110)
+ jjAddStates(72, 73);
+ else if (curChar == 103)
+ jjAddStates(74, 75);
+ else if (curChar == 108)
+ jjAddStates(76, 77);
+ else if (curChar == 92)
+ jjCheckNAddStates(78, 81);
+ else if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 51;
+ else if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 41;
+ else if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 39;
+ else if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 9:
+ jjCheckNAddStates(59, 62);
+ break;
+ case 13:
+ if (curChar == 92)
+ jjAddStates(82, 87);
+ break;
+ case 14:
+ if ((0x14404400000000L & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 19:
+ if (curChar == 117)
+ jjstateSet[jjnewStateCnt++] = 20;
+ break;
+ case 20:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 21:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddStates(59, 62);
+ break;
+ case 27:
+ jjAddStates(55, 58);
+ break;
+ case 30:
+ if (curChar == 92)
+ jjAddStates(70, 71);
+ break;
+ case 36:
+ if (curChar == 100 && kind > 43)
+ kind = 43;
+ break;
+ case 37:
+ if (curChar == 110)
+ jjstateSet[jjnewStateCnt++] = 36;
+ break;
+ case 38:
+ if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 39:
+ if (curChar == 124 && kind > 44)
+ kind = 44;
+ break;
+ case 40:
+ if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 39;
+ break;
+ case 41:
+ if (curChar == 114 && kind > 44)
+ kind = 44;
+ break;
+ case 42:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 41;
+ break;
+ case 51:
+ if (curChar == 113 && kind > 49)
+ kind = 49;
+ break;
+ case 52:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 51;
+ break;
+ case 58:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(88, 89);
+ break;
+ case 61:
+ if (curChar == 92)
+ jjCheckNAddStates(78, 81);
+ break;
+ case 62:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(62, 63);
+ break;
+ case 64:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(64, 65);
+ break;
+ case 66:
+ if (curChar == 92)
+ jjAddStates(90, 91);
+ break;
+ case 69:
+ if (curChar == 108)
+ jjAddStates(76, 77);
+ break;
+ case 70:
+ if (curChar == 116 && kind > 45)
+ kind = 45;
+ break;
+ case 71:
+ if (curChar == 101 && kind > 46)
+ kind = 46;
+ break;
+ case 72:
+ if (curChar == 103)
+ jjAddStates(74, 75);
+ break;
+ case 73:
+ if (curChar == 116 && kind > 47)
+ kind = 47;
+ break;
+ case 74:
+ if (curChar == 101 && kind > 48)
+ kind = 48;
+ break;
+ case 75:
+ if (curChar == 110)
+ jjAddStates(72, 73);
+ break;
+ case 76:
+ if (curChar == 101 && kind > 50)
+ kind = 50;
+ break;
+ case 77:
+ if (curChar == 116 && kind > 51)
+ kind = 51;
+ break;
+ case 78:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 77;
+ break;
+ case 86:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(92, 93);
+ break;
+ case 90:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(94, 95);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ case 9:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(59, 62);
+ break;
+ case 27:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(55, 58);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 94 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_13(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_13(int pos, long active0)
+{
+ return jjMoveNfa_13(jjStopStringLiteralDfa_13(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_13()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_13(0xa00000L);
+ default :
+ return jjMoveNfa_13(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_13(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_13(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_13(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_13(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_13(0, active0);
+}
+private int jjMoveStringLiteralDfa2_13(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_13(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_13(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_13(1, active0);
+}
+private int jjStartNfaWithStates_13(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_13(state, pos + 1);
+}
+private int jjMoveNfa_13(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 12;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ }
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 6:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 8:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 10:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 11:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(115, 116);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 12 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_14(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_14(int pos, long active0)
+{
+ return jjMoveNfa_14(jjStopStringLiteralDfa_14(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_14()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_14(0xa00000L);
+ case 42:
+ return jjMoveStringLiteralDfa1_14(0x8000000L);
+ default :
+ return jjMoveNfa_14(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_14(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_14(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x8000000L) != 0L)
+ return jjStopAtPos(1, 27);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_14(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_14(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_14(0, active0);
+}
+private int jjMoveStringLiteralDfa2_14(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_14(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_14(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_14(1, active0);
+}
+private int jjStartNfaWithStates_14(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_14(state, pos + 1);
+}
+private int jjMoveNfa_14(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 12;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ }
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 6:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 8:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 10:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 11:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(115, 116);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 12 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_2(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 25;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 31;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_2(int pos, long active0, long active1)
+{
+ return jjMoveNfa_2(jjStopStringLiteralDfa_2(pos, active0, active1), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_2()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 1);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_2(0x2a00000L);
+ case 123:
+ return jjStopAtPos(0, 72);
+ case 125:
+ return jjStopAtPos(0, 73);
+ default :
+ return jjMoveNfa_2(4, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_2(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_2(0, active0, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_2(1, 23, 31);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_2(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_2(0, active0, 0L);
+}
+private int jjMoveStringLiteralDfa2_2(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_2(0, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_2(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_2(1, active0, 0L);
+}
+private int jjStartNfaWithStates_2(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_2(state, pos + 1);
+}
+private int jjMoveNfa_2(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 34;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 25:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 31;
+ break;
+ case 4:
+ if ((0x2400L & l) != 0L)
+ {
+ if (kind > 34)
+ kind = 34;
+ }
+ else if ((0x100000200L & l) != 0L)
+ jjCheckNAddStates(143, 145);
+ else if (curChar == 35)
+ jjAddStates(146, 148);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(17, 18);
+ }
+ if (curChar == 36)
+ jjCheckNAddStates(149, 152);
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if ((0x100000200L & l) != 0L)
+ jjCheckNAddStates(143, 145);
+ break;
+ case 1:
+ if ((0x2400L & l) != 0L && kind > 34)
+ kind = 34;
+ break;
+ case 2:
+ if (curChar == 10 && kind > 34)
+ kind = 34;
+ break;
+ case 3:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 5:
+ if ((0x3ff200000000000L & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjstateSet[jjnewStateCnt++] = 5;
+ break;
+ case 6:
+ if (curChar == 36)
+ jjCheckNAddStates(149, 152);
+ break;
+ case 8:
+ case 9:
+ if (curChar == 33)
+ jjCheckNAdd(7);
+ break;
+ case 11:
+ if (curChar == 46 && kind > 80)
+ kind = 80;
+ break;
+ case 14:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 16:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(17, 18);
+ break;
+ case 18:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 19:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(17, 18);
+ break;
+ case 20:
+ if (curChar == 35)
+ jjAddStates(146, 148);
+ break;
+ case 22:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(153, 154);
+ break;
+ case 23:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 31:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 32;
+ break;
+ case 32:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 25:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 29;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 24;
+ break;
+ case 4:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ {
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAdd(5);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(155, 158);
+ break;
+ case 5:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 70)
+ kind = 70;
+ jjCheckNAdd(5);
+ break;
+ case 7:
+ if (curChar == 91 && kind > 80)
+ kind = 80;
+ break;
+ case 10:
+ if (curChar == 92)
+ jjstateSet[jjnewStateCnt++] = 9;
+ break;
+ case 12:
+ if (curChar == 92)
+ jjCheckNAddStates(155, 158);
+ break;
+ case 13:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(13, 14);
+ break;
+ case 15:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(15, 16);
+ break;
+ case 17:
+ if (curChar == 92)
+ jjAddStates(159, 160);
+ break;
+ case 21:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(22, 23);
+ break;
+ case 24:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 26:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(22, 23);
+ break;
+ case 27:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 26;
+ break;
+ case 28:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 27;
+ break;
+ case 29:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 30:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 29;
+ break;
+ case 32:
+ if (kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 32:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 34 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_10(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 39;
+ if ((active1 & 0x3000L) != 0L)
+ return 18;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 45;
+ if ((active1 & 0x1000L) != 0L)
+ return 51;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_10(int pos, long active0, long active1)
+{
+ return jjMoveNfa_10(jjStopStringLiteralDfa_10(pos, active0, active1), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_10()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_10(0x2a00000L, 0x0L);
+ case 92:
+ jjmatchedKind = 77;
+ return jjMoveStringLiteralDfa1_10(0x0L, 0x1000L);
+ default :
+ return jjMoveNfa_10(1, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_10(long active0, long active1)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_10(0, active0, active1);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_10(1, 23, 45);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_10(active0, 0x200000L, active1, 0L);
+ case 92:
+ if ((active1 & 0x1000L) != 0L)
+ return jjStartNfaWithStates_10(1, 76, 51);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_10(0, active0, active1);
+}
+private int jjMoveStringLiteralDfa2_10(long old0, long active0, long old1, long active1)
+{
+ if (((active0 &= old0) | (active1 &= old1)) == 0L)
+ return jjStartNfa_10(0, old0, old1);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_10(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_10(1, active0, 0L);
+}
+private int jjStartNfaWithStates_10(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_10(state, pos + 1);
+}
+private int jjMoveNfa_10(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 51;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 39:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 45;
+ break;
+ case 18:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(31, 32);
+ else if (curChar == 35)
+ jjAddStates(161, 162);
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ }
+ break;
+ case 51:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(31, 32);
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ }
+ break;
+ case 1:
+ if ((0xffffffe7efffdbffL & l) != 0L)
+ {
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAddStates(163, 165);
+ }
+ else if ((0x2400L & l) != 0L)
+ {
+ if (kind > 33)
+ kind = 33;
+ }
+ else if (curChar == 35)
+ jjAddStates(166, 168);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(31, 32);
+ }
+ if ((0xffffffe6efffd9ffL & l) != 0L)
+ jjCheckNAddStates(169, 172);
+ else if ((0x100000200L & l) != 0L)
+ {
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(0);
+ }
+ else if (curChar == 36)
+ jjCheckNAddStates(173, 176);
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if ((0x100000200L & l) == 0L)
+ break;
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(0);
+ break;
+ case 2:
+ if (curChar == 10 && kind > 33)
+ kind = 33;
+ break;
+ case 3:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 4:
+ if ((0xffffffe6efffd9ffL & l) != 0L)
+ jjCheckNAddStates(169, 172);
+ break;
+ case 5:
+ if ((0xffffffe7efffdbffL & l) != 0L)
+ jjCheckNAddStates(177, 179);
+ break;
+ case 6:
+ if ((0x2400L & l) == 0L)
+ break;
+ if (kind > 78)
+ kind = 78;
+ jjCheckNAddStates(180, 182);
+ break;
+ case 7:
+ if ((0xffffffe7efffdbffL & l) != 0L)
+ jjCheckNAddStates(180, 182);
+ break;
+ case 8:
+ if (curChar != 10)
+ break;
+ if (kind > 78)
+ kind = 78;
+ jjCheckNAddStates(180, 182);
+ break;
+ case 9:
+ case 10:
+ if (curChar == 13)
+ jjCheckNAdd(8);
+ break;
+ case 11:
+ if (curChar == 36)
+ jjCheckNAddStates(173, 176);
+ break;
+ case 13:
+ case 14:
+ if (curChar == 33)
+ jjCheckNAdd(12);
+ break;
+ case 16:
+ if (curChar == 46 && kind > 80)
+ kind = 80;
+ break;
+ case 19:
+ if (curChar == 35)
+ jjAddStates(161, 162);
+ break;
+ case 21:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 17)
+ kind = 17;
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 24:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjAddStates(68, 69);
+ break;
+ case 28:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 30:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(31, 32);
+ break;
+ case 32:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 33:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(31, 32);
+ break;
+ case 34:
+ if (curChar == 35)
+ jjAddStates(166, 168);
+ break;
+ case 36:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(183, 184);
+ break;
+ case 37:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 45:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 46;
+ break;
+ case 46:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 48:
+ if ((0xffffffe7efffdbffL & l) == 0L)
+ break;
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAddStates(163, 165);
+ break;
+ case 49:
+ if ((0xffffffe7efffdbffL & l) != 0L)
+ jjCheckNAddTwoStates(49, 4);
+ break;
+ case 50:
+ if ((0xffffffe7efffdbffL & l) == 0L)
+ break;
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAdd(50);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 39:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 43;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 38;
+ break;
+ case 18:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(29, 30);
+ if (curChar == 92)
+ jjCheckNAddTwoStates(27, 28);
+ if (curChar == 92)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 51:
+ if (curChar == 92)
+ jjAddStates(185, 186);
+ if (curChar == 92)
+ jjCheckNAddTwoStates(29, 30);
+ if (curChar == 92)
+ jjCheckNAddTwoStates(27, 28);
+ break;
+ case 1:
+ if ((0xffffffffefffffffL & l) != 0L)
+ {
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAddStates(163, 165);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(187, 190);
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAddStates(169, 172);
+ else if (curChar == 92)
+ jjAddStates(185, 186);
+ break;
+ case 4:
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAddStates(169, 172);
+ break;
+ case 5:
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAddStates(177, 179);
+ break;
+ case 7:
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAddStates(180, 182);
+ break;
+ case 12:
+ if (curChar == 91 && kind > 80)
+ kind = 80;
+ break;
+ case 15:
+ if (curChar == 92)
+ jjstateSet[jjnewStateCnt++] = 14;
+ break;
+ case 17:
+ if (curChar == 92)
+ jjAddStates(185, 186);
+ break;
+ case 20:
+ if ((0x7fffffe87ffffffL & l) == 0L)
+ break;
+ if (kind > 17)
+ kind = 17;
+ jjCheckNAdd(21);
+ break;
+ case 21:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 17)
+ kind = 17;
+ jjCheckNAdd(21);
+ break;
+ case 22:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 23:
+ if ((0x7fffffe87ffffffL & l) != 0L)
+ jjCheckNAddTwoStates(24, 25);
+ break;
+ case 24:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ jjCheckNAddTwoStates(24, 25);
+ break;
+ case 25:
+ if (curChar == 125 && kind > 17)
+ kind = 17;
+ break;
+ case 26:
+ if (curChar == 92)
+ jjCheckNAddStates(187, 190);
+ break;
+ case 27:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(27, 28);
+ break;
+ case 29:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(29, 30);
+ break;
+ case 31:
+ if (curChar == 92)
+ jjAddStates(70, 71);
+ break;
+ case 35:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(36, 37);
+ break;
+ case 38:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 35;
+ break;
+ case 40:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(36, 37);
+ break;
+ case 41:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 40;
+ break;
+ case 42:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 41;
+ break;
+ case 43:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 42;
+ break;
+ case 44:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 43;
+ break;
+ case 46:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 48:
+ if ((0xffffffffefffffffL & l) == 0L)
+ break;
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAddStates(163, 165);
+ break;
+ case 49:
+ if ((0xffffffffefffffffL & l) != 0L)
+ jjCheckNAddTwoStates(49, 4);
+ break;
+ case 50:
+ if ((0xffffffffefffffffL & l) == 0L)
+ break;
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAdd(50);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjCheckNAddStates(169, 172);
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ {
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAddStates(163, 165);
+ }
+ break;
+ case 4:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjCheckNAddStates(169, 172);
+ break;
+ case 5:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjCheckNAddStates(177, 179);
+ break;
+ case 7:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjCheckNAddStates(180, 182);
+ break;
+ case 46:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ case 48:
+ if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+ break;
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAddStates(163, 165);
+ break;
+ case 49:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjCheckNAddTwoStates(49, 4);
+ break;
+ case 50:
+ if (!jjCanMove_0(hiByte, i1, i2, l1, l2))
+ break;
+ if (kind > 79)
+ kind = 79;
+ jjCheckNAdd(50);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 51 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_1(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 25;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 31;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_1(int pos, long active0, long active1)
+{
+ return jjMoveNfa_1(jjStopStringLiteralDfa_1(pos, active0, active1), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_1()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 1);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_1(0x2a00000L);
+ case 123:
+ return jjStopAtPos(0, 72);
+ case 125:
+ return jjStopAtPos(0, 73);
+ default :
+ return jjMoveNfa_1(4, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_1(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_1(0, active0, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_1(1, 23, 31);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_1(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_1(0, active0, 0L);
+}
+private int jjMoveStringLiteralDfa2_1(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_1(0, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_1(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_1(1, active0, 0L);
+}
+private int jjStartNfaWithStates_1(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_1(state, pos + 1);
+}
+private int jjMoveNfa_1(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 34;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 25:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 31;
+ break;
+ case 4:
+ if ((0x2400L & l) != 0L)
+ {
+ if (kind > 34)
+ kind = 34;
+ }
+ else if ((0x100000200L & l) != 0L)
+ jjCheckNAddStates(143, 145);
+ else if (curChar == 35)
+ jjAddStates(146, 148);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(17, 18);
+ }
+ if (curChar == 36)
+ jjCheckNAddStates(149, 152);
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if ((0x100000200L & l) != 0L)
+ jjCheckNAddStates(143, 145);
+ break;
+ case 1:
+ if ((0x2400L & l) != 0L && kind > 34)
+ kind = 34;
+ break;
+ case 2:
+ if (curChar == 10 && kind > 34)
+ kind = 34;
+ break;
+ case 3:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 5:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 67)
+ kind = 67;
+ jjstateSet[jjnewStateCnt++] = 5;
+ break;
+ case 6:
+ if (curChar == 36)
+ jjCheckNAddStates(149, 152);
+ break;
+ case 8:
+ case 9:
+ if (curChar == 33)
+ jjCheckNAdd(7);
+ break;
+ case 11:
+ if (curChar == 46 && kind > 80)
+ kind = 80;
+ break;
+ case 14:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 16:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(17, 18);
+ break;
+ case 18:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 19:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(17, 18);
+ break;
+ case 20:
+ if (curChar == 35)
+ jjAddStates(146, 148);
+ break;
+ case 22:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(153, 154);
+ break;
+ case 23:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 31:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 32;
+ break;
+ case 32:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 25:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 29;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 24;
+ break;
+ case 4:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ {
+ if (kind > 67)
+ kind = 67;
+ jjCheckNAdd(5);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(155, 158);
+ break;
+ case 5:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 67)
+ kind = 67;
+ jjCheckNAdd(5);
+ break;
+ case 7:
+ if (curChar == 91 && kind > 80)
+ kind = 80;
+ break;
+ case 10:
+ if (curChar == 92)
+ jjstateSet[jjnewStateCnt++] = 9;
+ break;
+ case 12:
+ if (curChar == 92)
+ jjCheckNAddStates(155, 158);
+ break;
+ case 13:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(13, 14);
+ break;
+ case 15:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(15, 16);
+ break;
+ case 17:
+ if (curChar == 92)
+ jjAddStates(159, 160);
+ break;
+ case 21:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(22, 23);
+ break;
+ case 24:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 26:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(22, 23);
+ break;
+ case 27:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 26;
+ break;
+ case 28:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 27;
+ break;
+ case 29:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 28;
+ break;
+ case 30:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 29;
+ break;
+ case 32:
+ if (kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 32:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 34 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_6(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3a00000L) != 0L)
+ return 15;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 21;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_6(int pos, long active0, long active1)
+{
+ return jjMoveNfa_6(jjStopStringLiteralDfa_6(pos, active0, active1), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_6()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_6(0x2a00000L);
+ case 91:
+ return jjStopAtPos(0, 3);
+ case 123:
+ return jjStopAtPos(0, 72);
+ case 124:
+ jjmatchedKind = 5;
+ return jjMoveStringLiteralDfa1_6(0x10L);
+ case 125:
+ return jjStopAtPos(0, 73);
+ default :
+ return jjMoveNfa_6(0, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_6(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_6(0, active0, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_6(1, 23, 21);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_6(active0, 0x200000L);
+ case 124:
+ if ((active0 & 0x10L) != 0L)
+ return jjStopAtPos(1, 4);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_6(0, active0, 0L);
+}
+private int jjMoveStringLiteralDfa2_6(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_6(0, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_6(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_6(1, active0, 0L);
+}
+private int jjStartNfaWithStates_6(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_6(state, pos + 1);
+}
+private int jjMoveNfa_6(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 24;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (curChar == 35)
+ jjAddStates(0, 2);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(7, 8);
+ }
+ else if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 15:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 4:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 6:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 8:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 9:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 10:
+ if (curChar == 35)
+ jjAddStates(0, 2);
+ break;
+ case 12:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(3, 4);
+ break;
+ case 13:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 21:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 22;
+ break;
+ case 22:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (curChar == 92)
+ jjCheckNAddStates(5, 8);
+ break;
+ case 15:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 19;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 14;
+ break;
+ case 1:
+ if ((0x7fffffe87fffffeL & l) != 0L && kind > 71)
+ kind = 71;
+ break;
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(3, 4);
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjAddStates(9, 10);
+ break;
+ case 11:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(12, 13);
+ break;
+ case 14:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 11;
+ break;
+ case 16:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(12, 13);
+ break;
+ case 17:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 18:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 19:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 20:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 22:
+ if (kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 22:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 24 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_9(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x4000000000L) != 0L)
+ return 96;
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ return 53;
+ }
+ if ((active0 & 0x10000000000000L) != 0L)
+ return 45;
+ if ((active0 & 0x400L) != 0L)
+ return 107;
+ if ((active0 & 0x3a00000L) != 0L)
+ return 67;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 73;
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ jjmatchedPos = 1;
+ return 53;
+ }
+ return -1;
+ case 2:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ jjmatchedPos = 2;
+ return 53;
+ }
+ return -1;
+ case 3:
+ if ((active0 & 0x1000000000L) != 0L)
+ return 53;
+ if ((active0 & 0x2000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ jjmatchedPos = 3;
+ return 53;
+ }
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_9(int pos, long active0)
+{
+ return jjMoveNfa_9(jjStopStringLiteralDfa_9(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_9()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_9(0x2a00000L);
+ case 37:
+ return jjStopAtPos(0, 42);
+ case 41:
+ return jjStopAtPos(0, 16);
+ case 42:
+ return jjStopAtPos(0, 40);
+ case 43:
+ return jjStopAtPos(0, 39);
+ case 44:
+ return jjStopAtPos(0, 9);
+ case 45:
+ return jjStartNfaWithStates_9(0, 38, 96);
+ case 46:
+ return jjMoveStringLiteralDfa1_9(0x400L);
+ case 47:
+ return jjStopAtPos(0, 41);
+ case 58:
+ return jjStopAtPos(0, 11);
+ case 61:
+ return jjStartNfaWithStates_9(0, 52, 45);
+ case 91:
+ return jjStopAtPos(0, 7);
+ case 93:
+ return jjStopAtPos(0, 8);
+ case 102:
+ return jjMoveStringLiteralDfa1_9(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_9(0x1000000000L);
+ case 123:
+ return jjStopAtPos(0, 12);
+ case 125:
+ return jjStopAtPos(0, 13);
+ default :
+ return jjMoveNfa_9(1, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_9(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_9(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_9(1, 23, 73);
+ break;
+ case 46:
+ if ((active0 & 0x400L) != 0L)
+ return jjStopAtPos(1, 10);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_9(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_9(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_9(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_9(0, active0);
+}
+private int jjMoveStringLiteralDfa2_9(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_9(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_9(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_9(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_9(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_9(1, active0);
+}
+private int jjMoveStringLiteralDfa3_9(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_9(1, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_9(2, active0);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStartNfaWithStates_9(3, 36, 53);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_9(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_9(2, active0);
+}
+private int jjMoveStringLiteralDfa4_9(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_9(2, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_9(3, active0);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStartNfaWithStates_9(4, 37, 53);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_9(3, active0);
+}
+private int jjStartNfaWithStates_9(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_9(state, pos + 1);
+}
+private int jjMoveNfa_9(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 108;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 96:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(101, 102);
+ else if (curChar == 46)
+ jjCheckNAdd(97);
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(90, 91);
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(87, 89);
+ }
+ break;
+ case 1:
+ if ((0x3ff000000000000L & l) != 0L)
+ {
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(191, 196);
+ }
+ else if ((0x2400L & l) != 0L)
+ {
+ if (kind > 33)
+ kind = 33;
+ }
+ else if ((0x100000200L & l) != 0L)
+ {
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(0);
+ }
+ else if (curChar == 46)
+ jjCheckNAddTwoStates(97, 107);
+ else if (curChar == 45)
+ jjCheckNAddStates(197, 200);
+ else if (curChar == 35)
+ jjAddStates(201, 203);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(59, 60);
+ }
+ else if (curChar == 33)
+ {
+ if (kind > 51)
+ kind = 51;
+ }
+ else if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 45;
+ else if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 43;
+ else if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 40;
+ else if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 30;
+ else if (curChar == 39)
+ jjCheckNAddStates(204, 207);
+ else if (curChar == 34)
+ jjCheckNAddStates(208, 211);
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 49;
+ else if (curChar == 62)
+ {
+ if (kind > 47)
+ kind = 47;
+ }
+ else if (curChar == 60)
+ {
+ if (kind > 45)
+ kind = 45;
+ }
+ else if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 107:
+ case 97:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(97, 98);
+ break;
+ case 67:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 73;
+ break;
+ case 0:
+ if ((0x100000200L & l) == 0L)
+ break;
+ if (kind > 32)
+ kind = 32;
+ jjCheckNAdd(0);
+ break;
+ case 2:
+ if (curChar == 10 && kind > 33)
+ kind = 33;
+ break;
+ case 3:
+ if (curChar == 13)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 4:
+ case 6:
+ if (curChar == 34)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 5:
+ if ((0xfffffffbefffffffL & l) != 0L)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 7:
+ if (curChar == 34)
+ jjstateSet[jjnewStateCnt++] = 6;
+ break;
+ case 8:
+ if (curChar == 34 && kind > 35)
+ kind = 35;
+ break;
+ case 11:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(212, 216);
+ break;
+ case 12:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 13:
+ if ((0xf000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 14;
+ break;
+ case 14:
+ if ((0xff000000000000L & l) != 0L)
+ jjCheckNAdd(12);
+ break;
+ case 16:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 17:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 19:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 20:
+ if (curChar == 32)
+ jjAddStates(217, 218);
+ break;
+ case 21:
+ if (curChar == 10)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 22:
+ case 24:
+ if (curChar == 39)
+ jjCheckNAddStates(204, 207);
+ break;
+ case 23:
+ if ((0xffffff7fefffffffL & l) != 0L)
+ jjCheckNAddStates(204, 207);
+ break;
+ case 25:
+ if (curChar == 39)
+ jjstateSet[jjnewStateCnt++] = 24;
+ break;
+ case 27:
+ if (curChar == 32)
+ jjAddStates(219, 220);
+ break;
+ case 28:
+ if (curChar == 10)
+ jjCheckNAddStates(204, 207);
+ break;
+ case 29:
+ if (curChar == 39 && kind > 35)
+ kind = 35;
+ break;
+ case 30:
+ if (curChar == 38 && kind > 43)
+ kind = 43;
+ break;
+ case 31:
+ if (curChar == 38)
+ jjstateSet[jjnewStateCnt++] = 30;
+ break;
+ case 39:
+ if (curChar == 60 && kind > 45)
+ kind = 45;
+ break;
+ case 40:
+ if (curChar == 61 && kind > 46)
+ kind = 46;
+ break;
+ case 41:
+ if (curChar == 60)
+ jjstateSet[jjnewStateCnt++] = 40;
+ break;
+ case 42:
+ if (curChar == 62 && kind > 47)
+ kind = 47;
+ break;
+ case 43:
+ if (curChar == 61 && kind > 48)
+ kind = 48;
+ break;
+ case 44:
+ if (curChar == 62)
+ jjstateSet[jjnewStateCnt++] = 43;
+ break;
+ case 45:
+ if (curChar == 61 && kind > 49)
+ kind = 49;
+ break;
+ case 46:
+ if (curChar == 61)
+ jjstateSet[jjnewStateCnt++] = 45;
+ break;
+ case 49:
+ if (curChar == 61 && kind > 50)
+ kind = 50;
+ break;
+ case 50:
+ if (curChar == 33)
+ jjstateSet[jjnewStateCnt++] = 49;
+ break;
+ case 51:
+ if (curChar == 33 && kind > 51)
+ kind = 51;
+ break;
+ case 53:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 67)
+ kind = 67;
+ jjstateSet[jjnewStateCnt++] = 53;
+ break;
+ case 56:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 58:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(59, 60);
+ break;
+ case 60:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 61:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(59, 60);
+ break;
+ case 62:
+ if (curChar == 35)
+ jjAddStates(201, 203);
+ break;
+ case 64:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(221, 222);
+ break;
+ case 65:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 73:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 74;
+ break;
+ case 74:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 86:
+ if (curChar == 45)
+ jjCheckNAddStates(197, 200);
+ break;
+ case 87:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddTwoStates(87, 89);
+ break;
+ case 88:
+ if (curChar == 46 && kind > 58)
+ kind = 58;
+ break;
+ case 89:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 88;
+ break;
+ case 90:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(90, 91);
+ break;
+ case 91:
+ if (curChar != 46)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(92, 93);
+ break;
+ case 92:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAddTwoStates(92, 93);
+ break;
+ case 94:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(95);
+ break;
+ case 95:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(95);
+ break;
+ case 99:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(100);
+ break;
+ case 100:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(100);
+ break;
+ case 101:
+ if ((0x3ff000000000000L & l) != 0L)
+ jjCheckNAddTwoStates(101, 102);
+ break;
+ case 103:
+ if ((0x280000000000L & l) != 0L)
+ jjCheckNAdd(104);
+ break;
+ case 104:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 59)
+ kind = 59;
+ jjCheckNAdd(104);
+ break;
+ case 105:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 58)
+ kind = 58;
+ jjCheckNAddStates(191, 196);
+ break;
+ case 106:
+ if (curChar == 46)
+ jjCheckNAddTwoStates(97, 107);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ {
+ if (kind > 67)
+ kind = 67;
+ jjCheckNAdd(53);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(223, 226);
+ else if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 35;
+ if (curChar == 110)
+ jjAddStates(227, 228);
+ else if (curChar == 103)
+ jjAddStates(229, 230);
+ else if (curChar == 108)
+ jjAddStates(231, 232);
+ else if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 47;
+ else if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 37;
+ else if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 33;
+ break;
+ case 107:
+ if ((0x7fffffe87fffffeL & l) != 0L && kind > 71)
+ kind = 71;
+ break;
+ case 67:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 71;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 66;
+ break;
+ case 5:
+ jjCheckNAddStates(208, 211);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(233, 238);
+ break;
+ case 10:
+ if ((0x14404400000000L & l) != 0L)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 15:
+ if (curChar == 117)
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 16:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 17;
+ break;
+ case 17:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 18:
+ if ((0x7e0000007eL & l) != 0L)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 19:
+ if ((0x7e0000007eL & l) != 0L)
+ jjCheckNAddStates(208, 211);
+ break;
+ case 23:
+ jjAddStates(204, 207);
+ break;
+ case 26:
+ if (curChar == 92)
+ jjAddStates(219, 220);
+ break;
+ case 32:
+ if (curChar == 100 && kind > 43)
+ kind = 43;
+ break;
+ case 33:
+ if (curChar == 110)
+ jjstateSet[jjnewStateCnt++] = 32;
+ break;
+ case 34:
+ if (curChar == 97)
+ jjstateSet[jjnewStateCnt++] = 33;
+ break;
+ case 35:
+ if (curChar == 124 && kind > 44)
+ kind = 44;
+ break;
+ case 36:
+ if (curChar == 124)
+ jjstateSet[jjnewStateCnt++] = 35;
+ break;
+ case 37:
+ if (curChar == 114 && kind > 44)
+ kind = 44;
+ break;
+ case 38:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 37;
+ break;
+ case 47:
+ if (curChar == 113 && kind > 49)
+ kind = 49;
+ break;
+ case 48:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 47;
+ break;
+ case 52:
+ case 53:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 67)
+ kind = 67;
+ jjCheckNAdd(53);
+ break;
+ case 54:
+ if (curChar == 92)
+ jjCheckNAddStates(223, 226);
+ break;
+ case 55:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(55, 56);
+ break;
+ case 57:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(57, 58);
+ break;
+ case 59:
+ if (curChar == 92)
+ jjAddStates(88, 89);
+ break;
+ case 63:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(64, 65);
+ break;
+ case 66:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 63;
+ break;
+ case 68:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(64, 65);
+ break;
+ case 69:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 68;
+ break;
+ case 70:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 69;
+ break;
+ case 71:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 70;
+ break;
+ case 72:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 71;
+ break;
+ case 74:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 76:
+ if (curChar == 108)
+ jjAddStates(231, 232);
+ break;
+ case 77:
+ if (curChar == 116 && kind > 45)
+ kind = 45;
+ break;
+ case 78:
+ if (curChar == 101 && kind > 46)
+ kind = 46;
+ break;
+ case 79:
+ if (curChar == 103)
+ jjAddStates(229, 230);
+ break;
+ case 80:
+ if (curChar == 116 && kind > 47)
+ kind = 47;
+ break;
+ case 81:
+ if (curChar == 101 && kind > 48)
+ kind = 48;
+ break;
+ case 82:
+ if (curChar == 110)
+ jjAddStates(227, 228);
+ break;
+ case 83:
+ if (curChar == 101 && kind > 50)
+ kind = 50;
+ break;
+ case 84:
+ if (curChar == 116 && kind > 51)
+ kind = 51;
+ break;
+ case 85:
+ if (curChar == 111)
+ jjstateSet[jjnewStateCnt++] = 84;
+ break;
+ case 93:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(239, 240);
+ break;
+ case 98:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(241, 242);
+ break;
+ case 102:
+ if ((0x2000000020L & l) != 0L)
+ jjAddStates(243, 244);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 5:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(208, 211);
+ break;
+ case 23:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2))
+ jjAddStates(204, 207);
+ break;
+ case 74:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 108 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_11(int pos, long active0)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x1a00000L) != 0L)
+ return 2;
+ return -1;
+ case 1:
+ if ((active0 & 0x800000L) != 0L)
+ return 0;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_11(int pos, long active0)
+{
+ return jjMoveNfa_11(jjStopStringLiteralDfa_11(pos, active0), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_11()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_11(0xa00000L);
+ default :
+ return jjMoveNfa_11(3, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_11(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_11(0, active0);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_11(1, 23, 0);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_11(active0, 0x200000L);
+ default :
+ break;
+ }
+ return jjStartNfa_11(0, active0);
+}
+private int jjMoveStringLiteralDfa2_11(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_11(0, old0);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_11(1, active0);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_11(1, active0);
+}
+private int jjStartNfaWithStates_11(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_11(state, pos + 1);
+}
+private int jjMoveNfa_11(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 12;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ }
+ else if (curChar == 35)
+ jjstateSet[jjnewStateCnt++] = 2;
+ break;
+ case 0:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 1:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ case 2:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 0;
+ break;
+ case 6:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 8:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 10:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 11:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 3:
+ if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 1:
+ if (kind > 22)
+ kind = 22;
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(115, 116);
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 1:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 12 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+private final int jjStopStringLiteralDfa_4(int pos, long active0, long active1)
+{
+ switch (pos)
+ {
+ case 0:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ return 1;
+ }
+ if ((active0 & 0x3a00000L) != 0L)
+ return 17;
+ return -1;
+ case 1:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ jjmatchedPos = 1;
+ return 1;
+ }
+ if ((active0 & 0x800000L) != 0L)
+ return 23;
+ return -1;
+ case 2:
+ if ((active0 & 0x3000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ jjmatchedPos = 2;
+ return 1;
+ }
+ return -1;
+ case 3:
+ if ((active0 & 0x2000000000L) != 0L)
+ {
+ jjmatchedKind = 67;
+ jjmatchedPos = 3;
+ return 1;
+ }
+ if ((active0 & 0x1000000000L) != 0L)
+ return 1;
+ return -1;
+ default :
+ return -1;
+ }
+}
+private final int jjStartNfa_4(int pos, long active0, long active1)
+{
+ return jjMoveNfa_4(jjStopStringLiteralDfa_4(pos, active0, active1), pos + 1);
+}
+private int jjMoveStringLiteralDfa0_4()
+{
+ switch(curChar)
+ {
+ case 28:
+ return jjStopAtPos(0, 2);
+ case 35:
+ jjmatchedKind = 24;
+ return jjMoveStringLiteralDfa1_4(0x2a00000L);
+ case 40:
+ return jjStopAtPos(0, 14);
+ case 91:
+ return jjStopAtPos(0, 3);
+ case 102:
+ return jjMoveStringLiteralDfa1_4(0x2000000000L);
+ case 116:
+ return jjMoveStringLiteralDfa1_4(0x1000000000L);
+ case 123:
+ return jjStopAtPos(0, 72);
+ case 124:
+ jjmatchedKind = 5;
+ return jjMoveStringLiteralDfa1_4(0x10L);
+ case 125:
+ return jjStopAtPos(0, 73);
+ default :
+ return jjMoveNfa_4(0, 0);
+ }
+}
+private int jjMoveStringLiteralDfa1_4(long active0)
+{
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_4(0, active0, 0L);
+ return 1;
+ }
+ switch(curChar)
+ {
+ case 35:
+ if ((active0 & 0x2000000L) != 0L)
+ return jjStopAtPos(1, 25);
+ break;
+ case 42:
+ if ((active0 & 0x800000L) != 0L)
+ return jjStartNfaWithStates_4(1, 23, 23);
+ break;
+ case 91:
+ return jjMoveStringLiteralDfa2_4(active0, 0x200000L);
+ case 97:
+ return jjMoveStringLiteralDfa2_4(active0, 0x2000000000L);
+ case 114:
+ return jjMoveStringLiteralDfa2_4(active0, 0x1000000000L);
+ case 124:
+ if ((active0 & 0x10L) != 0L)
+ return jjStopAtPos(1, 4);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_4(0, active0, 0L);
+}
+private int jjMoveStringLiteralDfa2_4(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_4(0, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_4(1, active0, 0L);
+ return 2;
+ }
+ switch(curChar)
+ {
+ case 91:
+ if ((active0 & 0x200000L) != 0L)
+ return jjStopAtPos(2, 21);
+ break;
+ case 108:
+ return jjMoveStringLiteralDfa3_4(active0, 0x2000000000L);
+ case 117:
+ return jjMoveStringLiteralDfa3_4(active0, 0x1000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_4(1, active0, 0L);
+}
+private int jjMoveStringLiteralDfa3_4(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_4(1, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_4(2, active0, 0L);
+ return 3;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x1000000000L) != 0L)
+ return jjStartNfaWithStates_4(3, 36, 1);
+ break;
+ case 115:
+ return jjMoveStringLiteralDfa4_4(active0, 0x2000000000L);
+ default :
+ break;
+ }
+ return jjStartNfa_4(2, active0, 0L);
+}
+private int jjMoveStringLiteralDfa4_4(long old0, long active0)
+{
+ if (((active0 &= old0)) == 0L)
+ return jjStartNfa_4(2, old0, 0L);
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) {
+ jjStopStringLiteralDfa_4(3, active0, 0L);
+ return 4;
+ }
+ switch(curChar)
+ {
+ case 101:
+ if ((active0 & 0x2000000000L) != 0L)
+ return jjStartNfaWithStates_4(4, 37, 1);
+ break;
+ default :
+ break;
+ }
+ return jjStartNfa_4(3, active0, 0L);
+}
+private int jjStartNfaWithStates_4(int pos, int kind, int state)
+{
+ jjmatchedKind = kind;
+ jjmatchedPos = pos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return pos + 1; }
+ return jjMoveNfa_4(state, pos + 1);
+}
+private int jjMoveNfa_4(int startState, int curPos)
+{
+ int startsAt = 0;
+ jjnewStateCnt = 26;
+ int i = 1;
+ jjstateSet[0] = startState;
+ int kind = 0x7fffffff;
+ for (;;)
+ {
+ if (++jjround == 0x7fffffff)
+ ReInitRounds();
+ if (curChar < 64)
+ {
+ long l = 1L << curChar;
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if (curChar == 35)
+ jjAddStates(106, 108);
+ else if (curChar == 36)
+ {
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ }
+ else if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 3;
+ break;
+ case 17:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 23;
+ break;
+ case 1:
+ if ((0x3ff000000000000L & l) == 0L)
+ break;
+ if (kind > 67)
+ kind = 67;
+ jjstateSet[jjnewStateCnt++] = 1;
+ break;
+ case 2:
+ if (curChar == 46)
+ jjstateSet[jjnewStateCnt++] = 3;
+ break;
+ case 6:
+ if (curChar == 36 && kind > 19)
+ kind = 19;
+ break;
+ case 8:
+ if (curChar == 36)
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 10:
+ if (curChar == 33 && kind > 20)
+ kind = 20;
+ break;
+ case 11:
+ if (curChar != 36)
+ break;
+ if (kind > 19)
+ kind = 19;
+ jjCheckNAddTwoStates(9, 10);
+ break;
+ case 12:
+ if (curChar == 35)
+ jjAddStates(106, 108);
+ break;
+ case 14:
+ if ((0x100000200L & l) != 0L)
+ jjAddStates(109, 110);
+ break;
+ case 15:
+ if (curChar == 40 && kind > 18)
+ kind = 18;
+ break;
+ case 23:
+ if (curChar == 42)
+ jjstateSet[jjnewStateCnt++] = 24;
+ break;
+ case 24:
+ if ((0xfffffff7efffffffL & l) != 0L && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else if (curChar < 128)
+ {
+ long l = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 0:
+ if ((0x7fffffe87fffffeL & l) != 0L)
+ {
+ if (kind > 67)
+ kind = 67;
+ jjCheckNAdd(1);
+ }
+ else if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 17:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 21;
+ else if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 16;
+ break;
+ case 1:
+ if ((0x7fffffe87fffffeL & l) == 0L)
+ break;
+ if (kind > 67)
+ kind = 67;
+ jjCheckNAdd(1);
+ break;
+ case 3:
+ if ((0x7fffffe87fffffeL & l) != 0L && kind > 71)
+ kind = 71;
+ break;
+ case 4:
+ if (curChar == 92)
+ jjCheckNAddStates(111, 114);
+ break;
+ case 5:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(5, 6);
+ break;
+ case 7:
+ if (curChar == 92)
+ jjCheckNAddTwoStates(7, 8);
+ break;
+ case 9:
+ if (curChar == 92)
+ jjAddStates(115, 116);
+ break;
+ case 13:
+ if (curChar == 116)
+ jjCheckNAddTwoStates(14, 15);
+ break;
+ case 16:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 13;
+ break;
+ case 18:
+ if (curChar == 125)
+ jjCheckNAddTwoStates(14, 15);
+ break;
+ case 19:
+ if (curChar == 116)
+ jjstateSet[jjnewStateCnt++] = 18;
+ break;
+ case 20:
+ if (curChar == 101)
+ jjstateSet[jjnewStateCnt++] = 19;
+ break;
+ case 21:
+ if (curChar == 115)
+ jjstateSet[jjnewStateCnt++] = 20;
+ break;
+ case 22:
+ if (curChar == 123)
+ jjstateSet[jjnewStateCnt++] = 21;
+ break;
+ case 24:
+ if (kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ else
+ {
+ int hiByte = (int)(curChar >> 8);
+ int i1 = hiByte >> 6;
+ long l1 = 1L << (hiByte & 077);
+ int i2 = (curChar & 0xff) >> 6;
+ long l2 = 1L << (curChar & 077);
+ do
+ {
+ switch(jjstateSet[--i])
+ {
+ case 24:
+ if (jjCanMove_0(hiByte, i1, i2, l1, l2) && kind > 22)
+ kind = 22;
+ break;
+ default : break;
+ }
+ } while(i != startsAt);
+ }
+ if (kind != 0x7fffffff)
+ {
+ jjmatchedKind = kind;
+ jjmatchedPos = curPos;
+ kind = 0x7fffffff;
+ }
+ ++curPos;
+ if ((i = jjnewStateCnt) == (startsAt = 26 - (jjnewStateCnt = startsAt)))
+ return curPos;
+ try { curChar = input_stream.readChar(); }
+ catch(java.io.IOException e) { return curPos; }
+ }
+}
+static final int[] jjnextStates = {
+ 15, 20, 23, 12, 13, 3, 4, 5, 6, 7, 8, 61, 63, 64, 65, 70,
+ 71, 4, 5, 7, 61, 64, 10, 70, 19, 20, 44, 47, 54, 59, 22, 23,
+ 24, 25, 31, 36, 39, 13, 14, 26, 27, 68, 69, 72, 73, 80, 82, 83,
+ 84, 89, 90, 80, 83, 56, 89, 27, 29, 30, 33, 9, 11, 12, 13, 9,
+ 16, 11, 12, 13, 24, 25, 31, 32, 76, 78, 73, 74, 70, 71, 62, 63,
+ 64, 65, 14, 15, 17, 19, 24, 25, 59, 60, 66, 67, 87, 88, 91, 92,
+ 6, 7, 8, 9, 10, 11, 8, 9, 10, 11, 17, 22, 25, 14, 15, 5,
+ 6, 7, 8, 9, 10, 86, 88, 89, 90, 95, 96, 86, 89, 56, 95, 65,
+ 66, 68, 69, 70, 71, 82, 84, 79, 80, 76, 77, 93, 94, 97, 98, 0,
+ 1, 3, 25, 30, 33, 7, 8, 10, 11, 22, 23, 13, 14, 15, 16, 17,
+ 18, 20, 22, 49, 4, 50, 39, 44, 47, 4, 5, 6, 10, 12, 13, 15,
+ 16, 5, 6, 10, 7, 6, 9, 36, 37, 18, 19, 27, 28, 29, 30, 87,
+ 89, 90, 91, 101, 102, 87, 90, 96, 101, 67, 72, 75, 23, 25, 26, 29,
+ 5, 7, 8, 9, 5, 12, 7, 8, 9, 20, 21, 27, 28, 64, 65, 55,
+ 56, 57, 58, 83, 85, 80, 81, 77, 78, 10, 11, 13, 15, 20, 21, 94,
+ 95, 99, 100, 103, 104,
+};
+private static final boolean jjCanMove_0(int hiByte, int i1, int i2, long l1, long l2)
+{
+ switch(hiByte)
+ {
+ case 0:
+ return ((jjbitVec2[i2] & l2) != 0L);
+ default :
+ if ((jjbitVec0[i1] & l1) != 0L)
+ return true;
+ return false;
+ }
+}
+
+/** Token literal values. */
+public static final String[] jjstrLiteralImages = {
+null, null, null, null, null, null, null, null, null, null, null, null, null,
+null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+null, null, null, null, null, null, null, null, null, null, null, null, };
+
+/** Lexer state names. */
+public static final String[] lexStateNames = {
+ "PRE_DIRECTIVE",
+ "PRE_REFERENCE",
+ "PRE_OLD_REFERENCE",
+ "REFERENCE",
+ "REFMODIFIER",
+ "OLD_REFMODIFIER",
+ "REFMOD3",
+ "REFINDEX",
+ "DIRECTIVE",
+ "REFMOD2",
+ "DEFAULT",
+ "REFMOD",
+ "IN_TEXTBLOCK",
+ "IN_MULTILINE_COMMENT",
+ "IN_FORMAL_COMMENT",
+ "IN_SINGLE_LINE_COMMENT",
+ "ALT_VAL",
+ "IN_MULTI_LINE_COMMENT",
+};
+
+/** Lex State array. */
+public static final int[] jjnewLexState = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1,
+};
+static final long[] jjtoToken = {
+ 0x8dffffff3e07ffffL, 0x1f3c9L,
+};
+static final long[] jjtoSkip = {
+ 0x40000000L, 0xc00L,
+};
+static final long[] jjtoSpecial = {
+ 0x0L, 0xc00L,
+};
+static final long[] jjtoMore = {
+ 0x81f80000L, 0x0L,
+};
+protected CharStream input_stream;
+private final int[] jjrounds = new int[108];
+private final int[] jjstateSet = new int[216];
+private final StringBuilder jjimage = new StringBuilder();
+private StringBuilder image = jjimage;
+private int jjimageLen;
+private int lengthOfMatch;
+protected char curChar;
+
+/** Constructor with parser. */
+public StandardParserTokenManager(StandardParser parserArg, CharStream stream){
+ parser = parserArg;
+ input_stream = stream;
+}
+
+/** Constructor with parser. */
+public StandardParserTokenManager(StandardParser parserArg, CharStream stream, int lexState){
+ this(parserArg, stream);
+ SwitchTo(lexState);
+}
+
+/** Reinitialise parser. */
+public void ReInit(CharStream stream)
+{
+ jjmatchedPos = jjnewStateCnt = 0;
+ curLexState = defaultLexState;
+ input_stream = stream;
+ ReInitRounds();
+}
+private void ReInitRounds()
+{
+ int i;
+ jjround = 0x80000001;
+ for (i = 108; i-- > 0;)
+ jjrounds[i] = 0x80000000;
+}
+
+/** Reinitialise parser. */
+public void ReInit(CharStream stream, int lexState)
+{
+ ReInit(stream);
+ SwitchTo(lexState);
+}
+
+/** Switch to specified lex state. */
+public void SwitchTo(int lexState)
+{
+ if (lexState >= 18 || lexState < 0)
+ throw new TokenMgrError("Error: Ignoring invalid lexical state : " + lexState + ". State unchanged.", TokenMgrError.INVALID_LEXICAL_STATE);
+ else
+ curLexState = lexState;
+}
+
+protected Token jjFillToken()
+{
+ final Token t;
+ final String curTokenImage;
+ final int beginLine;
+ final int endLine;
+ final int beginColumn;
+ final int endColumn;
+ String im = jjstrLiteralImages[jjmatchedKind];
+ curTokenImage = (im == null) ? input_stream.GetImage() : im;
+ beginLine = input_stream.getBeginLine();
+ beginColumn = input_stream.getBeginColumn();
+ endLine = input_stream.getEndLine();
+ endColumn = input_stream.getEndColumn();
+ t = Token.newToken(jjmatchedKind, curTokenImage);
+
+ t.beginLine = beginLine;
+ t.endLine = endLine;
+ t.beginColumn = beginColumn;
+ t.endColumn = endColumn;
+
+ return t;
+}
+
+int curLexState = 10;
+int defaultLexState = 10;
+int jjnewStateCnt;
+int jjround;
+int jjmatchedPos;
+int jjmatchedKind;
+
+/** Get the next Token. */
+public Token getNextToken()
+{
+ Token specialToken = null;
+ Token matchedToken;
+ int curPos = 0;
+
+ EOFLoop :
+ for (;;)
+ {
+ try
+ {
+ curChar = input_stream.BeginToken();
+ }
+ catch(java.io.IOException e)
+ {
+ jjmatchedKind = 0;
+ matchedToken = jjFillToken();
+ matchedToken.specialToken = specialToken;
+ return matchedToken;
+ }
+ image = jjimage;
+ image.setLength(0);
+ jjimageLen = 0;
+
+ for (;;)
+ {
+ switch(curLexState)
+ {
+ case 0:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_0();
+ if (jjmatchedPos == 0 && jjmatchedKind > 75)
+ {
+ jjmatchedKind = 75;
+ }
+ break;
+ case 1:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_1();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 2:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_2();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 3:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_3();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 4:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_4();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 5:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_5();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 6:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_6();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 7:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_7();
+ break;
+ case 8:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_8();
+ break;
+ case 9:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_9();
+ break;
+ case 10:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_10();
+ break;
+ case 11:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_11();
+ if (jjmatchedPos == 0 && jjmatchedKind > 74)
+ {
+ jjmatchedKind = 74;
+ }
+ break;
+ case 12:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_12();
+ break;
+ case 13:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_13();
+ break;
+ case 14:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_14();
+ if (jjmatchedPos == 0 && jjmatchedKind > 30)
+ {
+ jjmatchedKind = 30;
+ }
+ break;
+ case 15:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_15();
+ if (jjmatchedPos == 0 && jjmatchedKind > 30)
+ {
+ jjmatchedKind = 30;
+ }
+ break;
+ case 16:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_16();
+ break;
+ case 17:
+ jjmatchedKind = 0x7fffffff;
+ jjmatchedPos = 0;
+ curPos = jjMoveStringLiteralDfa0_17();
+ if (jjmatchedPos == 0 && jjmatchedKind > 30)
+ {
+ jjmatchedKind = 30;
+ }
+ break;
+ }
+ if (jjmatchedKind != 0x7fffffff)
+ {
+ if (jjmatchedPos + 1 < curPos)
+ input_stream.backup(curPos - jjmatchedPos - 1);
+ if ((jjtoToken[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+ {
+ matchedToken = jjFillToken();
+ matchedToken.specialToken = specialToken;
+ TokenLexicalActions(matchedToken);
+ if (jjnewLexState[jjmatchedKind] != -1)
+ curLexState = jjnewLexState[jjmatchedKind];
+ return matchedToken;
+ }
+ else if ((jjtoSkip[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+ {
+ if ((jjtoSpecial[jjmatchedKind >> 6] & (1L << (jjmatchedKind & 077))) != 0L)
+ {
+ matchedToken = jjFillToken();
+ if (specialToken == null)
+ specialToken = matchedToken;
+ else
+ {
+ matchedToken.specialToken = specialToken;
+ specialToken = (specialToken.next = matchedToken);
+ }
+ SkipLexicalActions(matchedToken);
+ }
+ else
+ SkipLexicalActions(null);
+ if (jjnewLexState[jjmatchedKind] != -1)
+ curLexState = jjnewLexState[jjmatchedKind];
+ continue EOFLoop;
+ }
+ MoreLexicalActions();
+ if (jjnewLexState[jjmatchedKind] != -1)
+ curLexState = jjnewLexState[jjmatchedKind];
+ curPos = 0;
+ jjmatchedKind = 0x7fffffff;
+ try {
+ curChar = input_stream.readChar();
+ continue;
+ }
+ catch (java.io.IOException e1) { }
+ }
+ int error_line = input_stream.getEndLine();
+ int error_column = input_stream.getEndColumn();
+ String error_after = null;
+ boolean EOFSeen = false;
+ try { input_stream.readChar(); input_stream.backup(1); }
+ catch (java.io.IOException e1) {
+ EOFSeen = true;
+ error_after = curPos <= 1 ? "" : input_stream.GetImage();
+ if (curChar == '\n' || curChar == '\r') {
+ error_line++;
+ error_column = 0;
+ }
+ else
+ error_column++;
+ }
+ if (!EOFSeen) {
+ input_stream.backup(1);
+ error_after = curPos <= 1 ? "" : input_stream.GetImage();
+ }
+ throw new TokenMgrError(EOFSeen, curLexState, error_line, error_column, error_after, curChar, TokenMgrError.LEXICAL_ERROR);
+ }
+ }
+}
+
+void SkipLexicalActions(Token matchedToken)
+{
+ switch(jjmatchedKind)
+ {
+ case 74 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /*
+ * push every terminator character back into the stream
+ */
+
+ input_stream.backup(1);
+
+ trace("REF_TERM :");
+
+ stateStackPop();
+ break;
+ case 75 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ trace("DIRECTIVE_TERM :");
+
+ input_stream.backup(1);
+ stateStackPop();
+ break;
+ default :
+ break;
+ }
+}
+void MoreLexicalActions()
+{
+ jjimageLen += (lengthOfMatch = jjmatchedPos + 1);
+ switch(jjmatchedKind)
+ {
+ case 19 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ if (! inComment)
+ {
+ /*
+ * if we find ourselves in REFERENCE or PRE_REFERENCE, we need to pop down
+ * to end the previous ref
+ */
+
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE)
+ {
+ stateStackPop();
+ }
+
+ int preReferenceState = parser.hyphenAllowedInIdentifiers ? PRE_OLD_REFERENCE : PRE_REFERENCE;
+
+ trace( " $ : going to " + lexStateNames[preReferenceState]);
+
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo(preReferenceState);
+ }
+ break;
+ case 20 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ if (! inComment)
+ {
+ /*
+ * if we find ourselves in REFERENCE or PRE_REFERENCE, we need to pop down
+ * to end the previous ref
+ */
+
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE)
+ {
+ stateStackPop();
+ }
+
+ int preReferenceState = parser.hyphenAllowedInIdentifiers ? PRE_OLD_REFERENCE : PRE_REFERENCE;
+
+ trace( " $ : going to " + lexStateNames[preReferenceState]);
+
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo(preReferenceState);
+ }
+ break;
+ case 21 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ if (!inComment)
+ {
+ inComment = true;
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo( IN_TEXTBLOCK );
+ }
+ break;
+ case 22 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ if (!inComment)
+ {
+ input_stream.backup(1);
+ inComment = true;
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo( IN_FORMAL_COMMENT);
+ }
+ break;
+ case 23 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ if (!inComment)
+ {
+ inComment=true;
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo( IN_MULTI_LINE_COMMENT );
+ }
+ break;
+ case 24 :
+ image.append(input_stream.GetSuffix(jjimageLen));
+ jjimageLen = 0;
+ if (! inComment)
+ {
+ /*
+ * We can have the situation where #if($foo)$foo#end.
+ * We need to transition out of REFERENCE before going to DIRECTIVE.
+ * I don't really like this, but I can't think of a legal way
+ * you are going into DIRECTIVE while in REFERENCE. -gmj
+ */
+
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE || curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER )
+ {
+ stateStackPop();
+ }
+
+ trace(" # : going to PRE_DIRECTIVE" );
+
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo(PRE_DIRECTIVE);
+ }
+ break;
+ default :
+ break;
+ }
+}
+void TokenLexicalActions(Token matchedToken)
+{
+ switch(jjmatchedKind)
+ {
+ case 1 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPop();
+ break;
+ case 3 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPush();
+ switchTo(REFINDEX);
+ break;
+ case 4 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPop();
+ break;
+ case 5 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ if (curlyLevel == 1)
+ {
+ switchTo(ALT_VAL);
+ }
+ else
+ {
+ stateStackPop();
+ }
+ break;
+ case 6 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPop();
+ break;
+ case 12 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ ++curlyLevel;
+ break;
+ case 13 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ --curlyLevel;
+ if (curLexState == ALT_VAL && curlyLevel == 0)
+ {
+ stateStackPop();
+ }
+ break;
+ case 14 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ if (!inComment)
+ lparen++;
+
+ /*
+ * If in REFERENCE and we have seen the dot, then move
+ * to REFMOD2 -> Modifier()
+ */
+
+ if (curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER )
+ switchTo( REFMOD2 );
+ break;
+ case 15 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ RPARENHandler();
+ break;
+ case 16 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /*
+ * need to simply switch back to REFERENCE, not drop down the stack
+ * because we can (infinitely) chain, ala
+ * $foo.bar().blargh().woogie().doogie()
+ */
+
+ switchTo( REFMOD3 );
+ break;
+ case 18 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ if (! inComment)
+ {
+ trace(" #set : going to DIRECTIVE" );
+
+ stateStackPush();
+ setInSet(true);
+ switchTo(DIRECTIVE);
+ }
+
+ /*
+ * need the LPAREN action
+ */
+
+ if (!inComment)
+ {
+ lparen++;
+
+ /*
+ * If in REFERENCE and we have seen the dot, then move
+ * to REFMOD2 -> Modifier()
+ */
+
+ if (curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER )
+ switchTo( REFMOD2 );
+ }
+ break;
+ case 25 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ if (!inComment)
+ {
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE)
+ {
+ stateStackPop();
+ }
+
+ inComment = true;
+ stateStackPush();
+ switchTo(IN_SINGLE_LINE_COMMENT);
+ }
+ break;
+ case 26 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ inComment = false;
+ stateStackPop();
+ if (curLexState == REFERENCE || curLexState == REFMOD3)
+ {
+ // end of reference: pop again
+ stateStackPop();
+ }
+ break;
+ case 27 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ inComment = false;
+ stateStackPop();
+ if (curLexState == REFERENCE || curLexState == REFMOD3)
+ {
+ // end of reference: pop again
+ stateStackPop();
+ }
+ break;
+ case 28 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ inComment = false;
+ stateStackPop();
+ if (curLexState == REFERENCE || curLexState == REFMOD3)
+ {
+ // end of reference: pop again
+ stateStackPop();
+ }
+ break;
+ case 29 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ inComment = false;
+ stateStackPop();
+ break;
+ case 33 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ trace(" NEWLINE :");
+
+ /* if (isInSet()) */
+ setInSet(false);
+ break;
+ case 34 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPop();
+ break;
+ case 35 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /*
+ * - if we are in DIRECTIVE and haven't seen ( yet, then also drop out.
+ * don't forget to account for the beloved yet wierd #set
+ * - finally, if we are in REFMOD2 (remember : $foo.bar( ) then " is ok!
+ */
+
+ if( curLexState == DIRECTIVE && !isInSet() && lparen == 0)
+ stateStackPop();
+ break;
+ case 53 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPop();
+ break;
+ case 54 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ switchTo(DIRECTIVE);
+ break;
+ case 55 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ switchTo(DIRECTIVE);
+ break;
+ case 56 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ stateStackPop();
+ break;
+ case 58 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /*
+ * Remove the double period if it is there
+ */
+ if (matchedToken.image.endsWith("..")) {
+ input_stream.backup(2);
+ matchedToken.image = matchedToken.image.substring(0,matchedToken.image.length()-2);
+ }
+
+ /*
+ * check to see if we are in set
+ * ex. #set($foo = $foo + 3)
+ * because we want to handle the \n after
+ */
+
+ if ( lparen == 0 && !isInSet() && curLexState != REFMOD2 && curLexState != REFINDEX && curLexState != ALT_VAL)
+ {
+ stateStackPop();
+ }
+ break;
+ case 59 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /*
+ * check to see if we are in set
+ * ex. #set $foo = $foo + 3
+ * because we want to handle the \n after
+ */
+
+ if ( lparen == 0 && !isInSet() && curLexState != REFMOD2 && curLexState != ALT_VAL)
+ {
+ stateStackPop();
+ }
+ break;
+ case 67 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ if (curLexState == PRE_REFERENCE)
+ {
+ switchTo(REFERENCE);
+ }
+ break;
+ case 70 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ if (curLexState == PRE_OLD_REFERENCE)
+ {
+ switchTo(REFERENCE);
+ }
+ break;
+ case 71 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /*
+ * push the alpha char back into the stream so the following identifier
+ * is complete
+ */
+
+ input_stream.backup(1);
+
+ /*
+ * and munge the <DOT> so we just get a . when we have normal text that
+ * looks like a ref.ident
+ */
+
+ matchedToken.image = ".";
+
+ int refModifierState = parser.hyphenAllowedInIdentifiers ? OLD_REFMODIFIER : REFMODIFIER;
+
+ trace("DOT : switching to " + lexStateNames[refModifierState]);
+ switchTo(refModifierState);
+ break;
+ case 72 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ ++curlyLevel;
+ break;
+ case 73 :
+ image.append(input_stream.GetSuffix(jjimageLen + (lengthOfMatch = jjmatchedPos + 1)));
+ /* maybe it wasn't for our state */
+ while (curlyLevel == 0 && curLexState != DEFAULT)
+ {
+ stateStackPop();
+ }
+ /* At this point, here are all the possible states:
+ * - DEFAULT, which means the '}' is schmoo
+ * - DIRECTIVE or REFMOD2, which means the '}' is a closing map curly
+ * - one of the other REFERENCE states or ALT_VAL, which means the '}' ends the reference
+ * If we're in the last case, pop up state.
+ */
+ if (curLexState != DEFAULT && curLexState != DIRECTIVE && curLexState != REFMOD2)
+ {
+ stateStackPop();
+ }
+ break;
+ default :
+ break;
+ }
+}
+private void jjCheckNAdd(int state)
+{
+ if (jjrounds[state] != jjround)
+ {
+ jjstateSet[jjnewStateCnt++] = state;
+ jjrounds[state] = jjround;
+ }
+}
+private void jjAddStates(int start, int end)
+{
+ do {
+ jjstateSet[jjnewStateCnt++] = jjnextStates[start];
+ } while (start++ != end);
+}
+private void jjCheckNAddTwoStates(int state1, int state2)
+{
+ jjCheckNAdd(state1);
+ jjCheckNAdd(state2);
+}
+
+private void jjCheckNAddStates(int start, int end)
+{
+ do {
+ jjCheckNAdd(jjnextStates[start]);
+ } while (start++ != end);
+}
+
+}
diff --git a/generated-sources/javacc/org/apache/velocity/runtime/parser/Token.java b/generated-sources/javacc/org/apache/velocity/runtime/parser/Token.java
new file mode 100644
index 00000000..066bbbbd
--- /dev/null
+++ b/generated-sources/javacc/org/apache/velocity/runtime/parser/Token.java
@@ -0,0 +1,131 @@
+/* Generated By:JavaCC: Do not edit this line. Token.java Version 5.0 */
+/* JavaCCOptions:TOKEN_EXTENDS=,KEEP_LINE_COL=null,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
+package org.apache.velocity.runtime.parser;
+
+/**
+ * Describes the input token stream.
+ */
+
+public class Token implements java.io.Serializable {
+
+ /**
+ * The version identifier for this Serializable class.
+ * Increment only if the <i>serialized</i> form of the
+ * class changes.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * An integer that describes the kind of this token. This numbering
+ * system is determined by JavaCCParser, and a table of these numbers is
+ * stored in the file ...Constants.java.
+ */
+ public int kind;
+
+ /** The line number of the first character of this Token. */
+ public int beginLine;
+ /** The column number of the first character of this Token. */
+ public int beginColumn;
+ /** The line number of the last character of this Token. */
+ public int endLine;
+ /** The column number of the last character of this Token. */
+ public int endColumn;
+
+ /**
+ * The string image of the token.
+ */
+ public String image;
+
+ /**
+ * A reference to the next regular (non-special) token from the input
+ * stream. If this is the last token from the input stream, or if the
+ * token manager has not read tokens beyond this one, this field is
+ * set to null. This is true only if this token is also a regular
+ * token. Otherwise, see below for a description of the contents of
+ * this field.
+ */
+ public Token next;
+
+ /**
+ * This field is used to access special tokens that occur prior to this
+ * token, but after the immediately preceding regular (non-special) token.
+ * If there are no such special tokens, this field is set to null.
+ * When there are more than one such special token, this field refers
+ * to the last of these special tokens, which in turn refers to the next
+ * previous special token through its specialToken field, and so on
+ * until the first special token (whose specialToken field is null).
+ * The next fields of special tokens refer to other special tokens that
+ * immediately follow it (without an intervening regular token). If there
+ * is no such token, this field is null.
+ */
+ public Token specialToken;
+
+ /**
+ * An optional attribute value of the Token.
+ * Tokens which are not used as syntactic sugar will often contain
+ * meaningful values that will be used later on by the compiler or
+ * interpreter. This attribute value is often different from the image.
+ * Any subclass of Token that actually wants to return a non-null value can
+ * override this method as appropriate.
+ */
+ public Object getValue() {
+ return null;
+ }
+
+ /**
+ * No-argument constructor
+ */
+ public Token() {}
+
+ /**
+ * Constructs a new token for the specified Image.
+ */
+ public Token(int kind)
+ {
+ this(kind, null);
+ }
+
+ /**
+ * Constructs a new token for the specified Image and Kind.
+ */
+ public Token(int kind, String image)
+ {
+ this.kind = kind;
+ this.image = image;
+ }
+
+ /**
+ * Returns the image.
+ */
+ public String toString()
+ {
+ return image;
+ }
+
+ /**
+ * Returns a new Token object, by default. However, if you want, you
+ * can create and return subclass objects based on the value of ofKind.
+ * Simply add the cases to the switch for all those special cases.
+ * For example, if you have a subclass of Token called IDToken that
+ * you want to create if ofKind is ID, simply add something like :
+ *
+ * case MyParserConstants.ID : return new IDToken(ofKind, image);
+ *
+ * to the following switch statement. Then you can cast matchedToken
+ * variable to the appropriate type and use sit in your lexical actions.
+ */
+ public static Token newToken(int ofKind, String image)
+ {
+ switch(ofKind)
+ {
+ default : return new Token(ofKind, image);
+ }
+ }
+
+ public static Token newToken(int ofKind)
+ {
+ return newToken(ofKind, null);
+ }
+
+}
+/* JavaCC - OriginalChecksum=0a7b7b090d8309460a5e68cffc5e2260 (do not edit this line) */
diff --git a/generated-sources/javacc/org/apache/velocity/runtime/parser/TokenMgrError.java b/generated-sources/javacc/org/apache/velocity/runtime/parser/TokenMgrError.java
new file mode 100644
index 00000000..f4812925
--- /dev/null
+++ b/generated-sources/javacc/org/apache/velocity/runtime/parser/TokenMgrError.java
@@ -0,0 +1,147 @@
+/* Generated By:JavaCC: Do not edit this line. TokenMgrError.java Version 5.0 */
+/* JavaCCOptions: */
+package org.apache.velocity.runtime.parser;
+
+/** Token Manager Error. */
+public class TokenMgrError extends Error
+{
+
+ /**
+ * The version identifier for this Serializable class.
+ * Increment only if the <i>serialized</i> form of the
+ * class changes.
+ */
+ private static final long serialVersionUID = 1L;
+
+ /*
+ * Ordinals for various reasons why an Error of this type can be thrown.
+ */
+
+ /**
+ * Lexical error occurred.
+ */
+ public static final int LEXICAL_ERROR = 0;
+
+ /**
+ * An attempt was made to create a second instance of a static token manager.
+ */
+ public static final int STATIC_LEXER_ERROR = 1;
+
+ /**
+ * Tried to change to an invalid lexical state.
+ */
+ public static final int INVALID_LEXICAL_STATE = 2;
+
+ /**
+ * Detected (and bailed out of) an infinite loop in the token manager.
+ */
+ public static final int LOOP_DETECTED = 3;
+
+ /**
+ * Indicates the reason why the exception is thrown. It will have
+ * one of the above 4 values.
+ */
+ int errorCode;
+
+ /**
+ * Replaces unprintable characters by their escaped (or unicode escaped)
+ * equivalents in the given string
+ */
+ protected static final String addEscapes(String str) {
+ StringBuffer retval = new StringBuffer();
+ 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" + s.substring(s.length() - 4, s.length()));
+ } else {
+ retval.append(ch);
+ }
+ continue;
+ }
+ }
+ return retval.toString();
+ }
+
+ /**
+ * Returns a detailed message for the Error when it is thrown by the
+ * token manager to indicate a lexical error.
+ * Parameters :
+ * EOFSeen : indicates if EOF caused the lexical error
+ * curLexState : lexical state in which this error occurred
+ * errorLine : line number when the error occurred
+ * errorColumn : column number when the error occurred
+ * errorAfter : prefix that was seen before this error occurred
+ * curchar : the offending character
+ * Note: You can customize the lexical error message by modifying this method.
+ */
+ protected static String LexicalError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar) {
+ return("Lexical error at line " +
+ errorLine + ", column " +
+ errorColumn + ". Encountered: " +
+ (EOFSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int)curChar + "), ") +
+ "after : \"" + addEscapes(errorAfter) + "\"");
+ }
+
+ /**
+ * You can also modify the body of this method to customize your error messages.
+ * For example, cases like LOOP_DETECTED and INVALID_LEXICAL_STATE are not
+ * of end-users concern, so you can return something like :
+ *
+ * "Internal Error : Please file a bug report .... "
+ *
+ * from this method for such cases in the release version of your parser.
+ */
+ public String getMessage() {
+ return super.getMessage();
+ }
+
+ /*
+ * Constructors of various flavors follow.
+ */
+
+ /** No arg constructor. */
+ public TokenMgrError() {
+ }
+
+ /** Constructor with message and reason. */
+ public TokenMgrError(String message, int reason) {
+ super(message);
+ errorCode = reason;
+ }
+
+ /** Full Constructor. */
+ public TokenMgrError(boolean EOFSeen, int lexState, int errorLine, int errorColumn, String errorAfter, char curChar, int reason) {
+ this(LexicalError(EOFSeen, lexState, errorLine, errorColumn, errorAfter, curChar), reason);
+ }
+}
+/* JavaCC - OriginalChecksum=42f02fdec2666762ff549943b174bf6b (do not edit this line) */
diff --git a/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/JJTStandardParserState.java b/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/JJTStandardParserState.java
new file mode 100644
index 00000000..eb61e104
--- /dev/null
+++ b/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/JJTStandardParserState.java
@@ -0,0 +1,125 @@
+/* Generated By:JavaCC: Do not edit this line. JJTStandardParserState.java Version 5.0 */
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.runtime.parser.*;
+
+public class JJTStandardParserState {
+ private java.util.List<Node> nodes;
+ private java.util.List<Integer> marks;
+
+ private int sp; // number of nodes on stack
+ private int mk; // current mark
+ private boolean node_created;
+
+ public JJTStandardParserState() {
+ nodes = new java.util.ArrayList<Node>();
+ marks = new java.util.ArrayList<Integer>();
+ sp = 0;
+ mk = 0;
+ }
+
+ /* Determines whether the current node was actually closed and
+ pushed. This should only be called in the final user action of a
+ node scope. */
+ public boolean nodeCreated() {
+ return node_created;
+ }
+
+ /* Call this to reinitialize the node stack. It is called
+ automatically by the parser's ReInit() method. */
+ public void reset() {
+ nodes.clear();
+ marks.clear();
+ sp = 0;
+ mk = 0;
+ }
+
+ /* Returns the root node of the AST. It only makes sense to call
+ this after a successful parse. */
+ public Node rootNode() {
+ return nodes.get(0);
+ }
+
+ /* Pushes a node on to the stack. */
+ public void pushNode(Node n) {
+ nodes.add(n);
+ ++sp;
+ }
+
+ /* Returns the node on the top of the stack, and remove it from the
+ stack. */
+ public Node popNode() {
+ if (--sp < mk) {
+ mk = marks.remove(marks.size()-1);
+ }
+ return nodes.remove(nodes.size()-1);
+ }
+
+ /* Returns the node currently on the top of the stack. */
+ public Node peekNode() {
+ return nodes.get(nodes.size()-1);
+ }
+
+ /* Returns the number of children on the stack in the current node
+ scope. */
+ public int nodeArity() {
+ return sp - mk;
+ }
+
+
+ public void clearNodeScope(Node n) {
+ while (sp > mk) {
+ popNode();
+ }
+ mk = marks.remove(marks.size()-1);
+ }
+
+
+ public void openNodeScope(Node n) {
+ marks.add(mk);
+ mk = sp;
+ n.jjtOpen();
+ }
+
+
+ /* A definite node is constructed from a specified number of
+ children. That number of nodes are popped from the stack and
+ made the children of the definite node. Then the definite node
+ is pushed on to the stack. */
+ public void closeNodeScope(Node n, int num) {
+ mk = marks.remove(marks.size()-1);
+ while (num-- > 0) {
+ Node c = popNode();
+ c.jjtSetParent(n);
+ n.jjtAddChild(c, num);
+ }
+ n.jjtClose();
+ pushNode(n);
+ node_created = true;
+ }
+
+
+ /* A conditional node is constructed if its condition is true. All
+ the nodes that have been pushed since the node was opened are
+ made children of the conditional node, which is then pushed
+ on to the stack. If the condition is false the node is not
+ constructed and they are left on the stack. */
+ public void closeNodeScope(Node n, boolean condition) {
+ if (condition) {
+ int a = nodeArity();
+ mk = marks.remove(marks.size()-1);
+ while (a-- > 0) {
+ Node c = popNode();
+ c.jjtSetParent(n);
+ n.jjtAddChild(c, a);
+ }
+ n.jjtClose();
+ pushNode(n);
+ node_created = true;
+ } else {
+ mk = marks.remove(marks.size()-1);
+ node_created = false;
+ }
+ }
+}
+/* JavaCC - OriginalChecksum=d34682d17dff0a3b107321d543729d5d (do not edit this line) */
diff --git a/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserTreeConstants.java b/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserTreeConstants.java
new file mode 100644
index 00000000..f6cdcb65
--- /dev/null
+++ b/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserTreeConstants.java
@@ -0,0 +1,101 @@
+/* Generated By:JavaCC: Do not edit this line. StandardParserTreeConstants.java Version 5.0 */
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.runtime.parser.*;
+
+public interface StandardParserTreeConstants
+{
+ public int JJTPROCESS = 0;
+ public int JJTVOID = 1;
+ public int JJTTEXT = 2;
+ public int JJTESCAPEDDIRECTIVE = 3;
+ public int JJTESCAPE = 4;
+ public int JJTCOMMENT = 5;
+ public int JJTTEXTBLOCK = 6;
+ public int JJTFLOATINGPOINTLITERAL = 7;
+ public int JJTINTEGERLITERAL = 8;
+ public int JJTSTRINGLITERAL = 9;
+ public int JJTIDENTIFIER = 10;
+ public int JJTWORD = 11;
+ public int JJTDIRECTIVEASSIGN = 12;
+ public int JJTDIRECTIVE = 13;
+ public int JJTBLOCK = 14;
+ public int JJTMAP = 15;
+ public int JJTOBJECTARRAY = 16;
+ public int JJTINTEGERRANGE = 17;
+ public int JJTMETHOD = 18;
+ public int JJTINDEX = 19;
+ public int JJTREFERENCE = 20;
+ public int JJTTRUE = 21;
+ public int JJTFALSE = 22;
+ public int JJTIFSTATEMENT = 23;
+ public int JJTELSESTATEMENT = 24;
+ public int JJTELSEIFSTATEMENT = 25;
+ public int JJTSETDIRECTIVE = 26;
+ public int JJTEXPRESSION = 27;
+ public int JJTASSIGNMENT = 28;
+ public int JJTORNODE = 29;
+ public int JJTANDNODE = 30;
+ public int JJTEQNODE = 31;
+ public int JJTNENODE = 32;
+ public int JJTLTNODE = 33;
+ public int JJTGTNODE = 34;
+ public int JJTLENODE = 35;
+ public int JJTGENODE = 36;
+ public int JJTADDNODE = 37;
+ public int JJTSUBTRACTNODE = 38;
+ public int JJTMULNODE = 39;
+ public int JJTDIVNODE = 40;
+ public int JJTMODNODE = 41;
+ public int JJTNOTNODE = 42;
+ public int JJTNEGATENODE = 43;
+
+
+ public String[] jjtNodeName = {
+ "process",
+ "void",
+ "Text",
+ "EscapedDirective",
+ "Escape",
+ "Comment",
+ "Textblock",
+ "FloatingPointLiteral",
+ "IntegerLiteral",
+ "StringLiteral",
+ "Identifier",
+ "Word",
+ "DirectiveAssign",
+ "Directive",
+ "Block",
+ "Map",
+ "ObjectArray",
+ "IntegerRange",
+ "Method",
+ "Index",
+ "Reference",
+ "True",
+ "False",
+ "IfStatement",
+ "ElseStatement",
+ "ElseIfStatement",
+ "SetDirective",
+ "Expression",
+ "Assignment",
+ "OrNode",
+ "AndNode",
+ "EQNode",
+ "NENode",
+ "LTNode",
+ "GTNode",
+ "LENode",
+ "GENode",
+ "AddNode",
+ "SubtractNode",
+ "MulNode",
+ "DivNode",
+ "ModNode",
+ "NotNode",
+ "NegateNode",
+ };
+}
+/* JavaCC - OriginalChecksum=3cbe37a416c36eb645df00e50cd4ad4d (do not edit this line) */
diff --git a/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserVisitor.java b/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserVisitor.java
new file mode 100644
index 00000000..0140ae18
--- /dev/null
+++ b/generated-sources/jjtree/org/apache/velocity/runtime/parser/node/StandardParserVisitor.java
@@ -0,0 +1,53 @@
+/* Generated By:JavaCC: Do not edit this line. StandardParserVisitor.java Version 5.0 */
+package org.apache.velocity.runtime.parser.node;
+
+import org.apache.velocity.runtime.parser.*;
+
+public interface StandardParserVisitor
+{
+ public Object visit(SimpleNode node, Object data);
+ public Object visit(ASTprocess node, Object data);
+ public Object visit(ASTText node, Object data);
+ public Object visit(ASTEscapedDirective node, Object data);
+ public Object visit(ASTEscape node, Object data);
+ public Object visit(ASTComment node, Object data);
+ public Object visit(ASTTextblock node, Object data);
+ public Object visit(ASTFloatingPointLiteral node, Object data);
+ public Object visit(ASTIntegerLiteral node, Object data);
+ public Object visit(ASTStringLiteral node, Object data);
+ public Object visit(ASTIdentifier node, Object data);
+ public Object visit(ASTWord node, Object data);
+ public Object visit(ASTDirectiveAssign node, Object data);
+ public Object visit(ASTDirective node, Object data);
+ public Object visit(ASTBlock node, Object data);
+ public Object visit(ASTMap node, Object data);
+ public Object visit(ASTObjectArray node, Object data);
+ public Object visit(ASTIntegerRange node, Object data);
+ public Object visit(ASTMethod node, Object data);
+ public Object visit(ASTIndex node, Object data);
+ public Object visit(ASTReference node, Object data);
+ public Object visit(ASTTrue node, Object data);
+ public Object visit(ASTFalse node, Object data);
+ public Object visit(ASTIfStatement node, Object data);
+ public Object visit(ASTElseStatement node, Object data);
+ public Object visit(ASTElseIfStatement node, Object data);
+ public Object visit(ASTSetDirective node, Object data);
+ public Object visit(ASTExpression node, Object data);
+ public Object visit(ASTAssignment node, Object data);
+ public Object visit(ASTOrNode node, Object data);
+ public Object visit(ASTAndNode node, Object data);
+ public Object visit(ASTEQNode node, Object data);
+ public Object visit(ASTNENode node, Object data);
+ public Object visit(ASTLTNode node, Object data);
+ public Object visit(ASTGTNode node, Object data);
+ public Object visit(ASTLENode node, Object data);
+ public Object visit(ASTGENode node, Object data);
+ public Object visit(ASTAddNode node, Object data);
+ public Object visit(ASTSubtractNode node, Object data);
+ public Object visit(ASTMulNode node, Object data);
+ public Object visit(ASTDivNode node, Object data);
+ public Object visit(ASTModNode node, Object data);
+ public Object visit(ASTNotNode node, Object data);
+ public Object visit(ASTNegateNode node, Object data);
+}
+/* JavaCC - OriginalChecksum=ca5f161786ac4b6910db90de458469db (do not edit this line) */
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..4432c60c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,371 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-master</artifactId>
+ <version>4</version>
+ <relativePath />
+ </parent>
+
+ <artifactId>velocity-engine-parent</artifactId>
+ <version>2.4-SNAPSHOT</version>
+
+ <name>Apache Velocity</name>
+ <url>http://velocity.apache.org/engine/devel/</url>
+ <description>Apache Velocity is a general purpose template engine.</description>
+ <inceptionYear>2000</inceptionYear>
+ <packaging>pom</packaging>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <junit.version>4.13.2</junit.version>
+ <slf4j.version>1.7.30</slf4j.version>
+ <surefire.plugin.version>2.22.1</surefire.plugin.version>
+ <jira.browse.url>https://issues.apache.org/jira/browse</jira.browse.url>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ </properties>
+
+ <build>
+ <defaultGoal>install</defaultGoal>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>3.0.0-M1</version>
+ <configuration>
+ <useReleaseProfile>false</useReleaseProfile>
+ <autoVersionSubmodules>true</autoVersionSubmodules>
+ <goals>deploy</goals>
+ <arguments>-Papache-release</arguments>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.1.2</version>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>3.5.1</version>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>3.1.1</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.1.0</version>
+ </plugin>
+ <plugin>
+ <groupId>com.google.code.maven-replacer-plugin</groupId>
+ <artifactId>replacer</artifactId>
+ <version>1.5.3</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>3.1.0</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>3.1.0</version>
+ <configuration>
+ <includePom>false</includePom>
+ </configuration>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>extra-enforcer-rules</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>3.2.0</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <showDeprecation>true</showDeprecation>
+ <showWarning>true</showWarning>
+ <source>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>3.1.1</version>
+ <configuration>
+ <!-- full checking is left disabled
+ <doclint>html,missing,reference,syntax</doclint>
+ -->
+ <doclint>none</doclint>
+ </configuration>
+ <executions>
+ <execution>
+ <id>aggregate</id>
+ <goals>
+ <goal>aggregate</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-enforcer-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>enforce-bytecode-version</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <enforceBytecodeVersion>
+ <maxJdkVersion>${maven.compiler.target}</maxJdkVersion>
+ </enforceBytecodeVersion>
+ <requireJavaVersion>
+ <version>[1.8,)</version>
+ </requireJavaVersion>
+ </rules>
+ <fail>true</fail>
+ </configuration>
+ </execution>
+ <execution>
+ <id>ban-known-bad-maven-versions</id>
+ <goals>
+ <goal>enforce</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <requireMavenVersion>
+ <version>[3.0.5,)</version>
+ <message>Maven minimal expected version is 3.0.5.</message>
+ </requireMavenVersion>
+ </rules>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>extra-enforcer-rules</artifactId>
+ <version>1.3</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+ <distributionManagement>
+ <site>
+ <id>velocity.apache.org</id>
+ <url>scpexe://people.apache.org/www/velocity.apache.org/engine/devel/
+ </url>
+ </site>
+ </distributionManagement>
+
+ <scm>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/velocity-engine.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/velocity-engine.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=velocity-engine.git</url>
+ <tag>HEAD</tag>
+ </scm>
+
+ <issueManagement>
+ <system>JIRA</system>
+ <url>${jira.browse.url}/VELOCITY</url>
+ </issueManagement>
+ <modules>
+ <module>velocity-engine-core</module>
+ <module>velocity-engine-examples</module>
+ <module>velocity-engine-scripting</module>
+ <module>velocity-custom-parser-example</module>
+ <module>spring-velocity-support</module>
+ </modules>
+
+ <!-- This project is an effort by many people. If you feel that your name
+ should be in here and has been omitted in error, please open an issue
+ with the Apache Velocity Issue tracker. -->
+ <contributors>
+ <contributor>
+ <name>Adrian Tarau</name>
+ </contributor>
+ <contributor>
+ <name>Aki Nieminen</name>
+ </contributor>
+ <contributor>
+ <name>Alexey Pachenko</name>
+ </contributor>
+ <contributor>
+ <name>Anil K. Vijendran</name>
+ </contributor>
+ <contributor>
+ <name>Attila Szegedi</name>
+ </contributor>
+ <contributor>
+ <name>Bob McWhirter</name>
+ </contributor>
+ <contributor>
+ <name>Byron Foster</name>
+ </contributor>
+ <contributor>
+ <name>Candid Dauth</name>
+ </contributor>
+ <contributor>
+ <name>Christoph Reck</name>
+ </contributor>
+ <contributor>
+ <name>Darren Cruse</name>
+ </contributor>
+ <contributor>
+ <name>Dave Bryson</name>
+ </contributor>
+ <contributor>
+ <name>David Kinnvall</name>
+ </contributor>
+ <contributor>
+ <name>Dawid Weiss</name>
+ </contributor>
+ <contributor>
+ <name>Dishara Wijewardana</name>
+ </contributor>
+ <contributor>
+ <name>Eelco Hillenius</name>
+ </contributor>
+ <contributor>
+ <name>Fedor Karpelevitch</name>
+ </contributor>
+ <contributor>
+ <name>Felipe Maschio</name>
+ </contributor>
+ <contributor>
+ <name>Gal Shachor</name>
+ </contributor>
+ <contributor>
+ <name>Hervé Boutemy</name>
+ </contributor>
+ <contributor>
+ <name>Jarkko Viinamäki</name>
+ </contributor>
+ <contributor>
+ <name>Jeff Bowden</name>
+ </contributor>
+ <contributor>
+ <name>Jorgen Rydenius</name>
+ </contributor>
+ <contributor>
+ <name>Jose Alberto Fernandez</name>
+ </contributor>
+ <contributor>
+ <name>Kasper Nielsen</name>
+ </contributor>
+ <contributor>
+ <name>Kent Johnson</name>
+ </contributor>
+ <contributor>
+ <name>Kyle F. Downey</name>
+ </contributor>
+ <contributor>
+ <name>Leon Messerschmidt</name>
+ </contributor>
+ <contributor>
+ <name>Llewellyn Falco</name>
+ </contributor>
+ <contributor>
+ <name>Matt Raible</name>
+ </contributor>
+ <contributor>
+ <name>Matt Ryall</name>
+ </contributor>
+ <contributor>
+ <name>Matthijs Lambooy</name>
+ </contributor>
+ <contributor>
+ <name>Oswaldo Hernandez</name>
+ </contributor>
+ <contributor>
+ <name>Paulo Gaspar</name>
+ </contributor>
+ <contributor>
+ <name>Peter Romianowski</name>
+ </contributor>
+ <contributor>
+ <name>Robert Burrell Donkin</name>
+ </contributor>
+ <contributor>
+ <name>Robert Fuller</name>
+ </contributor>
+ <contributor>
+ <name>Sam Ruby</name>
+ </contributor>
+ <contributor>
+ <name>Sean Legassick</name>
+ </contributor>
+ <contributor>
+ <name>Serge Knystautas</name>
+ </contributor>
+ <contributor>
+ <name>Stephane Bailliez</name>
+ </contributor>
+ <contributor>
+ <name>Stephen Habermann</name>
+ </contributor>
+ <contributor>
+ <name>Sylwester Lachiewicz</name>
+ </contributor>
+ </contributors>
+</project>
diff --git a/spring-velocity-support/README.md b/spring-velocity-support/README.md
new file mode 100644
index 00000000..c31ab24c
--- /dev/null
+++ b/spring-velocity-support/README.md
@@ -0,0 +1,22 @@
+Title: Apache Velocity Spring Support
+
+# Apache Velocity Spring Support
+
+This module is an adaptation of the engine support initially hosted by the Spring project in its 4.x versions.
+
+Example configuration:
+
+```xml
+<bean id="velocityEngine"
+ class="org.apache.velocity.spring.VelocityEngineFactoryBean">
+ <property name="velocityProperties">
+ <props>
+ <prop key="resource.loader">class</prop>
+ <prop key="class.resource.loader.class">
+ org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+ </prop>
+ </props>
+ </property>
+</bean>
+```
+
diff --git a/spring-velocity-support/pom.xml b/spring-velocity-support/pom.xml
new file mode 100644
index 00000000..936aa5d5
--- /dev/null
+++ b/spring-velocity-support/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-parent</artifactId>
+ <version>2.4-SNAPSHOT</version>
+ </parent>
+ <artifactId>spring-velocity-support</artifactId>
+ <name>Spring framework Velocity support</name>
+ <description>Velocity Engine factory bean for Spring framework</description>
+ <properties>
+ <springframework.version>5.3.20</springframework.version>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ <version>${springframework.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ <version>${springframework.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ <version>${springframework.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.plugin.version}</version>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>test.resources.dir</name>
+ <value>${project.build.testOutputDirectory}</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ <executions>
+ <execution>
+ <id>integration-test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java
new file mode 100644
index 00000000..befe4c5b
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/SpringResourceLoader.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.velocity.spring;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Arrays;
+
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+
+import org.apache.velocity.util.ExtProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
+
+/**
+ * Velocity ResourceLoader adapter that loads via a Spring ResourceLoader.
+ * Used by VelocityEngineFactory for any resource loader path that cannot
+ * be resolved to a {@code java.io.File}.
+ *
+ * <p>Note that this loader does not allow for modification detection:
+ * Use Velocity's default FileResourceLoader for {@code java.io.File}
+ * resources.
+ *
+ * <p>Expects "spring.resource.loader" and "spring.resource.loader.path"
+ * application attributes in the Velocity runtime: the former of type
+ * {@code org.springframework.core.io.ResourceLoader}, the latter a String.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ * @see VelocityEngineFactory#setResourceLoaderPath
+ * @see org.springframework.core.io.ResourceLoader
+ * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
+ */
+public class SpringResourceLoader extends ResourceLoader {
+
+ public static final String NAME = "spring";
+
+ public static final String SPRING_RESOURCE_LOADER_CLASS = "spring.resource.loader.class";
+
+ public static final String SPRING_RESOURCE_LOADER_CACHE = "spring.resource.loader.cache";
+
+ public static final String SPRING_RESOURCE_LOADER = "spring.resource.loader";
+
+ public static final String SPRING_RESOURCE_LOADER_PATH = "spring.resource.loader.path";
+
+
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private org.springframework.core.io.ResourceLoader resourceLoader;
+
+ private String[] resourceLoaderPaths;
+
+
+ @Override
+ public void init(ExtProperties configuration) {
+ this.resourceLoader = (org.springframework.core.io.ResourceLoader)
+ this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER);
+ String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH);
+ if (this.resourceLoader == null) {
+ throw new IllegalArgumentException(
+ "'resourceLoader' application attribute must be present for SpringResourceLoader");
+ }
+ if (resourceLoaderPath == null) {
+ throw new IllegalArgumentException(
+ "'resourceLoaderPath' application attribute must be present for SpringResourceLoader");
+ }
+ this.resourceLoaderPaths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
+ for (int i = 0; i < this.resourceLoaderPaths.length; i++) {
+ String path = this.resourceLoaderPaths[i];
+ if (!path.endsWith("/")) {
+ this.resourceLoaderPaths[i] = path + "/";
+ }
+ }
+ if (logger.isInfoEnabled()) {
+ logger.info("SpringResourceLoader for Velocity: using resource loader [" + this.resourceLoader +
+ "] and resource loader paths " + Arrays.asList(this.resourceLoaderPaths));
+ }
+ }
+
+ /**
+ * Get the Reader that the Runtime will parse
+ * to create a template.
+ *
+ * @param source resource name
+ * @param encoding resource encoding
+ * @return The reader for the requested resource.
+ * @throws ResourceNotFoundException
+ * @since 2.0
+ */
+ @Override
+ public Reader getResourceReader(String source, String encoding) throws ResourceNotFoundException {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Looking for Velocity resource with name [" + source + "]");
+ }
+ for (String resourceLoaderPath : this.resourceLoaderPaths) {
+ org.springframework.core.io.Resource resource =
+ this.resourceLoader.getResource(resourceLoaderPath + source);
+ try {
+ return new InputStreamReader(resource.getInputStream(), encoding);
+ }
+ catch (IOException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Could not find Velocity resource: " + resource);
+ }
+ }
+ }
+ throw new ResourceNotFoundException(
+ "Could not find resource [" + source + "] in Spring resource loader path");
+ }
+
+ @Override
+ public boolean isSourceModified(Resource resource) {
+ return false;
+ }
+
+ @Override
+ public long getLastModified(Resource resource) {
+ return 0;
+ }
+
+}
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java
new file mode 100644
index 00000000..e2ecda6f
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactory.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.velocity.spring;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * Factory that configures a VelocityEngine. Can be used standalone,
+ * but typically you will either use {@link VelocityEngineFactoryBean}
+ * for preparing a VelocityEngine as bean reference, or
+ * <a href="https://docs.spring.io/spring-framework/docs/4.3.29.RELEASE/javadoc-api/org/springframework/web/servlet/view/velocity/VelocityConfigurer.html">org.springframework.web.servlet.view.velocity.VelocityConfigurer</a>
+ * for web views.
+ *
+ * <p>The optional "configLocation" property sets the location of the Velocity
+ * properties file, within the current application. Velocity properties can be
+ * overridden via "velocityProperties", or even completely specified locally,
+ * avoiding the need for an external properties file.
+ *
+ * <p>The "resourceLoaderPath" property can be used to specify the Velocity
+ * resource loader path via Spring's Resource abstraction, possibly relative
+ * to the Spring application context.
+ *
+ * <p>The simplest way to use this class is to specify a
+ * {@link #setResourceLoaderPath(String) "resourceLoaderPath"}; the
+ * VelocityEngine typically then does not need any further configuration.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ * @see #setConfigLocation
+ * @see #setVelocityProperties
+ * @see #setResourceLoaderPath
+ * @see #createVelocityEngine
+ * @see VelocityEngineFactoryBean
+ * @see <a href="https://docs.spring.io/spring-framework/docs/4.3.29.RELEASE/javadoc-api/org/springframework/web/servlet/view/velocity/VelocityConfigurer.html">org.springframework.web.servlet.view.velocity.VelocityConfigurer</a>
+ * @see org.apache.velocity.app.VelocityEngine
+ */
+public class VelocityEngineFactory {
+
+ protected static final Logger logger = LoggerFactory.getLogger(VelocityEngineFactory.class);
+
+ private Resource configLocation;
+
+ private final Map<String, Object> velocityProperties = new HashMap<String, Object>();
+
+ private String resourceLoaderPath;
+
+ private ResourceLoader resourceLoader = new DefaultResourceLoader();
+
+ private boolean preferFileSystemAccess = true;
+
+ private boolean overrideLogging = true;
+
+
+ /**
+ * Set the location of the Velocity config file.
+ * Alternatively, you can specify all properties locally.
+ * @see #setVelocityProperties
+ * @see #setResourceLoaderPath
+ */
+ public void setConfigLocation(Resource configLocation) {
+ this.configLocation = configLocation;
+ }
+
+ /**
+ * Set Velocity properties, like "file.resource.loader.path".
+ * Can be used to override values in a Velocity config file,
+ * or to specify all necessary properties locally.
+ * <p>Note that the Velocity resource loader path also be set to any
+ * Spring resource location via the "resourceLoaderPath" property.
+ * Setting it here is just necessary when using a non-file-based
+ * resource loader.
+ * @see #setVelocityPropertiesMap
+ * @see #setConfigLocation
+ * @see #setResourceLoaderPath
+ */
+ public void setVelocityProperties(Properties velocityProperties) {
+ CollectionUtils.mergePropertiesIntoMap(velocityProperties, this.velocityProperties);
+ }
+
+ /**
+ * Set Velocity properties as Map, to allow for non-String values
+ * like "ds.resource.loader.instance".
+ * @see #setVelocityProperties
+ */
+ public void setVelocityPropertiesMap(Map<String, Object> velocityPropertiesMap) {
+ if (velocityPropertiesMap != null) {
+ this.velocityProperties.putAll(velocityPropertiesMap);
+ }
+ }
+
+ /**
+ * Set the Velocity resource loader path via a Spring resource location.
+ * Accepts multiple locations in Velocity's comma-separated path style.
+ * <p>When populated via a String, standard URLs like "file:" and "classpath:"
+ * pseudo URLs are supported, as understood by ResourceLoader. Allows for
+ * relative paths when running in an ApplicationContext.
+ * <p>Will define a path for the default Velocity resource loader with the name
+ * "file". If the specified resource cannot be resolved to a {@code java.io.File},
+ * a generic SpringResourceLoader will be used under the name "spring", without
+ * modification detection.
+ * <p>Note that resource caching will be enabled in any case. With the file
+ * resource loader, the last-modified timestamp will be checked on access to
+ * detect changes. With SpringResourceLoader, the resource will be cached
+ * forever (for example for class path resources).
+ * <p>To specify a modification check interval for files, use Velocity's
+ * standard "file.resource.loader.modificationCheckInterval" property. By default,
+ * the file timestamp is checked on every access (which is surprisingly fast).
+ * Of course, this just applies when loading resources from the file system.
+ * <p>To enforce the use of SpringResourceLoader, i.e. to not resolve a path
+ * as file system resource in any case, turn off the "preferFileSystemAccess"
+ * flag. See the latter's javadoc for details.
+ * @see #setResourceLoader
+ * @see #setVelocityProperties
+ * @see #setPreferFileSystemAccess
+ * @see SpringResourceLoader
+ * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
+ */
+ public void setResourceLoaderPath(String resourceLoaderPath) {
+ this.resourceLoaderPath = resourceLoaderPath;
+ }
+
+ /**
+ * Set the Spring ResourceLoader to use for loading Velocity template files.
+ * The default is DefaultResourceLoader. Will get overridden by the
+ * ApplicationContext if running in a context.
+ * @see org.springframework.core.io.DefaultResourceLoader
+ * @see org.springframework.context.ApplicationContext
+ */
+ public void setResourceLoader(ResourceLoader resourceLoader) {
+ this.resourceLoader = resourceLoader;
+ }
+
+ /**
+ * Return the Spring ResourceLoader to use for loading Velocity template files.
+ */
+ protected ResourceLoader getResourceLoader() {
+ return this.resourceLoader;
+ }
+
+ /**
+ * Set whether to prefer file system access for template loading.
+ * File system access enables hot detection of template changes.
+ * <p>If this is enabled, VelocityEngineFactory will try to resolve the
+ * specified "resourceLoaderPath" as file system resource (which will work
+ * for expanded class path resources and ServletContext resources too).
+ * <p>Default is "true". Turn this off to always load via SpringResourceLoader
+ * (i.e. as stream, without hot detection of template changes), which might
+ * be necessary if some of your templates reside in an expanded classes
+ * directory while others reside in jar files.
+ * @see #setResourceLoaderPath
+ */
+ public void setPreferFileSystemAccess(boolean preferFileSystemAccess) {
+ this.preferFileSystemAccess = preferFileSystemAccess;
+ }
+
+ /**
+ * Return whether to prefer file system access for template loading.
+ */
+ protected boolean isPreferFileSystemAccess() {
+ return this.preferFileSystemAccess;
+ }
+
+ /**
+ * Prepare the VelocityEngine instance and return it.
+ * @return the VelocityEngine instance
+ * @throws IOException if the config file wasn't found
+ * @throws VelocityException on Velocity initialization failure
+ */
+ public VelocityEngine createVelocityEngine() throws IOException, VelocityException {
+ VelocityEngine velocityEngine = newVelocityEngine();
+ Map<String, Object> props = new HashMap<String, Object>();
+
+ // Load config file if set.
+ if (this.configLocation != null) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Loading Velocity config from [" + this.configLocation + "]");
+ }
+ CollectionUtils.mergePropertiesIntoMap(PropertiesLoaderUtils.loadProperties(this.configLocation), props);
+ }
+
+ // Merge local properties if set.
+ if (!this.velocityProperties.isEmpty()) {
+ props.putAll(this.velocityProperties);
+ }
+
+ // Set a resource loader path, if required.
+ if (this.resourceLoaderPath != null) {
+ initVelocityResourceLoader(velocityEngine, this.resourceLoaderPath);
+ }
+
+ // Apply properties to VelocityEngine.
+ for (Map.Entry<String, Object> entry : props.entrySet()) {
+ velocityEngine.setProperty(entry.getKey(), entry.getValue());
+ }
+
+ postProcessVelocityEngine(velocityEngine);
+
+ // Perform actual initialization.
+ velocityEngine.init();
+
+ return velocityEngine;
+ }
+
+ /**
+ * Return a new VelocityEngine. Subclasses can override this for
+ * custom initialization, or for using a mock object for testing.
+ * <p>Called by {@code createVelocityEngine()}.
+ * @return the VelocityEngine instance
+ * @throws IOException if a config file wasn't found
+ * @throws VelocityException on Velocity initialization failure
+ * @see #createVelocityEngine()
+ */
+ protected VelocityEngine newVelocityEngine() throws IOException, VelocityException {
+ return new VelocityEngine();
+ }
+
+ /**
+ * Initialize a Velocity resource loader for the given VelocityEngine:
+ * either a standard Velocity FileResourceLoader or a SpringResourceLoader.
+ * <p>Called by {@code createVelocityEngine()}.
+ * @param velocityEngine the VelocityEngine to configure
+ * @param resourceLoaderPath the path to load Velocity resources from
+ * @see org.apache.velocity.runtime.resource.loader.FileResourceLoader
+ * @see SpringResourceLoader
+ * @see #initSpringResourceLoader
+ * @see #createVelocityEngine()
+ */
+ protected void initVelocityResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
+ if (isPreferFileSystemAccess()) {
+ // Try to load via the file system, fall back to SpringResourceLoader
+ // (for hot detection of template changes, if possible).
+ try {
+ StringBuilder resolvedPath = new StringBuilder();
+ String[] paths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
+ for (int i = 0; i < paths.length; i++) {
+ String path = paths[i];
+ Resource resource = getResourceLoader().getResource(path);
+ File file = resource.getFile(); // will fail if not resolvable in the file system
+ if (logger.isDebugEnabled()) {
+ logger.debug("Resource loader path [" + path + "] resolved to file [" + file.getAbsolutePath() + "]");
+ }
+ resolvedPath.append(file.getAbsolutePath());
+ if (i < paths.length - 1) {
+ resolvedPath.append(',');
+ }
+ }
+ velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
+ velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
+ velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, resolvedPath.toString());
+ }
+ catch (IOException ex) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Cannot resolve resource loader path [" + resourceLoaderPath +
+ "] to [java.io.File]: using SpringResourceLoader", ex);
+ }
+ initSpringResourceLoader(velocityEngine, resourceLoaderPath);
+ }
+ }
+ else {
+ // Always load via SpringResourceLoader
+ // (without hot detection of template changes).
+ if (logger.isDebugEnabled()) {
+ logger.debug("File system access not preferred: using SpringResourceLoader");
+ }
+ initSpringResourceLoader(velocityEngine, resourceLoaderPath);
+ }
+ }
+
+ /**
+ * Initialize a SpringResourceLoader for the given VelocityEngine.
+ * <p>Called by {@code initVelocityResourceLoader}.
+ * @param velocityEngine the VelocityEngine to configure
+ * @param resourceLoaderPath the path to load Velocity resources from
+ * @see SpringResourceLoader
+ * @see #initVelocityResourceLoader
+ */
+ protected void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
+ velocityEngine.setProperty(
+ RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME);
+ velocityEngine.setProperty(
+ SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName());
+ velocityEngine.setProperty(
+ SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true");
+ velocityEngine.setApplicationAttribute(
+ SpringResourceLoader.SPRING_RESOURCE_LOADER, getResourceLoader());
+ velocityEngine.setApplicationAttribute(
+ SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath);
+ }
+
+ /**
+ * To be implemented by subclasses that want to perform custom
+ * post-processing of the VelocityEngine after this FactoryBean
+ * performed its default configuration (but before VelocityEngine.init).
+ * <p>Called by {@code createVelocityEngine()}.
+ * @param velocityEngine the current VelocityEngine
+ * @throws IOException if a config file wasn't found
+ * @throws VelocityException on Velocity initialization failure
+ * @see #createVelocityEngine()
+ * @see org.apache.velocity.app.VelocityEngine#init
+ */
+ protected void postProcessVelocityEngine(VelocityEngine velocityEngine)
+ throws IOException, VelocityException {
+ }
+
+}
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java
new file mode 100644
index 00000000..78299ab1
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineFactoryBean.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2012 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.velocity.spring;
+
+import java.io.IOException;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ResourceLoaderAware;
+
+/**
+ * Factory bean that configures a VelocityEngine and provides it as bean
+ * reference. This bean is intended for any kind of usage of Velocity in
+ * application code, e.g. for generating email content. For web views,
+ * VelocityConfigurer is used to set up a VelocityEngine for views.
+ *
+ * <p>The simplest way to use this class is to specify a "resourceLoaderPath";
+ * you do not need any further configuration then. For example, in a web
+ * application context:
+ *
+ * <pre class="code"> &lt;bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"&gt;
+ * &lt;property name="resourceLoaderPath" value="/WEB-INF/velocity/"/&gt;
+ * &lt;/bean&gt;</pre>
+ *
+ * See the base class VelocityEngineFactory for configuration details.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ * @see #setConfigLocation
+ * @see #setVelocityProperties
+ * @see #setResourceLoaderPath
+ * @see <a href="https://docs.spring.io/spring-framework/docs/4.3.29.RELEASE/javadoc-api/org/springframework/web/servlet/view/velocity/VelocityConfigurer.html">org.springframework.web.servlet.view.velocity.VelocityConfigurer</a>
+ */
+public class VelocityEngineFactoryBean extends VelocityEngineFactory
+ implements FactoryBean<VelocityEngine>, InitializingBean, ResourceLoaderAware {
+
+ private VelocityEngine velocityEngine;
+
+
+ @Override
+ public void afterPropertiesSet() throws IOException, VelocityException {
+ this.velocityEngine = createVelocityEngine();
+ }
+
+
+ @Override
+ public VelocityEngine getObject() {
+ return this.velocityEngine;
+ }
+
+ @Override
+ public Class<? extends VelocityEngine> getObjectType() {
+ return VelocityEngine.class;
+ }
+
+ @Override
+ public boolean isSingleton() {
+ return true;
+ }
+
+}
diff --git a/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java
new file mode 100644
index 00000000..7e0be723
--- /dev/null
+++ b/spring-velocity-support/src/main/java/org/apache/velocity/spring/VelocityEngineUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2002-2013 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.velocity.spring;
+
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+
+/**
+ * Utility class for working with a VelocityEngine.
+ * Provides convenience methods to merge a Velocity template with a model.
+ *
+ * @author Juergen Hoeller
+ * @author Claude Brisson
+ * @since 2020-05-29
+ */
+public abstract class VelocityEngineUtils {
+
+ /**
+ * Merge the specified Velocity template with the given model and write
+ * the result to the given Writer.
+ * @param velocityEngine VelocityEngine to work with
+ * @param templateLocation the location of template, relative to Velocity's resource loader path
+ * @param model the Map that contains model names as keys and model objects as values
+ * @param writer the Writer to write the result to
+ * @throws VelocityException if the template wasn't found or rendering failed
+ * @deprecated Use {@link #mergeTemplate(VelocityEngine, String, String, Map, Writer)}
+ * instead, following Velocity 1.6's corresponding deprecation in its own API.
+ */
+ @Deprecated
+ public static void mergeTemplate(
+ VelocityEngine velocityEngine, String templateLocation, Map<String, Object> model, Writer writer)
+ throws VelocityException {
+ mergeTemplate(velocityEngine, templateLocation, null, writer);
+ }
+
+ /**
+ * Merge the specified Velocity template with the given model and write the result
+ * to the given Writer.
+ * @param velocityEngine VelocityEngine to work with
+ * @param templateLocation the location of template, relative to Velocity's resource loader path
+ * @param encoding the encoding of the template file
+ * @param model the Map that contains model names as keys and model objects as values
+ * @param writer the Writer to write the result to
+ * @throws VelocityException if the template wasn't found or rendering failed
+ */
+ public static void mergeTemplate(
+ VelocityEngine velocityEngine, String templateLocation, String encoding,
+ Map<String, Object> model, Writer writer) throws VelocityException {
+
+ VelocityContext velocityContext = new VelocityContext(model);
+ velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
+ }
+
+ /**
+ * Merge the specified Velocity template with the given model into a String.
+ * <p>When using this method to prepare a text for a mail to be sent with Spring's
+ * mail support, consider wrapping VelocityException in MailPreparationException.
+ * @param velocityEngine VelocityEngine to work with
+ * @param templateLocation the location of template, relative to Velocity's resource loader path
+ * @param model the Map that contains model names as keys and model objects as values
+ * @return the result as String
+ * @throws VelocityException if the template wasn't found or rendering failed
+ * @see org.springframework.mail.MailPreparationException
+ * @deprecated Use {@link #mergeTemplateIntoString(VelocityEngine, String, String, Map)}
+ * instead, following Velocity 1.6's corresponding deprecation in its own API.
+ */
+ @Deprecated
+ public static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
+ Map<String, Object> model) throws VelocityException {
+ return mergeTemplateIntoString(velocityEngine, templateLocation, null, model);
+ }
+
+ /**
+ * Merge the specified Velocity template with the given model into a String.
+ * <p>When using this method to prepare a text for a mail to be sent with Spring's
+ * mail support, consider wrapping VelocityException in MailPreparationException.
+ * @param velocityEngine VelocityEngine to work with
+ * @param templateLocation the location of template, relative to Velocity's resource loader path
+ * @param encoding the encoding of the template file
+ * @param model the Map that contains model names as keys and model objects as values
+ * @return the result as String
+ * @throws VelocityException if the template wasn't found or rendering failed
+ * @see org.springframework.mail.MailPreparationException
+ */
+ public static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
+ String encoding, Map<String, Object> model) throws VelocityException {
+
+ StringWriter result = new StringWriter();
+ mergeTemplate(velocityEngine, templateLocation, encoding, model, result);
+ return result.toString();
+ }
+
+}
diff --git a/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java
new file mode 100644
index 00000000..97fdfd75
--- /dev/null
+++ b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryBeanTests.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2019 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.velocity.spring.test;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.spring.SpringResourceLoader;
+import org.apache.velocity.spring.VelocityEngineFactoryBean;
+import org.apache.velocity.spring.VelocityEngineUtils;
+import org.junit.Test;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.core.io.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Juergen Hoeller
+ * @author Issam El-atif
+ * @author Sam Brannen
+ */
+public class VelocityEngineFactoryBeanTests
+{
+ private static final String resourcesPath = System.getProperty("test.resources.dir");
+ private final VelocityEngineFactoryBean vefb = new VelocityEngineFactoryBean();
+
+ @Test
+ public void velocityFactoryBeanWithConfigLocation() throws Exception {
+ vefb.setConfigLocation(new ClassPathResource("velocity.properties"));
+ vefb.afterPropertiesSet();
+ VelocityEngine engine = vefb.getObject();
+ assertEquals("bean config location failed", "bar", engine.getProperty("foo"));
+ }
+
+ @Test
+ public void velocityFactoryBeanWithResourceLoaderPath() throws Exception {
+ vefb.setResourceLoaderPath("file:" + resourcesPath);
+ vefb.afterPropertiesSet();
+ VelocityEngine engine = vefb.getObject();
+ Map<String, Object> model = new HashMap<String, Object>();
+ model.put("foo", "bar");
+ String merged = VelocityEngineUtils.mergeTemplateIntoString(engine, "simple.vm", "utf-8", model).trim();
+ assertEquals("resource loader failed", "foo=bar", merged);
+ }
+
+ @Test // SPR-12448
+ public void velocityConfigurationAsBean() {
+ DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
+ RootBeanDefinition loaderDef = new RootBeanDefinition(SpringResourceLoader.class);
+ loaderDef.getConstructorArgumentValues().addGenericArgumentValue(new DefaultResourceLoader());
+ loaderDef.getConstructorArgumentValues().addGenericArgumentValue("/freemarker");
+ // RootBeanDefinition configDef = new RootBeanDefinition(Configuration.class);
+ //configDef.getPropertyValues().add("templateLoader", loaderDef);
+ //beanFactory.registerBeanDefinition("freeMarkerConfig", configDef);
+ // assertThat(beanFactory.getBean(Configuration.class)).isNotNull();
+ }
+
+}
diff --git a/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java
new file mode 100644
index 00000000..68b440b7
--- /dev/null
+++ b/spring-velocity-support/src/test/java/org/apache/velocity/spring/test/VelocityEngineFactoryTests.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2019 the original author or authors.
+ *
+ * Licensed 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
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.velocity.spring.test;
+
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.spring.VelocityEngineFactory;
+import org.apache.velocity.spring.VelocityEngineUtils;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Juergen Hoeller
+ * @author Issam El-atif
+ * @author Sam Brannen
+ */
+public class VelocityEngineFactoryTests {
+
+ private static final String resourcesPath = System.getProperty("test.resources.dir");
+
+ @Test
+ public void testCreateEngineDefaultFileLoader() throws Exception {
+ VelocityEngineFactory factory = new VelocityEngineFactory();
+ factory.setResourceLoaderPath("."); // defaults to target/test-classes file resource loading
+ VelocityEngine engine = factory.createVelocityEngine();
+ Map<String, Object> model = new HashMap<String, Object>();
+ model.put("foo", "bar");
+ String merged = VelocityEngineUtils.mergeTemplateIntoString(engine, "simple.vm", "utf-8", model).trim();
+ assertEquals("file loader failed", "foo=bar", merged);
+ }
+
+ @Test
+ public void testCreateEngineDefaultClasspathLoader() throws Exception {
+ VelocityEngineFactory factory = new VelocityEngineFactory();
+ factory.setResourceLoaderPath("/"); // defaults to classpath resource loading
+ VelocityEngine engine = factory.createVelocityEngine();
+ Map<String, Object> model = new HashMap<String, Object>();
+ model.put("foo", "bar");
+ String merged = VelocityEngineUtils.mergeTemplateIntoString(engine, "simple.vm", "utf-8", model).trim();
+ assertEquals("classpath loader failed", "foo=bar", merged);
+ }
+
+ @Test
+ public void testCreateEngineCustomConfig() throws Exception {
+ VelocityEngineFactory factory = new VelocityEngineFactory();
+ factory.setResourceLoaderPath(".");
+ factory.setConfigLocation(new ClassPathResource("/velocity.properties"));
+ VelocityEngine engine = factory.createVelocityEngine();
+ assertEquals("custom config failed", "bar", engine.getProperty("foo"));
+ }
+
+}
diff --git a/spring-velocity-support/src/test/resources/simple.vm b/spring-velocity-support/src/test/resources/simple.vm
new file mode 100644
index 00000000..c9f453e0
--- /dev/null
+++ b/spring-velocity-support/src/test/resources/simple.vm
@@ -0,0 +1 @@
+foo=$foo
diff --git a/spring-velocity-support/src/test/resources/velocity.properties b/spring-velocity-support/src/test/resources/velocity.properties
new file mode 100644
index 00000000..8e09e197
--- /dev/null
+++ b/spring-velocity-support/src/test/resources/velocity.properties
@@ -0,0 +1,19 @@
+# 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.
+
+foo = bar
+
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
new file mode 100644
index 00000000..98c36625
--- /dev/null
+++ b/src/changes/changes.xml
@@ -0,0 +1,1408 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<document>
+ <properties>
+ <title>Changelog</title>
+ </properties>
+
+ <body>
+ <release version="2.3" date="2021-02-27">
+ <action type="fix" dev="cbrisson" issue="VELOCITY-927">
+ Fix parser bug (newline and space inside an empty inline map definition).
+ </action>
+ <action type="new" dev="cbrisson" issue="VELOCITY-933">
+ Backported Spring framework Velocity Engine integration classes from Spring 4.x, in the new module <code>spring-velocity-support</code>.
+ </action>
+ <action type="fix" dev="wglass" issue="VELOCITY-931">
+ Let SecureUberspector block methods on ClassLoader and subclasses.
+ </action>
+ <action type="new" dev="cbrisson" due-to="mgrigorov">
+ Added Travis CI.
+ </action>
+ <action type="fix" dev="cbrisson" due-to="donnerpeter">
+ Don't leak classes via Stop.STOP_ALL stack trace.
+ </action>
+ </release>
+ <release version="2.2" date="2020-02-02">
+ <action type="new" dev="cbrisson">
+ Deprecated 2.1 flag <code>velocimacro.arguments.preserve_literals</code> in favor of
+ the new <code>valocimacro_enable_bc_mode</code> flag, which will also, like 1.7 does,
+ use global context values as defaults for missing macro arguments which do not have
+ an explicit default value.
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-926">
+ Fixed regression: Macro arguments names cannot collide with external references names
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-925">
+ Fixed macro calls without parenthesis eating the following newline in BC mode
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-924">
+ Fixed bad cache handling for java.lang.Class methods
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-923">
+ Fixed parser regression when || follow a Velocity expression
+ </action>
+ <action type="add" dev="cbrisson">
+ Added BigInteger and BigDecimal implicit conversions
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-919">
+ Fixed abnormal suppression of zero-width whitespaces
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-917">
+ Added an example of how to build a customized parser where the '#', '$', '*' and '@'
+ can be replaced by any character
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-916">
+ Added the <code>runtime.log.track_locations</code> debugging flag (disabled by default), which:
+ <ul>
+ <li>display a VTL stack trace on any error</li>
+ <li>Implement template location tracking with slf4j MDC tags: Once activated, an MDC-aware logger will be
+ able to display the <code>file</code>, <code>line</code> and <code>column</code> MDC tags</li>
+ </ul>
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-915">
+ Added the following 1.7.x backward compatibility flags:
+ <ul>
+ <li><code>event_handler.invalid_references.quiet</code> - warn on invalid quiet references</li>
+ <li><code>event_handler.invalid_references.null</code> - warn on null references</li>
+ <li><code>event_handler.invalid_references.tested</code> - warn on invalid references tested with <code>#if()</code></li>
+ </ul>
+ Those three flags are false by default, but can be set to true to mimic 1.7.x behavior
+ </action>
+ <action type="fix" dev="cbrisson">
+ Introspection: favor non-vararg methods on ambiguities as does the Java compiler
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-912">
+ Also allow hyphen in subproperties when the corresponding backward compatibility flag is on
+ </action>
+ </release>
+ <release version="2.1" date="2019-03-15">
+ <action type="fix" dev="cbrisson" issue="VELOCITY-909">
+ Reorganization of configuration properties key names, for clarity and consistency,
+ see the <a href="configuration-property-changes-in-2.1.html">table of correspondance</a>
+ </action>
+ <action type="fix" dev="cbrisson">
+ Rendering of arrays should display their content, as for lists
+ </action>
+ <action type="fix" dev="cbrisson">
+ Enhance space gobbling ("lines" mode): do not eat ending newline when directive doesn't start after a newline
+ </action>
+ <action type="fix" dev="cbrisson">
+ Fix unary negate
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-892">
+ Deprecate the Class to Class CoversionHandler in favor of a Class to Type TypeConversionHandler, which allows
+ to specity converters towards generic types specializations. The minimum JDK is now 1.8
+ </action>
+ <action type="fix" dev="michaelo" issue="VELOCITY-908">
+ Rely on Locale.ROOT for lowercase/uppercase operations
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-904">
+ Added the <code>velocimacro.preserve.arguments.literals</code> flag (false by default), for backward compatibility.
+ When true, for any macro argument that evaluates to null, the macro will display the provided argument literal representation
+ instead of the literal argument reference
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-903">
+ Default block for empty loops: <code>#foreach($i in []) loop block #else empty #end</code>
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-888">
+ Include proper OSGi bundle informations in manifest files
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-898">
+ Alternate reference values: <code>${foo|'foo'}</code> evaluates to false whenever boolean evaluation of $foo is false
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-889">
+ Fix parser regression in #macro whitespaces handling
+ </action>
+ <action type="add" dev="cbrisson" issue="VELOCITY-542">
+ Added a new 'parser.allow_hypen_in_identifiers' boolean property (false per default) to (dis)allow '-' in reference identifiers
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-896">
+ Fix parsing of a terminal hash or dollar sign in sing litteral and template
+ </action>
+ <action type="fix" dev="cbrisson" issue="VELOCITY-895">
+ Implicit conversion to numbers in integer ranges
+ </action>
+ </release>
+ <release version="2.0" date="2017-08-06">
+
+ <action type="fix" dev="cbrisson">
+ Fix DataSourceResourceLoader for UTF-8 correct handling in VARCHAR/CLOB columns, and for connection / statement / result set life cycle
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Allow expressions inside []: <code>$foo[$bar + 1]</code>
+ </action>
+
+ <action type="add" dev="cbrisson">
+ New strategy for reference boolean evaluation:
+ <ol>
+ <li>return false for a null object</li>
+ <li>return its value for a Boolean object, or the result of the <code>getAsBoolean()</code> method if it exists.</li>
+ <li>if <code>directive.if.emptycheck</code> is false (true by default), stop here and return true.</li>
+ <li>check for emptiness:
+ <ul>
+ <li>return whether an array is empty.</li>
+ <li>return whether <code>isEmpty()</code> is false (covers String and all Collection classes).</li>
+ <li>return whether <code>length()</code> is zero (covers CharSequence classes other than String).</li>
+ <li>returns whether <code>size()</code> is zero.</li>
+ <li>return whether a Number <b>strictly</b> equals zero.</li>
+ </ul>
+ </li>
+ <li>check for emptiness after explicit conversion methods:
+ <ul>
+ <li>return whether the result of <code>getAsString()</code> is empty (and false for a null result) if it exists.</li>
+ <li>return whether the result of <code>getAsNumber()</code> <b>strictly</b> equals zero (and false for a null result) if it exists.</li>
+ </ul>
+ </li>
+ </ol>
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Reviewed event handling API:
+ <ul>
+ <li>added a Context argument for all events</li>
+ <li>got rid of the Executor pattern ; event handlers are directly called by the cartridge</li>
+ </ul>
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Removed references to ExtProperties from the engine configuration API.
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Default encoding is now UTF-8.
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Make Velocity use the base logger namespace 'org.apache.velocity' unless specified with runtime.log.name in the configuration, and have the runtime instance log with this base namespace, and other modules log with children namespaces:
+ <ul>
+ <li>directive, and velocity.directive.[directivename]</li>
+ <li>parser</li>
+ <li>loader and loader.[loadername]</li>
+ <li>macro</li>
+ <li>rendering</li>
+ <li>event</li>
+ </ul>
+ Get rid of UberspectLoggable interface.
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Added unary negate math operator
+ </action>
+
+ <action type="add" dev="cbrisson">
+ Add a configurable space gobbling feature, to control indentation in the generated code.
+ <br/>
+ Possible values for the 'space.gobbling' configuration key:
+ <ul>
+ <li><code>none</code> : no space gobbling at all</li>
+ <li><code>bc</code> : Velocity 1.x backward compatible space gobbling</li>
+ <li><code>lines</code> (the default) : gobbles whitespaces and endline from lines containing a single VTL directive</li>
+ <li><code>structured</code> : like 'lines', but also fixes indentation in children text blocks</li>
+ </ul>
+ </action>
+
+ <action type="add" dev="cbrisson">
+ added a new pluggable ConversionHandler class which, by default, converts method arguments as needed between main basic Java data types (boolean, numbers and strings)
+ </action>
+
+ <action type="add" dev="cbrisson">
+ added a runtime.string.interning option to trigger Java String interning on or off
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-841" due-to="Jarkko Viinamäki">
+ Applied Jarkko memory-saving patch which frees tokens while parsing. Macros now use a call by sharing convention.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-843">
+ support $array.empty, as for $list.empty
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-833" due-to="Jarkko Viinamäki">
+ avoid useless string calculation in ASTStringLiteral
+ </action>
+
+ <action type="add" dev="cbrisson">
+ nicified AST tree debug output
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-830">
+ fix parsing of $obj._method()
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-827" due-to="Dawid Weiss">
+ loading default properties should not prepend '/' and should use classloader to get resource stream
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-825">
+ Allow conversion of method args from String to Enum constant
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-815" due-to="Oswaldo Hernandez">
+ Applied performance patch for MapGetExecutor: it's faster to directly use object
+ instance rather than to inspect all public interfaces
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-799">
+ The new configuration property context.autoreference.key, if present, allows to specify the name
+ of the reference under which the context is accessible in itself
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-746">
+ MethodExceptionEventHandler now provide template location infos
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-797" due-to="Jarkko Viinamäki">
+ Attach macros to their defining template. Also fixes VELOCITY-776. Thanks to Simon Kitching for the multithreading testcase.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-820" due-to="Robert Fuller">
+ have #foreach honnor the Closeable interface on the iterator
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-804" due-to="Felipe Maschio">
+ Don't leave 'initializing' to true if a problem occurs during initialization.
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-790">
+ Remove dependency upon commons-collections-3 (except at compile-time for deprecated methods and classes
+ which are needed for backward compatibility), use our own ExtProperties object.
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-735" due-to="Dishara Wijewardana">
+ Add a first implementation for the JSR 223 standard scripting interface.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-809">
+ Fix Template default encoding initialization problem.
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-793">
+ The ResourceLoader API now provides a Reader rather than an InputStream.
+ Also fixes VELOCITY-599.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-553">
+ InvalidReferenceHandler events should not be triggered by quiet references, null values,
+ or by references testing inside #if / #elseif. Thanks to Renato Steiner
+ for his testcase.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-266">
+ Take advantage of the major version jump to enforce String keys
+ in internal Context API
+ </action>
+
+ <action type="fix" dev="sdumitriu" issue="VELOCITY-863" due-to="Mike Kienenberger">
+ Fix regression: #set&lt;tab&gt;left-paren no longer valid grammar
+ Patch from Mike Kienenberger applied + added test
+ </action>
+
+ <action type="add" dev="cbrisson">
+ switch to slf4j logging facade
+ </action>
+
+ <action type="fix" dev="cbrisson">
+ fix parser for '$map{key}' text rendering (StackOverflow 32805217)
+ </action>
+
+ <action type="fix" dev="sdumitriu" issue="VELOCITY-869">
+ Vulnerability in dependency: commons-collections: 3.2.1
+ </action>
+
+ <action type="fix" dev="sdumitriu" issue="VELOCITY-870">
+ Exception displayed when trying to loop over an Iterable private class
+ </action>
+
+ <action type="fix" dev="sdumitriu" issue="VELOCITY-871">
+ #foreach should work over any Iterable class
+ </action>
+
+ <action type="fix" dev="sdumitriu">
+ Catch exceptions raised during chainable uberspector initialization
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-542">
+ Hypen is no more allowed in variable names
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-706">
+ Method arguments can now be expressions
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-819" due-to="Jarkko Viinamäki">
+ Parser generation from the .jjt file is now handled by Maven
+ </action>
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-12" due-to="Candid Dauth">
+ Add a public fields Uberspector
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-814" due-to="Darren Cruse">
+ Support query modification in DataSourceResourceLoader
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-813" due-to="Oswaldo Hernandez">
+ Add clear() to ResourceCache
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-692">
+ Make #if() return false for empty strings and empty collections
+ </action>
+
+ <action type="add" dev="apetrelli">
+ Add SLF4J logging option
+ </action>
+
+ <action type="add" dev="apetrelli">
+ Use Maven 2 as build system
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-785" due-to="Jarkko Viinamäki">
+ Fixed quotes escaping so that doubling single quotes only works when enclosing quotes are single quotes
+ (and same behaviour for double quotes)
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-751">
+ Removed all remaining "throws Exception" clauses and the now useless ExceptionUtils class.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-731">
+ Remove directive.if.tostring.nullcheck crutch with intent
+ to replace testing of toString() null status with support for
+ toBoolean() (or similar) method on objects being "#if'd".
+ </action>
+
+ <action type="add" dev="nbubna">
+ Remove directive.set.null.allowed and instead always allow null to be
+ #set to references. This also means removing all NullSetEventHandler
+ code, since it was only in play when nulls were not allowed to be #set.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-704">
+ Remove features deprecated in 1.7 ($velocityCount, $velocityHasNext,
+ velocimacro.context.localscope, directive.evaluate.context.class and
+ all internal supporting code (ProxyVMContext, EvaluateContext, etc).
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-697">
+ Add ability to specify default values for macro parameters, e.g.;
+ #macro(foo bar=1)
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-695">
+ Add ability to place line comments next to macro parameter definitions.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-690">
+ Block directives no longer require parenthesis so #@foo #end is now allowed.
+ Also, brackets now work with Block Macros so #{@foo}bar#end works.
+ </action>
+
+ <action type="fix" dev="nbubna">
+ Removed all deprecations (Anakia, Texen, VelocityServlet, etc.)
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-687">
+ Changed macro arguments to be pass by value.
+ </action>
+
+ </release>
+
+ <release version="1.7" date="2010-11-29">
+ <action type="fix" dev="cbrisson" issue="VELOCITY-785" due-to="Jarkko Viinamäki">
+ Fixed quotes escaping so that doubling single quotes only works when enclosing quotes are single quotes
+ (and same behaviour for double quotes)
+ </action>
+
+ <action type="add" dev="nbubna">
+ Add access to template and directive debugging info via $/scope/.info.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-766">
+ LogManager now catches UnsupportedOperationExceptions during LogChute init.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-760" due-to="Jarkko Viinamäki">
+ Ensure that DataSourceResourceLoader closes PreparedStatements.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-753" due-to="Matt Ryall">
+ Mark optional dependencies as such in OSGi bundle manifest.
+ </action>
+ </release>
+
+ <release version="1.7-beta1" date="2010-04-10">
+ <action type="add" dev="nbubna" issue="VELOCITY-694">
+ Add support for OSGi-ready manifests in build/release tasks.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-753">
+ When comparing parameter types during method mapping, make Object always less specific than other types.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-759" due-to="Rachid">
+ Avoid NPEs in case of bad #foreach config.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-755">
+ Use LinkedHashMap to maintain order of VTL-created maps.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-555" due-to="Jarkko Viinamäki">
+ Treat \ as non-special in string literals, except when specifying unicode sequences.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-555" due-to="Jarkko Viinamäki">
+ Allow escaping of quotes (single or double) within string literals by doubling them ("" or '').
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-758" due-to="Jarkko Viinamäki">
+ Give #parse better log/exception messages and give IncludeEventHandler a chance to handle null #parse args.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-754,VELOCITY-729" due-to="Jarkko Viinamäki">
+ Fix $.x parsing failures (particularly helpful for jQuery users).
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-744">
+ Log Velocimacro additions at debug level, as in pre-1.6 versions.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-742" due-to="Jarkko Viinamäki">
+ Add removeDirective(name) and loadDirective(classname) methods to allow runtime changes to the directive set.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-727" due-to="Jarkko Viinamäki">
+ Throw an informative VelocityException when #define is given no parameter (instead of an ArrayIndexOutOfBoundsException).
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-728" due-to="Jarkko Viinamäki">
+ Throw an informative VelocityException when #parse is given no args (instead of an NPE).
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-704">
+ Changed #stop to a directive implementation (get it out of the parser)
+ and allow it to accept a message as an argument to be written to the logs
+ for debugging purposes.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-704">
+ Change the scoping behavior of Velocity, keeping it optional (everything global
+ scoped by default) but now providing an explicit namespace in content-containing
+ directives (like macros, #foreach, #parse, etc.) in which references that should
+ stay local may be kept. This is accompanied by numerous related changes, including:
+ &lt;ul&gt;
+ &lt;li&gt;A Scope reference can now be automatically made
+ available within each content directive:
+ &lt;ul&gt;
+ &lt;li&gt;$template in Template.merge and #parse content&lt;li&gt;
+ &lt;li&gt;$macro in #macro&lt;/li&gt;
+ &lt;li&gt;$foreach in #foreach&lt;/li&gt;
+ &lt;li&gt;$evaluate in #evaluate or Velocity.evaluate(...) content&lt;/li&gt;
+ &lt;li&gt;$define in #define&lt;/li&gt;
+ &lt;li&gt;$&amp;lt;amacro&amp;gt; in #@&amp;lt;amacro&amp;gt;
+ (where &amp;lt;amacro&amp;gt; is the name of a macro being called with a body)&lt;/li&gt;
+ &lt;/ul&gt;
+ &lt;/li&gt;
+ &lt;li&gt;For performance and compatibility these are all off by
+ default, *except* for $foreach. The others may be enabled by setting a velocity property like:
+ &lt;br/&gt;&lt;code&gt;macro.provide.scope.control = true&lt;/code&gt;&lt;/li&gt;
+ &lt;li&gt;When scopes of the same type are nested make the parent Scope available
+ through the child (e.g. $foreach.parent or $foreach.topmost).&lt;/li&gt;
+ &lt;li&gt;When a Scope reference overrides an existing reference that is not a Scope,
+ make it available through the Scope (e.g. $foreach.replaced).&lt;/li&gt;
+ &lt;li&gt;Made #break work in any of the above scopes to exit the most immediate one,
+ unless a different scope is provided as an argument
+ (e.g. #break($macro) or #break($foreach.topmost)).&lt;/li&gt;
+ &lt;li&gt;Deprecated $velocityCount;
+ please use $foreach.count or $foreach.index.&lt;/li&gt;
+ &lt;li&gt;Deprecated $velocityHasNext;
+ please use $foreach.hasNext, $foreach.first or $foreach.last&lt;/li&gt;
+ &lt;li&gt;Deprecated velocimacro.context.localscope setting;
+ please get/set local #macro references as members of the provided $macro
+ scope control instead. (e.g. #set( $macro.foo = 'bar' ) and $macro.foo ).&lt;/li&gt;
+ &lt;li&gt;Deprecated directive.evaluate.context.class setting;
+ please get/set local #evaluate references as members of the provided $evaluate
+ scope control instead. (e.g. #set( $evaluate.foo = 'bar' ) and $evaluate.foo ).&lt;/li&gt;
+ &lt;/ul&gt;
+ All of the deprecated features will be removed in Velocity 2.0.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-681">
+ Calling #set on a macro argument (for which a #set-able reference
+ was passed) will no longer propagate the new value to the original
+ reference, but merely set the value of the macro argument reference.
+ This was an obscure, infrequently used feature and was decided to be
+ more problematic and unpredictable than useful.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-688">
+ In strict mode attempts to render references that evaluate to
+ null will throw an exception. In the user wishes to suppress
+ the exception and render nothing then the reference can be
+ preceeded with '$!' as in $!foo.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-673">
+ The non default VelocityEngine construtors now do not initialize the runtime
+ system so that properties may be set after construction. Also fixes an
+ Initialization race condition.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-685" due-to="Jarkko Viinamäki">
+ Make velocimacro.arguments.strict=true work with block macros.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-623">
+ Added a property for changing escape behavior such that putting a forward
+ slash before a reference or macro always escapes the reference or macro and
+ absorbs the forward slash regardless if the reference or macro is defined.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-676" due-to="Jarkko Viinamäki">
+ Fix StringIndexOutOfBoundsException caused by #[[##x]]# (line comment on
+ same line as end of textblock).
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-675" due-to="Jarkko Viinamäki">
+ Fix NPE caused by #@foo (w/o #end) in template.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-668" due-to="Jarkko Viinamäki">
+ Minor performance tweaks based on Findbugs findings
+ </action>
+
+ <action type="fix" dev="byron" issue="VELOCITY-667">
+ Pre 1.6 behavior of specifying #macro without parenthesis
+ would throw a VelocityException has been restored.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-666" due-to="Jarkko Viinamaki"><![CDATA[
+ Add support for calling velocimacros with body content by prefixing the macro
+ name with @.
+ (e.g. #macro( bold )<strong>$bodyContent</strong>#end #@bold()any valid VTL here#end)
+ The $bodyContent reference may be renamed via a velocimacro.body.reference
+ setting in your velocity.properties.
+ ]]></action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-661" due-to="Jarkko Viinamaki">
+ Added #[[this is included in output but not parsed]]# syntax to replace
+ and deprecate the much more limited #literal directive
+ (which required parse-able content).
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-663">
+ Re-implement #stop so that it stops template execution and rendering. This
+ Also addresses a performance bottleneck detected in the old implementation.
+ #stop is a general use directive now, and not just for debugging.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-662">
+ Reduce performance bottleneck with the referenceInsert event handler call
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-656">
+ Better error reporting when toString() throw an exception when testing
+ an #if conditional. For example #if($foo)
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-406">
+ Added bracketed index syntax, $foo[0], or #set($foo[0] = 1)
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-659">
+ Removed java.lang.Exception from throws clause of Velocity
+ and VelocityEngine API. Also removed IOException so that all
+ method calls use unchecked exceptions.
+ </action>
+
+ <action type="add" dev="nbubna">
+ Removed obsolete WebMacro conversion tool and experimental Veltag
+ (which has been replaced by VelocityViewTag from VelocityTools).
+ </action>
+
+ </release>
+
+ <release version="1.6.4" date="2010-05-10">
+ <action type="fix" dev="nbubna" issue="VELOCITY-750">
+ Fix double-checked locking in RuntimeInstance's optional lazy-init.
+ </action>
+ <action type="fix" dev="nbubna" issue="VELOCITY-718">
+ Fix 100% CPU loop hang under simultaneous HashMap calls in ClassMap
+ due to classic bug in Sun's implementation. Now uses
+ ConcurrentHashMap when available and Hashtable otherwise.
+ </action>
+ </release>
+
+ <release version="1.6.3" date="2009-12-16">
+ <action type="fix" dev="nbubna" issue="VELOCITY-731" due-to="Jorgen Rydenius">
+ Add directive.if.tostring.nullcheck config switch to match
+ past performance for those who do not need to check whether
+ $foo.toString() returns null when doing #if( $foo ).
+ </action>
+ </release>
+
+ <release version="1.6.2" date="2009-03-19">
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-702">
+ Fix obscure caching problem in multiple resource loader situations
+ where resources may exist in more than one loader and appear and
+ disappear from loaders.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-701">
+ Fix old regression from 1.4 in supporting methods declared as abstract
+ in a public class but implemented in a non-public class.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-693">
+ Fix problem with FileResourceLoader's resourceExists() when
+ configured w/multiple paths.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-689">
+ Fix ClassMap introspection bug introduced in 1.5,
+ where public super-interface methods implemented in
+ protected or private classes were unreachable.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-681">
+ Fix regression in proxying of macro argument #set calls.
+ Note that in 1.7, calling #set on a macro argument (for
+ which a #set-able reference was passed) will not propagate
+ the new value to the original reference, but merely set the
+ value of the macro argument reference.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-682">
+ Fix loss of inline macros when #evaluate is used.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-554" due-to="Adrian Tarau">
+ Fix name of sources jar for maven deployment.
+ </action>
+
+ <action type="fix" dev="byron" issue="VELOCITY-667">
+ Pre 1.6 behavior of specifying #macro without parenthesis
+ would throw a VelocityException has been restored.
+ </action>
+
+ <action type="add" dev="byron" issue="VELOCITY-656">
+ Better error reporting when toString() throw an exception
+ when testing an #if conditional. For example #if($foo)
+ </action>
+
+ <action type="fix" dev="byron" issue="VELOCITY-658" due-to="Jarkko Viinamäki">
+ Fix $velocityHasNext so that it works in nested foreach blocks.
+ </action>
+
+ <action type="fix" dev="byron" issue="VELOCITY-645">
+ Throw an exception in strict mode when >=, &lt;=, &lt; or >
+ comparisons can't be made.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-657">
+ Fix $velocityHasNext so it is not always true.
+ </action>
+
+ </release>
+
+ <release version="1.6.1" date="2008-12-15">
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-654" due-to="Byron Foster">
+ Correct/enhance template name reporting for #include and #parse.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-653" due-to="Byron Foster">
+ Removed redundant error message in ASTReference.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-615" due-to="Steve O'Hara">
+ Fix limitation in new macro implementation that resisted #set calls
+ to change references defined as arguments for the macro.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-651" due-to="Stephan Schmid">
+ Fix vararg bugs when calling overloaded methods like get(String,String,Object[])
+ and get(String,List). (e.g. in VelocityStruts' MessageTool)
+ </action>
+
+ </release>
+
+ <release version="1.6" date="2008-12-01">
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-649" due-to="Byron Foster">
+ Fix NPE when null value is passed to array/vararg method.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-644" due-to="Byron Foster">
+ Track template name in Node, to get proper template name in the exception
+ message when a reference in a macro causes that exception.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-352" due-to="Gonzalo Diethelm, Joni Salonen">
+ Update Finnish and Spanish User Guide sections on division operation.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-641" due-to="Will Glass-Husain">
+ Fix classpath for XMLApp example.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-617" due-to="Adrian Tarau">
+ Fix references to Oro dependency to make it clear that it is
+ only necessary for certain reference event handlers.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-640" due-to="Will Glass-Husain">
+ Remove old Anakia/Texen docs and examples.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-642" due-to="Ilkka Priha">
+ Fix bug in vararg support where an array passed to a vararg method
+ was being wrapped in another array.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-638" due-to="Benjamin Bentmann">
+ Locked down versions for reporting plugins in Maven POM.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-466" due-to="Adrian Tarau">
+ Add Maven-Ant tasks for installing and deploying Maven artifacts, including
+ previously missing source and javadoc jars (VELOCITY-554).
+ </action>
+
+ </release>
+
+ <release version="1.6-beta2" date="2008-10-26">
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-632" due-to="Sebb">
+ Fix User Guide's Table of Contents anchor links.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-631" due-to="Byron Foster">
+ Fix parser bug that prevented references from immediately preceding #set directives.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-630" due-to="Byron Foster">
+ Revert change made for VELOCITY-285, as it broke pass-by-name nature
+ of macro arguments.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-629" due-to="Byron Foster">
+ Fix line numbers in string literals.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-627" due-to="Byron Foster">
+ Fix line number for exceptions with body of #foreach directive.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-626" due-to="Byron Foster">
+ Log full macro stack when a RuntimeException occurs in a template.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-622" due-to="Byron Foster">
+ Format template and line/column numbers consistently in error and log messages.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-624" due-to="Byron Foster">
+ Don't override pre-configured log levels for JdkLogChute or Log4JLogChute.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-618" due-to="Byron Foster">
+ Add optional 'runtime.references.strict' property that has Velocity throw
+ exceptions when it encounters undefined references or velocimacros in a template.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-554" due-to="Adrian Tarau">
+ Inlude Maven Ant tasks jar in downloads and add support for downloading from Maven2 repos.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-585" due-to="Jon Seymour">
+ Fix problem with single backslash in interpolated (double-quoted) strings.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-616" due-to="Alik">
+ Fix escaping of bracketed directives (e.g. \#{if}($foo)$bar\#{end}).
+ </action>
+
+ </release>
+
+ <release version="1.6-beta1" date="2008-09-22">
+
+ <action type="add" dev="cbrisson" issue="VELOCITY-588" due-to="Vincent Massol, Sergiu Dumitriu">
+ Uberspectors chaining
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-585" due-to="Tim White">
+ Add ability to set a connection timeout for URLResourceLoader.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-227" due-to="Charles Morehead">
+ Make it easier to override ResourceFactory.getResource(...) in ResourceManagerImpl.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-544" due-to="Adam Bishop, Jarkko Viinamaki">
+ Fix BooleanPropertyExecutor to recognize "public Boolean isFoo" properties.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-439" due-to="Tassos Bassoukos, Henning Schmiedehausen">
+ Add a separate resourceExists(String name) method to ResourceLoader(s) to improve performance.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-355" due-to="Geoffrey Lowney, Dan Ertman">
+ Fix missing # and $ characters within #literal() directive output.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-102" due-to="Juozas Baliuka, Adrian Tarau, Jim Seach">
+ Add support for static utility classes: context.put("math", Math.class) -> $math.sin(0)
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-174" due-to="Andrew Tetlaw">
+ Add #define directive set an unrendered block of VTL as a reference.
+ (e.g. #define( $foo )Hello, $bar!#end #set( $bar = 'world') $foo -> Hello, world!)
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-612" due-to="Jarkko Viinamaki">
+ Add #break directive to exit #foreach loops early.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-607" due-to="Jarkko Viinamaki">
+ Fixed major performance issues with Velocimacros.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-606" due-to="Jarkko Viinamaki">
+ Removed many, various performance bottlenecks and concurrency issues. (Also see VELOCITY-595)
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-600" due-to="Adrian Tarau">
+ New VTL syntax $velocityHasNext within #foreach loops
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-580" due-to="Marnix van Bochove">
+ Multi-line comments in macros were rendering extra *# when evaluated.
+ </action>
+
+ <action type="fix" dev="cbrisson" issue="VELOCITY-570" due-to="Ronald Klop">
+ Have VelocityCharStream use an exponential buffer expansion rate.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-566" due-to="Etienne Massip">
+ Prevent exception due to simultaneous rendering of macros.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-533" due-to="Christopher Schultz">
+ New VTL syntax allows arrays to be treated like fixed length lists.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELOCITY-534">
+ Added support for varargs in method calls.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-536" due-to="Lei Gu, Dima Tkach">
+ Prevent NPE when template is parsed simultaneously by multiple users. (may apply only to macros).
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-362" due-to="Supun Kamburugamuva">
+ Users can now include libraries of macros with #parse in a template.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-529" due-to="Supun Kamburugamuva">
+ Macro libraries can now be selected programatically when merging templates.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-452" due-to="Alexey Panchenko">
+ Catch IllegalArgumentException when invoking method.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-538">
+ Fixed parse error for map definitions ending with a reference.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-297" due-to="Supun Kamburugamuva">
+ Add new property velocimacro.max.depth which limits max macro calling depth.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-547" due-to="Adam Heath">
+ Add ability to add directives programmatically
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-548" due-to="Adam Heath">
+ Allow user directives to have no content.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-551" due-to="Michiel Toneman">
+ IncludeNotFound event handler was displaying "not found" template.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-541">
+ Fix StringResourceLoader to make it possible to use more than one loader per VM.
+ </action>
+
+ <action type="fix" dev="nbubna" issue="VELOCITY-520" due-to="Stepan Koltsov">
+ Make unicode encoding work in velocity templates.
+ </action>
+
+ <action type="add" dev="nbubna">
+ Add a ServletLogChute that allows logging through the servlet API. Ported from the
+ Velocity Tools project.
+ </action>
+
+ <action type="add" dev="nbubna">
+ Add a CommonsLogLogChute that allows logging through commons-logging.
+ </action>
+
+ <action type="update" dev="wglass">
+ Deprecate integrated texen and anakia, factor it out into separate jars.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-509">
+ Added new directive #evaluate() to dynamically evaluate VTL.
+ </action>
+ </release>
+
+ <release version="1.5" date="2007-01-28">
+ <action type="fix" dev="wglass" issue="VELOCITY-516" due-to="Vincent Massol">
+ Fix to SecureUberspector to work properly with #foreach and iterators.
+ </action>
+
+ <action type="add" dev="henning" issue="VELOCITY-191" due-to="Aki Nieminen">
+ Make FileResourceLoader unicode aware to allow skipping over BOM markers
+ like those created by Windows Notepad. This is a workaround for a Java
+ bug, where Java itself does not recognize the UTF-8 BOM as defined by
+ the unicode standard.
+ </action>
+
+ </release>
+
+ <release version="1.5-beta2" date="2006-11-24">
+
+ <action type="add" dev="henning" issue="VELOCITY-183" due-to="Eelco Hillenius">
+ New StringResourceLoader can retrieve templates from repository of in-memory Strings.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-493" due-to="Claude Brisson">
+ RuntimeInstance.getProperty now returns value set with RuntimeInstance.setProperty,
+ even before initialization.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-435">
+ When macros have incorrect number of arguments, if property
+ "velocimacro.arguments.strict" is set to true a ParseErrorException
+ will be thrown.
+ </action>
+
+ <action type="add" dev="henning" issue="VELOCITY-414" due-to="Matthijs Lambooy">
+ MethodInvocationException now contains line, column, template name
+ allowing application to produce more useful error messages.
+ </action>
+
+ <action type="fix" dev="henning" issue="VELOCITY-24">
+ Fixed race condition in template retrieval that caused macros to
+ fail under simultaneous load.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-423">
+ New event handler InvalidReferenceHandler allows application
+ to catch invalid references. Sample implementation collects
+ them in list and optionally throws exception.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-179">
+ New, optional SecureIntrospector prohibits methods that involve manipulation of classes, classloaders
+ or reflection objects. Use this introspector to secure Velocity against a risk of
+ template writers using reflection to perform malicious acts.
+ </action>
+
+ <action type="fix" dev="henning" issue="VELOCITY-458">
+ Removed Serializable from InternalContextBase, because one of the members is not serializable anyway so this never worked (Found by Findbugs).
+ </action>
+
+ <action type="fix" dev="henning" issue="VELOCITY-449" due-to="Alexey Panchenko">
+ Add an additional pair of Executors that are smart about Map.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-453" due-to="Alexey Panchenko">
+ Method caching now uses consistent keys.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-459" due-to="Stephen Haberman">
+ Change the meaning of localscope for macros to allow access to references from
+ calling context.
+ </action>
+
+ <action type="add" dev="henning">
+ Add a test for the DataSourceResource Loader.
+ </action>
+
+ <action type="add" dev="henning">
+ Fix a problem in the DataSourceResource Loader, removing a potential security issue with SQL injection.
+ </action>
+
+ <action type="add" dev="henning">
+ Build now creates the MD5 and SHA1 checksums for archives and jars.
+ </action>
+
+ <action type="fix" dev="henning">
+ Fix a number of issues reported by running FindBugs on the Velocity source.
+ </action>
+ </release>
+
+ <release version="1.5-beta1" date="2006-09-13">
+ <action type="fix" dev="wglass" issue="VELOCITY-438" due-to="Stephen Haberman">
+ Stop references from calling object.toString() twice.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-429" due-to="">
+ Pass through all runtime exceptions. Among other benefits, this
+ allows plugins to throw a runtime exception to signify an application
+ level problem in the calling application.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-98" due-to="Michal Chmielewski">
+ When #include was followed by #parse with the same file name, a ClassCastException was thrown.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-425" due-to="Llewellyn Falco">
+ Wrapped exceptions now have Cause property set on JDK 1.4. (note that Velocity
+ continues to run under JDK 1.3).
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-418" due-to="Jason Weinstein">
+ When Velocity is initialized, default.properties stream was not being closed. This
+ made it difficult to undeploy webapps on Windows with Velocity unpacked.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-151" due-to="Kirk Wolf">
+ Upgraded to latest commons collection, fixing problem with non-recognition
+ of configuration file encoding in rare circumstances.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-370" due-to="Reggie Riser">
+ The Introspector could throw a NPE when a parameter to an overloaded method was null.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-381" due-to="Llwellyn Falco and Dan Powell">
+ If toString() returned null in a silent reference then "null" was displayed.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-359" due-to=""><![CDATA[
+ Fixed bug in which empty body for #if (e.g. <code>#if(some expression)#end</code>
+ caused ParseException.
+ ]]></action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-222" due-to="">
+ Added javacc task to build.xml simplifying modification process
+ for editing syntax files.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-374" due-to="">
+ Velocity Engine was throwing NPE when used without a call to
+ init(). Now gives a more meaningful exception message.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-404" due-to="Llewellyn Falco">
+ Fixed problem with Uberspect Info class being created incorrectly.
+ Added template name to Info allowing better error reporting.
+ </action>
+
+ <action type="update" dev="wglass" issue="" due-to="">
+ Numerous improvements to the documentation. Reorganized table of
+ contents, moved community content to the Wiki, added article on using
+ Velocity in web applications.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-350" due-to="">
+ When testing objects in VTL for equality, if both objects are a number, use
+ number equality. If both objects are the same class, use the equals method.
+ New behavior: If objects are different classes, compare the String
+ representation of both objects rather than logging an error.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-272" due-to="">
+ Velocity would give error when last line of file was a ## comment.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-412" due-to="Malcolm Edgar">
+ Added method to retrieve application attributes.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-196" due-to="">
+ Velocity now searches in the current thread's context classloader
+ before the system classloader for all templates loaded with the
+ ClasspathResourceLoader and for all user-defined ResourceLoaders,
+ introspectors, event handlers, etc.
+ A typical use for this is to have Velocity in the application
+ container classpath while keeping templates and plugins in the
+ webapp classpath.
+ </action>
+
+ <action type="update" dev="wglass" issue="" due-to="Thomas Veith"><![CDATA[
+ #set now sets references to null when required. For backwards
+ compatibility this must be enabled by setting the configuration key
+ <code>directive.set.null.allowed</code> to true.
+ ]]></action>
+
+ <action type="add" dev="wglass" issue="" due-to="">
+ New optional event handler that escapes all references.
+ Regular expressions can be used to configure which references
+ have HTML, JavaScript, SQL, or XML escaping.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-154" due-to="">
+ New optional event handler implementation that forces #parse / #include to stay
+ in same directory as parent template.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-260" due-to="">
+ New event handler to modify behavior of #parse / #include.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-144" due-to="">
+ FileResourceLoader now accepts absolute path when configured to accept it.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-126" due-to="">
+ String containing "##" was treated as unterminated String.
+ </action>
+
+ <action type="add" dev="nbubna" issue="VELTOOLS-55" due-to="Charles Harvey">
+ Spruced up Geir's old URLResourceLoader and promoted it from the whiteboard to
+ the main distribution.
+ </action>
+
+ <action type="update" dev="henning" issue="VELOCITY-424" due-to="Malcom Edgar">
+ Throw Runtime exceptions from nodes up the chain.
+ </action>
+ <action type="update" dev="henning" issue="VELOCITY-426" due-to="Malcom Edgar">
+ Revert the split between org.apache.velocity.runtime.parser.node.Node and
+ org.apache.velocity.runtime.parser.Node. The parser now only uses ...parser.node.Node
+ because this change broke custom directives.
+ </action>
+
+ <action type="update" dev="nbubna" issue="VELOCITY-403" due-to="">
+ Made a lot of internal logging upgrades including: Deprecated LogSystem interface
+ and replaced it (and all its implementations) with a new LogChute interface and implementations,
+ added getLog() to RuntimeServices (and all its friends) to improve on and replace its now deprecated
+ logging methods, added a JdkLogChute as a 3rd default option for those using JDK 1.4+, and added a
+ StandardOutLogChute as final resort if other LogChute inits fail. See JIRA issues VELOCITY-403, VELOCITY-166,
+ VELOCITY-403,VELOCITY-166,VELOCITY-78, VELOCITY-157, VELOCITY-159, VELOCITY-193.
+ </action>
+
+ <action type="remove" dev="henning" issue="VELOCITY-401" due-to="">
+ Removed all J2EE build tasks. Now automatically detects availability
+ of javax.sql.Datasource (in JDK 1.4+) and builds DatasourceResourceLoader
+ when allowed.
+ </action>
+
+ <action type="add" dev="henning" issue="" due-to="">
+ ant build now downloads the required dependency jars from ibiblio.org
+ </action>
+
+ <action type="update" dev="henning" issue="VELOCITY-373" due-to="Malcolm Edgar">
+ Unified template name, line and column number reporting for ParserErrorException
+ </action>
+
+ <action type="remove" dev="henning" issue="" due-to="">
+ Dropped the non-functional Velocity compiler.
+ </action>
+
+ <action type="update" dev="henning" issue="" due-to="">
+ Started separating out the JavaCC generated parts of the Velocity Parser. Not yet complete to avoid user visible changes. Scheduled to be completed for 2.0
+ </action>
+
+ <action type="add" dev="henning" issue="" due-to="">
+ Contributed a maven build for Velocity
+ </action>
+
+ <action type="fix" dev="henning" issue="" due-to="">
+ Reworked the ant build to product only two jars: velocity.jar and velocity-dep.jar.
+ </action>
+
+ <action type="remove" dev="henning" issue="" due-to="">
+ Removed the Configuration class and all methods that references it. This class was deprecated since Velocity 1.1 and was scheduled to be gone for Velocity 1.3 or 1.4. Now
+ it was finally removed in 1.5.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-242" due-to="Peter Romianowski">
+ Added support for decimal numbers.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-284" due-to="Mike Rettig">
+ MethodInvocationException now consistently thrown
+ (previously was hidden when in parameter to Velocimacro).
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-109" due-to="">
+ Fixed problem in which foreach loop would fail to call overloaded method.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-86" due-to="">
+ Removed ERROR level log message "Can't find 'VM_global_library.vm'".
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-348" due-to="">
+ Anakia now generates consistent line endings based on platform. Requires upgrade to JDom 1.0.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-190" due-to="Peter Ryan">
+ Anakia can now be pre-loaded with custom context values from an optional XML file.
+ </action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-43" due-to="">
+ Directives can now be delimited with curly braces, for example #if($condition)something#{else}otherthing#{end}.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-254" due-to="Christopher Reck">
+ Nulls now handled appropriate within #foreach.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-324" due-to="Shinobu Kuwai">
+ Upgraded JavaCC to version 3.2, providing JDK 1.5 compatibility.
+ (Older version used keyword 'enum' which is reserved in JDK 1.5).
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-267" due-to="Matt Raible ">
+ DatasourceResourceLoader now allows injection of Datasource, allowing it to be used in Inversion of Control (IOC) frameworks.
+ </action>
+
+ <action type="fix" dev="wglass" issue="VELOCITY-218" due-to="">
+ #stop now works properly.
+ </action>
+
+ <action type="update" dev="wglass" issue="VELOCITY-196" due-to="Charles Oliver Nutter">
+ ClasspathResourceLoader now searches ContextClassLoader for template.
+ </action>
+
+ <action type="remove" dev="dlr" issue="VELOCITY-164" due-to=""><![CDATA[
+ Removed use of <a href="http://jakarta.apache.org/log4j/">Log4J's</a>
+ deprecated Category and Priority classes in favor of the corresponding
+ and supported Logger and Level. To update, replace necessary
+ references, and Category.getInstance() with Logger.getLogger().
+ ]]></action>
+
+ <action type="add" dev="wglass" issue="VELOCITY-152" due-to="James Taylor">
+ New Map literal syntax.
+ </action>
+
+ <action type="remove" dev="dlr" issue="" due-to="">
+ Removed the long-deprecated Log4JLogSystem. Never fear,
+ SimpleLog4JLogSystem remains.
+ </action>
+
+ <action type="update" dev="dlr" issue="" due-to=""><![CDATA[
+ Enhanced the implementation of ResourceCacheImpl using Jakarta Commons
+ Collections LRUMap class. The previous greedy implementation did not
+ set an upper bound for the cache size, meaning that cached resources
+ were never relinquished (a possible memory leak). You can continue to
+ use that behavior by setting the
+ <code>resource.manager.cache.size</code> for your cache to less than
+ 1.
+ ]]></action>
+
+ <action type="update" dev="geirm" issue="" due-to="Daniel Rall">
+ Took dan's modified SimpleLog4jLogSystem, and renamed Log4JLogSystem, and
+ put back old version of SimpleLog4JLogSystem, as deprecated. That way
+ we can move forward with an up-to-date version that uses Logger, and
+ for one release, be backwards compatile for the Category-using log4j
+ crowd.
+ </action>
+
+ <action type="remove" dev="wglass" issue="" due-to="">
+ Deprecated org.apache.velocity.tools.VelocityFormatter class in favor
+ of the various format classes in the Velocity Tools library.
+ </action>
+
+ <action type="remove" dev="dlr" issue="" due-to="">
+ Deprecated the org.apache.velocity.servlet.VelocityServlet class in
+ favor of org.apache.velocity.tools.view.servlet.VelocityViewServlet
+ from the Velocity Tools library. Servlet interaction is more a core
+ competency of the Velocity Tools package than of Velocity's
+ core.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-185" due-to="wglass">
+ Fix to BaseTestCase as suggested by Will Glass-Husain to handle line endings
+ </action>
+
+ <action type="update" dev="" issue="" due-to="henning">
+ Parameterized cache and mod time control in TexenTask based on patch from
+ Henning.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-150" due-to="wglass">
+ Fix to DatasourceResourceLoader - stop using the old Runtime singleton as
+ would leak a little memory for each instance of VelocityEngine created.
+ Hunted down by Will Glass-Husain.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-161" due-to="wglass">
+ SimplePool now removes elements from pool on a get(). NOTE : Previously, it left the
+ reference to the object in the pool.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-61" due-to="wglass">
+ Fixes problem with single line comment
+ embedded in a multi-line comment.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-221" due-to="">
+ Change for VELOCITY-221 and partial for VELOCITY-148, allowing newlines in
+ directives.
+ </action>
+
+ <action type="update" dev="geirm" issue="VELOCITY-148" due-to="">
+ Change to finish request VELOCITY-148, allowing '+' as a string concat. We'll have
+ to see how the community likes it.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-239" due-to="">
+ Didn't allow formal reference notation as first arg
+ to foreach.
+ </action>
+
+ <action type="update" dev="geirm" issue="" due-to="">
+ To make using w/ XML easier allow alternative logical operators 'and', 'or',
+ 'lt', 'gt', 'le', 'ge', 'eq', 'ne', 'not'.
+ </action>
+
+ <action type="update" dev="geirm" issue="" due-to="">
+ Allow newlines in strings.
+ </action>
+
+ <action type="fix" dev="geirm" issue="VELOCITY-148" due-to="">
+ Tiny fix to VelocityWriter to prevent a NPE if someone passes it a null
+ </action>
+
+ <action type="fix" dev="geirm" issue="" due-to="">
+ Anakia changes to accomodate finalization of JDOM API. In AnakiaJDOMFactory,
+ AnakiaTask, and OutputWrapper
+ </action>
+
+ <action type="update" dev="geirm" issue="" due-to="">
+ Added template, line and column info to MIEs thrown by ASTMethod
+ </action>
+
+ </release>
+ </body>
+</document>
diff --git a/velocity-custom-parser-example/pom.xml b/velocity-custom-parser-example/pom.xml
new file mode 100644
index 00000000..72c6ab73
--- /dev/null
+++ b/velocity-custom-parser-example/pom.xml
@@ -0,0 +1,284 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-parent</artifactId>
+ <version>2.4-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>velocity-custom-parser-example</artifactId>
+ <name>Apache Velocity Custom Parser Example</name>
+ <description>Custom Parser Example for Apache Velocity</description>
+
+ <packaging>pom</packaging>
+
+ <!--
+ This module demonstrates how to build a custom Velocity parser.
+ The proposed custom parser replaces '#' with '@' and '@' with '%'
+ so that it's suitable to use with Markdown template files, for instance.
+
+ The generated parser class is ${parser.package}.${parser.basename}Parser,
+ and must be specified at runtime using the Velocity property parser.class:
+ parser.class = foo.bar.MyCustomParser
+
+ Please note that all configurable chars (*, @, $, #) must be specified, even when similar to default ones.
+ -->
+
+ <properties>
+ <!-- whether to display debug logs while parsing -->
+ <parser.debug>false</parser.debug>
+ <!-- parser basename -->
+ <parser.basename>Custom</parser.basename>
+ <!-- parser package -->
+ <parser.package>org.apache.velocity.runtime.parser.custom</parser.package>
+ <!-- character to substitute to '*' -->
+ <parser.char.asterisk>*</parser.char.asterisk>
+ <!-- character to substitute to '@' -->
+ <parser.char.at>%</parser.char.at>
+ <!-- character to substitute to '$' -->
+ <parser.char.dollar>$</parser.char.dollar>
+ <!-- character to substitute to '#' -->
+ <parser.char.hash>@</parser.char.hash>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.8.0</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- generate manifest file -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+
+ <!-- extract raw parser grammar from velocity jar -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>fetch-grammar-file</id>
+ <phase>initialize</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifact>org.apache.velocity:velocity-engine-core:${project.version}</artifact>
+ <includes>org/apache/velocity/runtime/parser/Parser.jjt</includes>
+ <outputDirectory>${project.build.directory}/grammar</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- generate custom grammar file -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-parser-grammar</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <useDefaultDelimiters>false</useDefaultDelimiters>
+ <delimiters>
+ <delimiter>${*}</delimiter>
+ </delimiters>
+ <resources>
+ <resource>
+ <directory>${project.build.directory}/grammar</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <outputDirectory>${project.build.directory}/parser</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- run javacc -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <isStatic>false</isStatic>
+ <buildParser>true</buildParser>
+ <buildNodeFiles>false</buildNodeFiles>
+ <multi>true</multi>
+ <debugParser>${parser.debug}</debugParser>
+ <debugLookAhead>${parser.debug}</debugLookAhead>
+ <debugTokenManager>${parser.debug}</debugTokenManager>
+ <jdkVersion>${maven.compiler.target}</jdkVersion>
+ <nodeUsesParser>true</nodeUsesParser>
+ <nodePackage>${parser.package}.node</nodePackage>
+ <sourceDirectory>${project.build.directory}/parser/org/apache/velocity/runtime/parser</sourceDirectory>
+ <tokenManagerUsesParser>true</tokenManagerUsesParser>
+ </configuration>
+ <executions>
+ <execution>
+ <id>jjtree-javacc</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>jjtree-javacc</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>Parser.jjt</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Remove extra generated files we don't want -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-clean-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>clean-extra-javacc</id>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>clean</goal>
+ </goals>
+ <configuration>
+ <excludeDefaultDirectories>true</excludeDefaultDirectories>
+ <filesets>
+ <fileset>
+ <directory>${project.build.directory}/generated-sources/javacc/</directory>
+ <includes>
+ <include>**/*.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/*${parser.basename}*.java</exclude>
+ </excludes>
+ </fileset>
+ <fileset>
+ <directory>${project.build.directory}/generated-sources/jjtree/</directory>
+ <includes>
+ <include>**/node/*.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/node/*${parser.basename}*.java</exclude>
+ </excludes>
+ </fileset>
+ </filesets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- add missing imports to some parser generated files -->
+ <plugin>
+ <groupId>com.google.code.maven-replacer-plugin</groupId>
+ <artifactId>replacer</artifactId>
+ <executions>
+ <execution>
+ <id>patch-parser-files</id>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>replace</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>${project.build.directory}/generated-sources/jjtree/**/JJT${parser.basename}ParserState.java</include>
+ <include>${project.build.directory}/generated-sources/jjtree/**/${parser.basename}ParserVisitor.java</include>
+ </includes>
+ <replacements>
+ <replacement>
+ <token>import ${parser.package}.*;</token>
+ <value>import ${parser.package}.*;
+import org.apache.velocity.runtime.parser.node.*;</value>
+ </replacement>
+ </replacements>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.plugin.version}</version>
+ <configuration>
+ <skip>${maven.test.skip}</skip>
+ <systemProperties>
+ <property>
+ <name>test</name>
+ <value>${test}</value>
+ </property>
+ <property>
+ <name>test.templates.dir</name>
+ <value>${project.build.testOutputDirectory}/templates</value>
+ </property>
+ <property>
+ <name>test.results.dir</name>
+ <value>${project.build.directory}/results</value>
+ </property>
+ <property>
+ <name>test.reference.dir</name>
+ <value>${project.build.testOutputDirectory}/reference</value>
+ </property>
+ <property>
+ <name>org.slf4j.simpleLogger.defaultLogLevel</name>
+ <value>warn</value>
+ </property>
+ <property>
+ <name>org.slf4j.simpleLogger.logFile</name>
+ <value>${project.build.directory}/velocity.log</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/velocity-custom-parser-example/src/test/java/org/apache/velocity/runtime/parser/CustomParserTestCase.java b/velocity-custom-parser-example/src/test/java/org/apache/velocity/runtime/parser/CustomParserTestCase.java
new file mode 100644
index 00000000..9a27ff89
--- /dev/null
+++ b/velocity-custom-parser-example/src/test/java/org/apache/velocity/runtime/parser/CustomParserTestCase.java
@@ -0,0 +1,56 @@
+package org.apache.velocity.runtime.parser;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.*;
+
+public class CustomParserTestCase
+{
+ VelocityEngine engine;
+
+ static String TEMPLATES_DIR = System.getProperty("test.templates.dir");
+ static String RESULTS_DIR = System.getProperty("test.results.dir");
+ static String REFERENCE_DIR = System.getProperty("test.reference.dir");
+
+ @Before
+ public void setUp()
+ {
+ engine = new VelocityEngine();
+ engine.setProperty("resource.loaders", "file");
+ engine.setProperty("resource.loader.file.path", TEMPLATES_DIR);
+ engine.setProperty("parser.class", "org.apache.velocity.runtime.parser.custom.CustomParser");
+ engine.init();
+ }
+
+ @Test
+ public void testMarkdownTemplate() throws Exception
+ {
+ VelocityContext ctx = new VelocityContext();
+ ctx.put("some", "value");
+ Template tmpl = engine.getTemplate("test.md", "UTF-8");
+
+ String resultFile = RESULTS_DIR + File.separator + "test.md";
+ String referenceFile = REFERENCE_DIR + File.separator + "test.md";
+
+ new File(resultFile).getParentFile().mkdirs();
+
+ FileWriter writer = new FileWriter(resultFile);
+ tmpl.merge(ctx, writer);
+ writer.flush();
+ writer.close();
+
+ String result = IOUtils.toString(new FileInputStream(resultFile), StandardCharsets.UTF_8);
+ String reference = IOUtils.toString(new FileInputStream(referenceFile), StandardCharsets.UTF_8);
+ assertEquals(reference, result);
+ }
+}
diff --git a/velocity-custom-parser-example/src/test/resources/reference/test.md b/velocity-custom-parser-example/src/test/resources/reference/test.md
new file mode 100644
index 00000000..941d4f68
--- /dev/null
+++ b/velocity-custom-parser-example/src/test/resources/reference/test.md
@@ -0,0 +1,13 @@
+# Test markdown template for the custom parser
+
+
+
+
+## Custom parser is needed
+
+some value
+ all seems fine
+
+
+ block macro called with foo=value and bodyContent=here
+
diff --git a/velocity-custom-parser-example/src/test/resources/templates/test.md b/velocity-custom-parser-example/src/test/resources/templates/test.md
new file mode 100644
index 00000000..f8badd9d
--- /dev/null
+++ b/velocity-custom-parser-example/src/test/resources/templates/test.md
@@ -0,0 +1,23 @@
+# Test markdown template for the custom parser
+
+@* this is a comment *@
+
+@set ($subtitle = 'Custom parser is needed')
+
+## $subtitle
+
+some $some @@ should print 'some value'
+
+@if ($some == 'value')
+ all seems fine
+@else
+ there is a problem
+@end
+
+@macro(block $foo)
+ block macro called with foo=$foo and bodyContent=$bodyContent
+@end
+
+@%block($some)
+here
+@end
diff --git a/velocity-engine-core/pom.xml b/velocity-engine-core/pom.xml
new file mode 100644
index 00000000..8ec2e390
--- /dev/null
+++ b/velocity-engine-core/pom.xml
@@ -0,0 +1,351 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-parent</artifactId>
+ <version>2.4-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>velocity-engine-core</artifactId>
+ <name>Apache Velocity - Engine</name>
+
+ <properties>
+ <!-- You should not directly modify those properties which define the behavior of the parser.
+ Instead, you should customize the velocity-custom-parser-example module to fit your own needs.
+ -->
+ <parser.debug>false</parser.debug>
+ <parser.package>org.apache.velocity.runtime.parser</parser.package>
+ <parser.basename>Standard</parser.basename>
+ <parser.char.asterisk>*</parser.char.asterisk>
+ <parser.char.at>@</parser.char.at>
+ <parser.char.dollar>$</parser.char.dollar>
+ <parser.char.hash>#</parser.char.hash>
+
+ <!-- You can modify those properties locally to test
+ the DataSourceResourceLoader against other engines.
+ Please note that you may have to also alter the file
+ src/test/resources/ds/create-db.sql for specific engine SQL grammars.
+ -->
+ <test.jdbc.driver.groupId>org.hsqldb</test.jdbc.driver.groupId>
+ <test.jdbc.driver.artifactId>hsqldb</test.jdbc.driver.artifactId>
+ <test.jdbc.driver.version>2.7.1</test.jdbc.driver.version>
+ <test.jdbc.driver.classifier>jdk8</test.jdbc.driver.classifier>
+ <test.jdbc.driver.className>org.hsqldb.jdbcDriver</test.jdbc.driver.className>
+ <test.jdbc.uri>jdbc:hsqldb:.</test.jdbc.uri>
+ <test.jdbc.login>sa</test.jdbc.login>
+ <test.jdbc.password />
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <!-- prepare parser grammar file -->
+ <execution>
+ <id>generate-parser-grammar</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <useDefaultDelimiters>false</useDefaultDelimiters>
+ <delimiters>
+ <delimiter>${*}</delimiter>
+ </delimiters>
+ <resources>
+ <resource>
+ <directory>src/main/parser</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <outputDirectory>${project.build.directory}/parser</outputDirectory>
+ </configuration>
+ </execution>
+ <!-- expose the raw grammar file for the custom parser maven plugin -->
+ <execution>
+ <id>expose-parser-grammar</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <resources>
+ <resource>
+ <directory>src/main/parser</directory>
+ <filtering>false</filtering>
+ </resource>
+ </resources>
+ <outputDirectory>${project.build.outputDirectory}/org/apache/velocity/runtime/parser</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- shading of commons-io -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.2.1</version>
+ <executions>
+ <execution>
+ <id>shade</id>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <artifactSet>
+ <includes>
+ <include>commons-io:commons-io</include>
+ </includes>
+ <excludes>
+ <exclude>org.slf4j:slf4j-api</exclude>
+ </excludes>
+ </artifactSet>
+ <relocations>
+ <relocation>
+ <pattern>org.apache.commons.io</pattern>
+ <shadedPattern>org.apache.velocity.shaded.commons.io</shadedPattern>
+ </relocation>
+ </relocations>
+ <minimizeJar>true</minimizeJar>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- parser -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>javacc-maven-plugin</artifactId>
+ <version>2.6</version>
+ <configuration>
+ <isStatic>false</isStatic>
+ <buildParser>true</buildParser>
+ <buildNodeFiles>false</buildNodeFiles>
+ <multi>true</multi>
+ <debugParser>${parser.debug}</debugParser>
+ <debugLookAhead>${parser.debug}</debugLookAhead>
+ <debugTokenManager>${parser.debug}</debugTokenManager>
+ <jdkVersion>${maven.compiler.target}</jdkVersion>
+ <nodeUsesParser>true</nodeUsesParser>
+ <nodePackage>org.apache.velocity.runtime.parser.node</nodePackage>
+ <sourceDirectory>${project.build.directory}/parser</sourceDirectory>
+ <tokenManagerUsesParser>true</tokenManagerUsesParser>
+ </configuration>
+ <executions>
+ <!-- build the standard parser -->
+ <execution>
+ <id>jjtree-javacc</id>
+ <goals>
+ <goal>jjtree-javacc</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>Parser.jjt</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- post-processing of parser genereted source files -->
+ <plugin>
+ <groupId>com.google.code.maven-replacer-plugin</groupId>
+ <artifactId>replacer</artifactId>
+ <executions>
+ <execution>
+ <id>patch-parser-files</id>
+ <phase>process-sources</phase>
+ <goals>
+ <goal>replace</goal>
+ </goals>
+ <configuration>
+ <file>${project.build.directory}/generated-sources/javacc/org/apache/velocity/runtime/parser/TokenMgrError.java</file>
+ <replacements>
+ <replacement>
+ <token>static final int</token>
+ <value>public static final int</value>
+ </replacement>
+ </replacements>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- handle VelocityEngineVersion file -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>templating-maven-plugin</artifactId>
+ <version>1.0.0</version>
+ <executions>
+ <execution>
+ <id>filter-src</id>
+ <goals>
+ <goal>filter-sources</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- handle OSGi information -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.apache.velocity.*
+ </Export-Package>
+ <Import-Package>
+ !org.apache.commons.io,
+ *
+ </Import-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <!-- tests -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.plugin.version}</version>
+ <configuration>
+ <skip>${maven.test.skip}</skip>
+ <systemProperties>
+ <property>
+ <name>test</name>
+ <value>${test}</value>
+ </property>
+ <property>
+ <name>test.compare.dir</name>
+ <value>${project.build.testOutputDirectory}</value>
+ </property>
+ <property>
+ <name>test.result.dir</name>
+ <value>${project.build.directory}/results</value>
+ </property>
+ <property>
+ <name>org.slf4j.simpleLogger.defaultLogLevel</name>
+ <value>warn</value>
+ </property>
+ <property>
+ <name>org.slf4j.simpleLogger.logFile</name>
+ <value>${project.build.directory}/velocity.log</value>
+ </property>
+ <property>
+ <name>test.jdbc.driver.className</name>
+ <value>${test.jdbc.driver.className}</value>
+ </property>
+ <property>
+ <name>test.jdbc.uri</name>
+ <value>${test.jdbc.uri}</value>
+ </property>
+ <property>
+ <name>test.jdbc.login</name>
+ <value>${test.jdbc.login}</value>
+ </property>
+ <property>
+ <name>test.jdbc.password</name>
+ <value>${test.jdbc.password}</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ <executions>
+ <execution>
+ <id>integration-test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.11</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${test.jdbc.driver.groupId}</groupId>
+ <artifactId>${test.jdbc.driver.artifactId}</artifactId>
+ <version>${test.jdbc.driver.version}</version>
+ <scope>test</scope>
+ <classifier>${test.jdbc.driver.classifier}</classifier>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.8.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>3.0.5</version>
+ <configuration>
+ <xmlOutput>true</xmlOutput>
+ <threshold>Low</threshold>
+ <effort>Max</effort>
+ <excludeFilterFile>src/etc/build/findbugs-exclude.xml</excludeFilterFile>
+ <xmlOutputDirectory>target/site</xmlOutputDirectory>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
diff --git a/velocity-engine-core/src/etc/build/findbugs-exclude.xml b/velocity-engine-core/src/etc/build/findbugs-exclude.xml
new file mode 100755
index 00000000..ed4a7fbb
--- /dev/null
+++ b/velocity-engine-core/src/etc/build/findbugs-exclude.xml
@@ -0,0 +1,121 @@
+<!--
+
+ 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.
+
+-->
+<!-- TODO: We should be putting findBugs exclusions -->
+<!-- as annotations in the source code. But that takes JDK 1.5+. -->
+
+<FindBugsFilter>
+
+ <!-- don't worry about deprecated things -->
+ <Match>
+ <Or>
+ <Package name="org.apache.velocity.app.tools"/>
+ <Package name="org.apache.velocity.anakia"/>
+ <Package name="org.apache.velocity.convert"/>
+ <Package name="org.apache.velocity.servlet"/>
+ <Package name="org.apache.velocity.texen"/>
+ </Or>
+ </Match>
+
+ <!-- ignore the JavaCC generated "bugs" -->
+ <Match>
+ <Package name="org.apache.velocity.runtime.parser"/>
+ <Or>
+ <Bug pattern="NM_METHOD_NAMING_CONVENTION"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ <Bug pattern="MS_PKGPROTECT"/>
+ <Bug pattern="MS_OOI_PKGPROTECT"/>
+ <Bug pattern="URF_UNREAD_FIELD"/>
+ <Bug pattern="MS_MUTABLE_ARRAY"/>
+ <Bug pattern="URF_UNREAD_FIELD"/>
+ <Bug pattern="BC_UNCONFIRMED_CAST"/>
+ <Bug pattern="DB_DUPLICATE_SWITCH_CLAUSES"/>
+ <Bug pattern="DLS_DEAD_LOCAL_STORE"/>
+ </Or>
+ </Match>
+
+ <!-- these just aren't worth changing right now -->
+ <Match>
+ <Class name="org.apache.velocity.app.event.ReferenceInsertionEventHandler$referenceInsertExecutor"/>
+ <Bug pattern="NM_CLASS_NAMING_CONVENTION"/>
+ </Match>
+ <Match>
+ <Or>
+ <Class name="org.apache.velocity.runtime.directive.VelocimacroProxy"/>
+ <Class name="org.apache.velocity.runtime.parser.ParseException"/>
+ <Class name="org.apache.velocity.runtime.parser.Parser"/>
+ <Class name="org.apache.velocity.util.introspection.SecureIntrospectorImpl"/>
+ </Or>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.runtime.parser.ParseException"/>
+ <Or>
+ <Bug pattern="SBSC_USE_STRINGBUFFER_CONCATENATION"/>
+ <Bug pattern="SE_BAD_FIELD"/>
+ </Or>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.runtime.RuntimeInstance"/>
+ <Bug pattern="UI_INHERITANCE_UNSAFE_GETRESOURCE"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.util.StringUtils"/>
+ <Bug pattern="DM_CONVERT_CASE"/>
+ </Match>
+
+ <!-- there's actually good reason for these -->
+ <Match>
+ <Class name="org.apache.velocity.runtime.parser.node.ASTReference"/>
+ <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.util.introspection.ClassMap$MethodCache"/>
+ <Bug pattern="URF_UNREAD_FIELD"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader"/>
+ <Bug pattern="SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.runtime.directive.Define$Block"/>
+ <Bug pattern="NP_TOSTRING_COULD_RETURN_NULL"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.runtime.resource.loader.FileResourceLoader"/>
+ <Bug pattern="NP_LOAD_OF_KNOWN_NULL_VALUE"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.util.MapFactory"/>
+ <Or>
+ <Bug pattern="DE_MIGHT_IGNORE"/>
+ <Bug pattern="REC_CATCH_EXCEPTION"/>
+ </Or>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.util.introspection.SecureUberspector"/>
+ <Bug pattern="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.velocity.runtime.VelocimacroFactory"/>
+ <Bug pattern="IS2_INCONSISTENT_SYNC"/>
+ </Match>
+
+</FindBugsFilter>
diff --git a/velocity-engine-core/src/main/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java b/velocity-engine-core/src/main/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java
new file mode 100644
index 00000000..e3a2dc8d
--- /dev/null
+++ b/velocity-engine-core/src/main/java-templates/org/apache/velocity/runtime/VelocityEngineVersion.java
@@ -0,0 +1,6 @@
+package org.apache.velocity.runtime;
+
+public class VelocityEngineVersion
+{
+ public static final String VERSION = "${project.version}";
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/Template.java b/velocity-engine-core/src/main/java/org/apache/velocity/Template.java
new file mode 100644
index 00000000..31cca192
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/Template.java
@@ -0,0 +1,441 @@
+package org.apache.velocity;
+
+/*
+ * 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.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.Scope;
+import org.apache.velocity.runtime.directive.StopCommand;
+import org.apache.velocity.runtime.parser.ParseException;
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.ResourceManager;
+import org.slf4j.Logger;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This class is used for controlling all template
+ * operations. This class uses a parser created
+ * by JavaCC to create an AST that is subsequently
+ * traversed by a Visitor.
+ *
+ * <pre>
+ * // set up and initialize Velocity before this code block
+ *
+ * Template template = Velocity.getTemplate("test.wm");
+ * Context context = new VelocityContext();
+ *
+ * context.put("foo", "bar");
+ * context.put("customer", new Customer());
+ *
+ * template.merge(context, writer);
+ * </pre>
+ *
+ * @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 Template extends Resource implements Cloneable
+{
+ /*
+ * The name of the variable to use when placing
+ * the scope object into the context.
+ */
+ private String scopeName = "template";
+ private boolean provideScope = false;
+ private Map<String, Object> macros = new ConcurrentHashMap<>(17, 0.7f);
+
+ private VelocityException errorCondition = null;
+
+ /** Default constructor */
+ public Template()
+ {
+ super();
+
+ setType(ResourceManager.RESOURCE_TEMPLATE);
+ }
+
+ /**
+ * get the map of all macros defined by this template
+ * @return macros map
+ */
+ public Map<String, Object> getMacros()
+ {
+ return macros;
+ }
+
+ /**
+ * gets the named resource as a stream, parses and inits
+ *
+ * @return true if successful
+ * @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 boolean process()
+ throws ResourceNotFoundException, ParseErrorException
+ {
+ data = null;
+ Reader reader = null;
+ errorCondition = null;
+
+ /*
+ * first, try to get the stream from the loader
+ */
+ try
+ {
+ reader = resourceLoader.getResourceReader(name, getEncoding());
+ }
+ catch( ResourceNotFoundException rnfe )
+ {
+ /*
+ * remember and re-throw
+ */
+
+ errorCondition = rnfe;
+ throw rnfe;
+ }
+
+ /*
+ * if that worked, lets protect in case a loader impl
+ * forgets to throw a proper exception
+ */
+
+ if (reader != null)
+ {
+ /*
+ * now parse the template
+ */
+
+ try
+ {
+ BufferedReader br = new BufferedReader( reader );
+ data = rsvc.parse( br, this);
+ initDocument();
+ return true;
+ }
+ catch ( ParseException pex )
+ {
+ /*
+ * remember the error and convert
+ */
+ errorCondition = new ParseErrorException(pex, name);
+ throw errorCondition;
+ }
+ catch ( TemplateInitException pex )
+ {
+ errorCondition = new ParseErrorException( pex, name);
+ throw errorCondition;
+ }
+ /*
+ * pass through runtime exceptions
+ */
+ catch( RuntimeException e )
+ {
+ errorCondition = new VelocityException("Exception thrown processing Template "
+ +getName(), e, rsvc.getLogContext().getStackTrace());
+ throw errorCondition;
+ }
+ finally
+ {
+ /*
+ * Make sure to close the inputstream when we are done.
+ */
+ try
+ {
+ reader.close();
+ }
+ catch(IOException e)
+ {
+ // If we are already throwing an exception then we want the original
+ // exception to be continued to be thrown, otherwise, throw a new Exception.
+ if (errorCondition == null)
+ {
+ throw new VelocityException(e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * is == null, therefore we have some kind of file issue
+ */
+ errorCondition = new ResourceNotFoundException("Unknown resource error for resource " + name, null, rsvc.getLogContext().getStackTrace() );
+ throw errorCondition;
+ }
+ }
+
+ /**
+ * initializes the document. init() is not longer
+ * dependant upon context, but we need to let the
+ * init() carry the template name down through for VM
+ * namespace features
+ * @throws TemplateInitException When a problem occurs during the document initialization.
+ */
+ public void initDocument()
+ throws TemplateInitException
+ {
+ /*
+ * send an empty InternalContextAdapter down into the AST to initialize it
+ */
+
+ InternalContextAdapterImpl ica = new InternalContextAdapterImpl( new VelocityContext() );
+
+ try
+ {
+ /*
+ * put the current template name on the stack
+ */
+
+ ica.pushCurrentTemplateName( name );
+ ica.setCurrentResource( this );
+
+ /*
+ * init the AST
+ */
+
+ ((SimpleNode)data).init( ica, rsvc);
+
+ provideScope = rsvc.isScopeControlEnabled(scopeName);
+ }
+ finally
+ {
+ /*
+ * in case something blows up...
+ * pull it off for completeness
+ */
+
+ ica.popCurrentTemplateName();
+ ica.setCurrentResource( null );
+ }
+
+ }
+
+ /**
+ * The AST node structure is merged with the
+ * context to produce the final output.
+ *
+ * @param context Context with data elements accessed by template
+ * @param writer output writer for rendered template
+ * @throws ResourceNotFoundException if template not found
+ * from any available source.
+ * @throws ParseErrorException if template cannot be parsed due
+ * to syntax (or other) error.
+ * @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
+ */
+ public void merge( Context context, Writer writer)
+ throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
+ {
+ merge(context, writer, null);
+ }
+
+
+ /**
+ * The AST node structure is merged with the
+ * context to produce the final output.
+ *
+ * @param context Context with data elements accessed by template
+ * @param writer output writer for rendered template
+ * @param macroLibraries a list of template files containing macros to be used when merging
+ * @throws ResourceNotFoundException if template not found
+ * from any available source.
+ * @throws ParseErrorException if template cannot be parsed due
+ * to syntax (or other) error.
+ * @throws MethodInvocationException When a method on a referenced object in the context could not invoked.
+ * @since 1.6
+ */
+ public void merge( Context context, Writer writer, List<String> macroLibraries)
+ throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
+ {
+ try
+ {
+ /*
+ * we shouldn't have to do this, as if there is an error condition,
+ * the application code should never get a reference to the
+ * Template
+ */
+
+ if (errorCondition != null)
+ {
+ throw errorCondition;
+ }
+
+ if (data != null)
+ {
+ /*
+ * create an InternalContextAdapter to carry the user Context down
+ * into the rendering engine. Set the template name and render()
+ */
+
+ InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
+
+ /*
+ * Set the macro libraries
+ */
+ List<Template> libTemplates = new ArrayList<>();
+ ica.setMacroLibraries(libTemplates);
+
+ if (macroLibraries != null)
+ {
+ for (String macroLibrary : macroLibraries)
+ {
+ /*
+ * Build the macro library
+ */
+ try
+ {
+ Template t = rsvc.getTemplate(macroLibrary);
+ libTemplates.add(t);
+ }
+ catch (ResourceNotFoundException re)
+ {
+ /*
+ * the macro lib wasn't found. Note it and throw
+ */
+ log.error("cannot find template {}", macroLibrary);
+ throw re;
+ }
+ catch (ParseErrorException pe)
+ {
+ /*
+ * the macro lib was found, but didn't parse - syntax error
+ * note it and throw
+ */
+ rsvc.getLog("parser").error("syntax error in template {}: {}",
+ macroLibrary, pe.getMessage(), pe);
+ throw pe;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("parse failed in template " +
+ macroLibrary + ".", e);
+ }
+ }
+ }
+
+ if (provideScope)
+ {
+ ica.put(scopeName, new Scope(this, ica.get(scopeName)));
+ }
+ try
+ {
+ ica.pushCurrentTemplateName(name);
+ ica.setCurrentResource(this);
+
+ ((SimpleNode) data).render(ica, writer);
+ }
+ catch (StopCommand stop)
+ {
+ if (!stop.isFor(this))
+ {
+ throw stop;
+ }
+ else
+ {
+ Logger renderingLog = rsvc.getLog("rendering");
+ renderingLog.debug(stop.getMessage());
+ }
+ }
+ catch (IOException e)
+ {
+ throw new VelocityException("IO Error rendering template '" + name + "'", e, rsvc.getLogContext().getStackTrace());
+ }
+ finally
+ {
+ /*
+ * lets make sure that we always clean up the context
+ */
+ ica.popCurrentTemplateName();
+ ica.setCurrentResource(null);
+
+ if (provideScope)
+ {
+ Object obj = ica.get(scopeName);
+ if (obj instanceof Scope)
+ {
+ Scope scope = (Scope) obj;
+ if (scope.getParent() != null)
+ {
+ ica.put(scopeName, scope.getParent());
+ }
+ else if (scope.getReplaced() != null)
+ {
+ ica.put(scopeName, scope.getReplaced());
+ }
+ else
+ {
+ ica.remove(scopeName);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * this shouldn't happen either, but just in case.
+ */
+
+ String msg = "Template merging failed. The document is null, " +
+ "most likely due to a parsing error.";
+
+ throw new RuntimeException(msg);
+
+ }
+ }
+ catch (VelocityException ve)
+ {
+ /* it's a good place to display the VTL stack trace if we have one */
+ String[] vtlStacktrace = ve.getVtlStackTrace();
+ if (vtlStacktrace != null)
+ {
+ Logger renderingLog = rsvc.getLog("rendering");
+ renderingLog.error(ve.getMessage());
+ renderingLog.error("VTL stacktrace:");
+ for (String level : vtlStacktrace)
+ {
+ renderingLog.error(level);
+ }
+ }
+ throw ve;
+ }
+ }
+
+ @Override
+ protected void deepCloneData() throws CloneNotSupportedException {
+ setData(((SimpleNode)data).clone(this));
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/VelocityContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/VelocityContext.java
new file mode 100644
index 00000000..9b4a6e4a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/VelocityContext.java
@@ -0,0 +1,196 @@
+package org.apache.velocity;
+
+/*
+ * 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.AbstractContext;
+import org.apache.velocity.context.Context;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * General purpose implementation of the application Context
+ * interface for general application use. This class should
+ * be used in place of the original Context class.
+ *
+ * This implementation uses a HashMap (@see java.util.HashMap )
+ * for data storage.
+ *
+ * This context implementation cannot be shared between threads
+ * without those threads synchronizing access between them, as
+ * the HashMap is not synchronized, nor are some of the fundamentals
+ * of AbstractContext. If you need to share a Context between
+ * threads with simultaneous access for some reason, please create
+ * your own and extend the interface Context
+ *
+ * @see org.apache.velocity.context.Context
+ *
+ * @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:fedor.karpelevitch@home.com">Fedor Karpelevitch</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @version $Id$
+ */
+public class VelocityContext
+ extends AbstractContext
+ implements Cloneable, Serializable
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = 9033846851064645037L;
+
+ /**
+ * Storage for key/value pairs.
+ */
+ private Map<String, Object> context = null;
+
+ /**
+ * Creates a new instance (with no inner context).
+ */
+ public VelocityContext()
+ {
+ this(null, null);
+ }
+
+ /**
+ * Creates a new instance with the provided storage (and no inner
+ * context).
+ * @param context
+ */
+ public VelocityContext(Map<String, Object> context)
+ {
+ this(context, null);
+ }
+
+ /**
+ * Chaining constructor, used when you want to
+ * wrap a context in another. The inner context
+ * will be 'read only' - put() calls to the
+ * wrapping context will only effect the outermost
+ * context
+ *
+ * @param innerContext The <code>Context</code> implementation to
+ * wrap.
+ */
+ public VelocityContext( Context innerContext )
+ {
+ this(null, innerContext);
+ }
+
+ /**
+ * Initializes internal storage (never to <code>null</code>), and
+ * inner context.
+ *
+ * @param context Internal storage, or <code>null</code> to
+ * create default storage.
+ * @param innerContext Inner context.
+ */
+ public VelocityContext(Map<String, Object> context, Context innerContext)
+ {
+ super(innerContext);
+ this.context = (context == null ? new HashMap<>() : context);
+ }
+
+ /**
+ * retrieves value for key from internal
+ * storage
+ *
+ * @param key name of value to get
+ * @return value as object
+ */
+ @Override
+ public Object internalGet(String key )
+ {
+ return context.get( key );
+ }
+
+ /**
+ * stores the value for key to internal
+ * storage
+ *
+ * @param key name of value to store
+ * @param value value to store
+ * @return previous value of key as Object
+ */
+ @Override
+ public Object internalPut(String key, Object value )
+ {
+ return context.put( key, value );
+ }
+
+ /**
+ * determines if there is a value for the
+ * given key
+ *
+ * @param key name of value to check
+ * @return true if non-null value in store
+ */
+ @Override
+ public boolean internalContainsKey(String key)
+ {
+ return context.containsKey( key );
+ }
+
+ /**
+ * returns array of keys
+ *
+ * @return keys as []
+ */
+ @Override
+ public String[] internalGetKeys()
+ {
+ return context.keySet().toArray(new String[context.size()]);
+ }
+
+ /**
+ * remove a key/value pair from the
+ * internal storage
+ *
+ * @param key name of value to remove
+ * @return value removed
+ */
+ @Override
+ public Object internalRemove(String key)
+ {
+ return context.remove( key );
+ }
+
+ /**
+ * Clones this context object.
+ *
+ * @return A shallow copy of this <code>Context</code>.
+ */
+ @Override
+ public Object clone()
+ {
+ VelocityContext clone = null;
+ try
+ {
+ clone = (VelocityContext) super.clone();
+ clone.context = new HashMap<>(context);
+ }
+ catch (CloneNotSupportedException ignored)
+ {
+ }
+ return clone;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/FieldMethodizer.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/FieldMethodizer.java
new file mode 100644
index 00000000..3428d1c6
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/FieldMethodizer.java
@@ -0,0 +1,185 @@
+package org.apache.velocity.app;
+
+/*
+ * 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.util.ClassUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>
+ * This is a small utility class allow easy access to static fields in a class,
+ * such as string constants. Velocity will not introspect for class
+ * fields (and won't in the future :), but writing setter/getter methods to do
+ * this really is a pain, so use this if you really have
+ * to access fields.
+ *
+ * <p>
+ * The idea it so enable access to the fields just like you would in Java.
+ * For example, in Java, you would access a static field like
+ * <blockquote><pre>
+ * MyClass.STRING_CONSTANT
+ * </pre></blockquote>
+ * and that is the same thing we are trying to allow here.
+ *
+ * <p>
+ * So to use in your Java code, do something like this :
+ * <blockquote><pre>
+ * context.put("runtime", new FieldMethodizer( "org.apache.velocity.runtime.Runtime" ));
+ * </pre></blockquote>
+ * and then in your template, you can access any of your static fields in this way :
+ * <blockquote><pre>
+ * $runtime.COUNTER_NAME
+ * </pre></blockquote>
+ *
+ * <p>
+ * Right now, this class only methodizes <code>public static</code> fields. It seems
+ * that anything else is too dangerous. This class is for convenience accessing
+ * 'constants'. If you have fields that aren't <code>static</code> it may be better
+ * to handle them by explicitly placing them into the context.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class FieldMethodizer
+{
+ /** Hold the field objects by field name */
+ private Map<String, Field> fieldHash = new HashMap<>();
+ private Logger logger = LoggerFactory.getLogger(FieldMethodizer.class);
+
+ /**
+ * Allow object to be initialized without any data. You would use
+ * addObject() to add data later.
+ */
+ public FieldMethodizer()
+ {
+ }
+
+ /**
+ * Constructor that takes as it's arg the name of the class
+ * to methodize.
+ *
+ * @param s Name of class to methodize.
+ */
+ public FieldMethodizer( String s )
+ {
+ try
+ {
+ addObject(s);
+ }
+ catch( Exception e )
+ {
+ logger.error("[FieldMethodizer] Could not add {} for field methodizing", s, e);
+ }
+ }
+
+ /**
+ * Constructor that takes as it's arg a living
+ * object to methodize. Note that it will still
+ * only methodized the public static fields of
+ * the class.
+ *
+ * @param o Name of class to methodize.
+ */
+ public FieldMethodizer( Object o )
+ {
+ try
+ {
+ addObject(o);
+ }
+ catch( Exception e )
+ {
+ logger.error("[FieldMethodizer] Could not add {} for field methodizing", o, e);
+ }
+ }
+
+ /**
+ * Add the Name of the class to methodize
+ * @param s
+ * @throws Exception
+ */
+ public void addObject ( String s )
+ throws Exception
+ {
+ inspect(ClassUtils.getClass(s));
+ }
+
+ /**
+ * Add an Object to methodize
+ * @param o
+ * @throws Exception
+ */
+ public void addObject ( Object o )
+ throws Exception
+ {
+ inspect(o.getClass());
+ }
+
+ /**
+ * Accessor method to get the fields by name.
+ *
+ * @param fieldName Name of static field to retrieve
+ *
+ * @return The value of the given field.
+ */
+ public Object get( String fieldName )
+ {
+ Object value = null;
+ try
+ {
+ Field f = fieldHash.get( fieldName );
+ if (f != null)
+ {
+ value = f.get(null);
+ }
+ }
+ catch( IllegalAccessException e )
+ {
+ System.err.println("IllegalAccessException while trying to access " + fieldName
+ + ": " + e.getMessage());
+ }
+ return value;
+ }
+
+ /**
+ * Method that retrieves all public static fields
+ * in the class we are methodizing.
+ */
+ private void inspect(Class<?> clas)
+ {
+ Field[] fields = clas.getFields();
+ for (Field field : fields)
+ {
+ /*
+ * only if public and static
+ */
+ int mod = field.getModifiers();
+ if (Modifier.isStatic(mod) && Modifier.isPublic(mod))
+ {
+ fieldHash.put(field.getName(), field);
+ }
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/Velocity.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/Velocity.java
new file mode 100644
index 00000000..351ecf45
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/Velocity.java
@@ -0,0 +1,387 @@
+package org.apache.velocity.app;
+
+/*
+ * 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.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;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.slf4j.Logger;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Properties;
+
+/**
+ * This class provides services to the application
+ * developer, such as:
+ * <ul>
+ * <li> Simple Velocity Runtime engine initialization methods.
+ * <li> Functions to apply the template engine to streams and strings
+ * to allow embedding and dynamic template generation.
+ * <li> Methods to access Velocimacros directly.
+ * </ul>
+ *
+ * <br><br>
+ * While the most common way to use Velocity is via templates, as
+ * Velocity is a general-purpose template engine, there are other
+ * uses that Velocity is well suited for, such as processing dynamically
+ * created templates, or processing content streams.
+ *
+ * <br><br>
+ * The methods herein were developed to allow easy access to the Velocity
+ * facilities without direct spelunking of the internals. If there is
+ * something you feel is necessary to add here, please, send a patch.
+ *
+ * @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:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class Velocity implements RuntimeConstants
+{
+ /**
+ * initialize the Velocity runtime engine, using the default
+ * properties of the Velocity distribution
+ */
+ public static void init()
+ {
+ RuntimeSingleton.init();
+ }
+
+ /**
+ * Resets the instance, so Velocity can be re-initialized again.
+ *
+ * @since 2.0.0
+ */
+ public static void reset()
+ {
+ RuntimeSingleton.reset();
+ }
+
+ /**
+ * initialize the Velocity runtime engine, using default properties
+ * plus the properties in the properties file passed in as the arg
+ *
+ * @param propsFilename file containing properties to use to initialize
+ * the Velocity runtime
+ */
+ public static void init( String propsFilename )
+ {
+ RuntimeSingleton.init(propsFilename);
+ }
+
+ /**
+ * initialize the Velocity runtime engine, using default properties
+ * plus the properties in the passed in java.util.Properties object
+ *
+ * @param p Properties object containing initialization properties
+ */
+ public static void init( Properties p )
+ {
+ RuntimeSingleton.init( p );
+ }
+
+ /**
+ * Set a Velocity Runtime property.
+ *
+ * @param key The property key.
+ * @param value The property value.
+ */
+ public static void setProperty(String key, Object value)
+ {
+ RuntimeSingleton.setProperty(key, value);
+ }
+
+ /**
+ * Add a Velocity Runtime property.
+ *
+ * @param key The property key.
+ * @param value The property value.
+ */
+ public static void addProperty(String key, Object value)
+ {
+ RuntimeSingleton.addProperty(key, value);
+ }
+
+ /**
+ * Clear a Velocity Runtime property.
+ *
+ * @param key of property to clear
+ */
+ public static void clearProperty(String key)
+ {
+ RuntimeSingleton.clearProperty(key);
+ }
+
+ /**
+ * Set an entire configuration at once.
+ *
+ * @param configuration A configuration object.
+ * @since 2.0
+ *
+ */
+ public static void setProperties( Properties configuration)
+ {
+ RuntimeSingleton.setProperties(configuration);
+ }
+
+ /**
+ * Get a Velocity Runtime property.
+ *
+ * @param key property to retrieve
+ * @return property value or null if the property
+ * not currently set
+ */
+ public static Object getProperty( String key )
+ {
+ return RuntimeSingleton.getProperty( key );
+ }
+
+ /**
+ * 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.
+ */
+ public static boolean evaluate( Context context, Writer out,
+ String logTag, String instring )
+ throws ParseErrorException, MethodInvocationException,
+ ResourceNotFoundException
+ {
+ return RuntimeSingleton.getRuntimeServices()
+ .evaluate(context, out, logTag, 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 v1.1
+ */
+ public static boolean evaluate( Context context, Writer writer,
+ String logTag, Reader reader )
+ throws ParseErrorException, MethodInvocationException,
+ ResourceNotFoundException
+ {
+ return RuntimeSingleton.getRuntimeServices().evaluate(context, writer,
+ logTag, 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.
+ */
+ public static boolean invokeVelocimacro( String vmName, String logTag,
+ String params[], Context context,
+ Writer writer )
+ {
+ return RuntimeSingleton.getRuntimeServices()
+ .invokeVelocimacro(vmName, logTag, params, context, writer);
+ }
+
+ /**
+ * Merges a template and puts the rendered stream into the writer
+ *
+ * @param templateName name of template to be used in merge
+ * @param encoding encoding used in template
+ * @param context filled context to be used in merge
+ * @param writer writer to write template into
+ *
+ * @return true if successful, false otherwise. Errors
+ * logged to velocity 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 v1.1
+ */
+ public static boolean mergeTemplate( String templateName, String encoding,
+ Context context, Writer writer )
+ throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
+ {
+ Template template = RuntimeSingleton.getTemplate(templateName, encoding);
+
+ if ( template == null )
+ {
+ String msg = "Velocity.mergeTemplate() was unable to load template '"
+ + templateName + "'";
+ getLog().error(msg);
+ throw new ResourceNotFoundException(msg);
+ }
+ else
+ {
+ template.merge(context, writer);
+ return true;
+ }
+ }
+
+ /**
+ * Returns a <code>Template</code> from the Velocity
+ * resource management system.
+ *
+ * @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.
+ */
+ public static Template getTemplate(String name)
+ throws ResourceNotFoundException, ParseErrorException
+ {
+ return RuntimeSingleton.getTemplate( name );
+ }
+
+ /**
+ * Returns a <code>Template</code> from the Velocity
+ * resource management system.
+ *
+ * @param name The file name of the desired template.
+ * @param encoding The character encoding to use for 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.
+ *
+ * @since Velocity v1.1
+ */
+ public static Template getTemplate(String name, String encoding)
+ throws ResourceNotFoundException, ParseErrorException
+ {
+ return RuntimeSingleton.getTemplate( name, encoding );
+ }
+
+ /**
+ * <p>Determines whether a resource is accessible via the
+ * currently configured resource loaders. {@link
+ * org.apache.velocity.runtime.resource.Resource} is the generic
+ * description of templates, static content, etc.</p>
+ *
+ * <p>Note that the current implementation will <b>not</b> change
+ * the state of the system in any real way - so this cannot be
+ * used to pre-load the resource cache, as the previous
+ * implementation did as a side-effect.</p>
+ *
+ * @param resourceName The name of the resource to search for.
+ * @return Whether the resource was located.
+ */
+ public static boolean resourceExists(String resourceName)
+ {
+ return (RuntimeSingleton.getLoaderNameForResource(resourceName) != null);
+ }
+
+ /**
+ * Returns the current logger.
+ *
+ * @return A convenience Logger instance.
+ * @since 1.5
+ */
+ public static Logger getLog()
+ {
+ return RuntimeSingleton.getLog();
+ }
+
+ /**
+ * <p>
+ * Set the an ApplicationAttribue, which is an Object
+ * set by the application which is accessible from
+ * any component of the system that gets a RuntimeServices.
+ * This allows communication between the application
+ * environment and custom pluggable components of the
+ * Velocity engine, such as loaders and loggers.
+ * </p>
+ *
+ * <p>
+ * Note that there is no enforcement or rules for the key
+ * used - it is up to the application developer. However, to
+ * help make the intermixing of components possible, using
+ * the target Class name (e.g. com.foo.bar ) as the key
+ * might help avoid collision.
+ * </p>
+ *
+ * @param key object 'name' under which the object is stored
+ * @param value object to store under this key
+ */
+ public static void setApplicationAttribute( Object key, Object value )
+ {
+ RuntimeSingleton.getRuntimeServices().setApplicationAttribute( key, value);
+ }
+
+
+ /**
+ * Remove a directive.
+ *
+ * @param name name of the directive.
+ */
+ public void removeDirective(String name)
+ {
+ RuntimeSingleton.removeDirective(name);
+ }
+
+ /**
+ * Instantiates and loads the directive with some basic checks.
+ *
+ * @param directiveClass classname of directive to load
+ */
+ public void loadDirective(String directiveClass)
+ {
+ RuntimeSingleton.loadDirective(directiveClass);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
new file mode 100644
index 00000000..93751f3a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/VelocityEngine.java
@@ -0,0 +1,431 @@
+package org.apache.velocity.app;
+
+/*
+ * 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.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;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.slf4j.Logger;
+
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Properties;
+
+/**
+ * <p>
+ * This class provides a separate new-able instance of the
+ * Velocity template engine. The alternative model for use
+ * is using the Velocity class which employs the singleton
+ * model.
+ * </p>
+ * <p>Velocity will call
+ * the parameter-less init() at the first use of this class
+ * if the init() wasn't explicitly called. While this will
+ * ensure that Velocity functions, it probably won't
+ * function in the way you intend, so it is strongly recommended that
+ * you call an init() method yourself if you use the default constructor.
+ * </p>
+ *
+ * @version $Id$
+ */
+public class VelocityEngine implements RuntimeConstants
+{
+ private RuntimeInstance ri = new RuntimeInstance();
+
+ /**
+ * Init-less CTOR
+ */
+ public VelocityEngine()
+ {
+ // do nothing
+ }
+
+ /**
+ * Construct a VelocityEngine with the initial properties defined in the file
+ * propsFilename
+ * @param propsFilename properties filename
+ */
+ public VelocityEngine(String propsFilename)
+ {
+ ri.setProperties(propsFilename);
+ }
+
+ /**
+ * Construct a VelocityEngine instance with the specified initial properties.
+ * @param p properties
+ */
+ public VelocityEngine(Properties p)
+ {
+ ri.setProperties(p);
+ }
+
+ /**
+ * initialize the Velocity runtime engine, using the default
+ * properties of the Velocity distribution
+ */
+ public void init()
+ {
+ ri.init();
+ }
+
+ /**
+ * Resets the instance, so Velocity can be re-initialized again.
+ *
+ * @since 2.0.0
+ */
+ public void reset()
+ {
+ ri.reset();
+ }
+
+ /**
+ * initialize the Velocity runtime engine, using default properties
+ * plus the properties in the properties file passed in as the arg
+ *
+ * @param propsFilename file containing properties to use to initialize
+ * the Velocity runtime
+ */
+ public void init(String propsFilename)
+ {
+ ri.init(propsFilename);
+ }
+
+ /**
+ * initialize the Velocity runtime engine, using default properties
+ * plus the properties in the passed in java.util.Properties object
+ *
+ * @param p Properties object containing initialization properties
+ */
+ public void init(Properties p)
+ {
+ ri.init(p);
+ }
+
+ /**
+ * Set a Velocity Runtime property.
+ *
+ * @param key
+ * @param value
+ */
+ public void setProperty(String key, Object value)
+ {
+ ri.setProperty(key, value);
+ }
+
+ /**
+ * Add a Velocity Runtime property.
+ *
+ * @param key
+ * @param value
+ */
+ public void addProperty(String key, Object value)
+ {
+ ri.addProperty(key, value);
+ }
+
+ /**
+ * Clear a Velocity Runtime property.
+ *
+ * @param key of property to clear
+ */
+ public void clearProperty(String key)
+ {
+ ri.clearProperty(key);
+ }
+
+ /**
+ * Set an entire configuration at once from a Properties configuration
+ *
+ * @param configuration
+ * @since 2.0
+ */
+ public 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 void setProperties(String propsFilename)
+ {
+ ri.setProperties(propsFilename);
+ }
+
+ /**
+ * Get a Velocity Runtime property.
+ *
+ * @param key property to retrieve
+ * @return property value or null if the property
+ * not currently set
+ */
+ public Object getProperty( String key )
+ {
+ return ri.getProperty( key );
+ }
+
+ /**
+ * 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.
+ */
+ public boolean evaluate( Context context, Writer out,
+ String logTag, String instring )
+ throws ParseErrorException, MethodInvocationException,
+ ResourceNotFoundException
+ {
+ return ri.evaluate(context, out, logTag, 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 v1.1
+ */
+ public boolean evaluate(Context context, Writer writer,
+ String logTag, Reader reader)
+ throws ParseErrorException, MethodInvocationException,
+ ResourceNotFoundException
+ {
+ return ri.evaluate(context, writer, logTag, 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.
+ */
+ public boolean invokeVelocimacro( String vmName, String logTag,
+ String params[], Context context,
+ Writer writer )
+ {
+ return ri.invokeVelocimacro(vmName, logTag, params, context, writer);
+ }
+
+ /**
+ * merges a template and puts the rendered stream into the writer
+ *
+ * @param templateName name of template to be used in merge
+ * @param encoding encoding used in template
+ * @param context filled context to be used in merge
+ * @param writer writer to write template into
+ *
+ * @return true if successful, false otherwise. Errors
+ * logged to velocity log
+ * @throws ResourceNotFoundException
+ * @throws ParseErrorException
+ * @throws MethodInvocationException
+ * @since Velocity v1.1
+ */
+ public boolean mergeTemplate( String templateName, String encoding,
+ Context context, Writer writer )
+ throws ResourceNotFoundException, ParseErrorException, MethodInvocationException
+ {
+ Template template = ri.getTemplate(templateName, encoding);
+
+ if ( template == null )
+ {
+ String msg = "VelocityEngine.mergeTemplate() was unable to load template '"
+ + templateName + "'";
+ getLog().error(msg);
+ throw new ResourceNotFoundException(msg);
+ }
+ else
+ {
+ template.merge(context, writer);
+ return true;
+ }
+ }
+
+ /**
+ * Returns a <code>Template</code> from the Velocity
+ * resource management system.
+ *
+ * @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.
+ */
+ public Template getTemplate(String name)
+ throws ResourceNotFoundException, ParseErrorException
+ {
+ return ri.getTemplate( name );
+ }
+
+ /**
+ * Returns a <code>Template</code> from the Velocity
+ * resource management system.
+ *
+ * @param name The file name of the desired template.
+ * @param encoding The character encoding to use for 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.
+ * @since Velocity v1.1
+ */
+ public Template getTemplate(String name, String encoding)
+ throws ResourceNotFoundException, ParseErrorException
+ {
+ return ri.getTemplate( name, encoding );
+ }
+
+ /**
+ * Determines if a resource is accessible via the currently
+ * configured resource loaders.
+ * <br><br>
+ * Note that the current implementation will <b>not</b>
+ * change the state of the system in any real way - so this
+ * cannot be used to pre-load the resource cache, as the
+ * previous implementation did as a side-effect.
+ * <br><br>
+ * The previous implementation exhibited extreme laziness and
+ * sloth, and the author has been flogged.
+ *
+ * @param resourceName name of the resource to search for
+ * @return true if found, false otherwise
+ * @since 1.5
+ */
+ public boolean resourceExists(String resourceName)
+ {
+ return (ri.getLoaderNameForResource(resourceName) != null);
+ }
+
+ /**
+ * Returns the current slf4j logger. Its namespace defaults to <code>org.apache.velocity</code>
+ * if it hasn't been configured using the property <code>runtime.log.name</code> or the property
+ * <code>runtime.log.instance</code>.
+ * @return A Logger object.
+ * @since 1.5
+ */
+ public Logger getLog()
+ {
+ return ri.getLog();
+ }
+
+ /**
+ * <p>
+ * Sets an application attribute (which can be any Object) that will be
+ * accessible from any component of the system that gets a
+ * RuntimeServices. This allows communication between the application
+ * environment and custom pluggable components of the Velocity engine,
+ * such as ResourceLoaders and Loggers.
+ * </p>
+ *
+ * <p>
+ * Note that there is no enforcement or rules for the key
+ * used - it is up to the application developer. However, to
+ * help make the intermixing of components possible, using
+ * the target Class name (e.g. com.foo.bar ) as the key
+ * might help avoid collision.
+ * </p>
+ *
+ * @param key object 'name' under which the object is stored
+ * @param value object to store under this key
+ */
+ public void setApplicationAttribute( Object key, Object value )
+ {
+ ri.setApplicationAttribute(key, value);
+ }
+
+ /**
+ * <p>
+ * Return an application attribute (which can be any Object)
+ * that was set by the application in order to be accessible from
+ * any component of the system that gets a RuntimeServices.
+ * This allows communication between the application
+ * environment and custom pluggable components of the
+ * Velocity engine, such as ResourceLoaders and Loggers.
+ * </p>
+ *
+ * @param key object 'name' under which the object is stored
+ * @return value object to store under this key
+ * @since 1.5
+ */
+ public Object getApplicationAttribute( Object key )
+ {
+ return ri.getApplicationAttribute(key);
+ }
+
+ /**
+ * Remove a directive.
+ * @param name name of the directive.
+ */
+ public void removeDirective(String name)
+ {
+ ri.removeDirective(name);
+ }
+
+ /**
+ * Instantiates and loads the directive with some basic checks.
+ *
+ * @param directiveClass classname of directive to load
+ */
+ public void loadDirective(String directiveClass)
+ {
+ ri.loadDirective(directiveClass);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventCartridge.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventCartridge.java
new file mode 100644
index 00000000..1bbb49f3
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventCartridge.java
@@ -0,0 +1,445 @@
+package org.apache.velocity.app.event;
+
+/*
+ * 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.Context;
+import org.apache.velocity.context.InternalContextAdapter;
+import org.apache.velocity.context.InternalEventContext;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.RuntimeServicesAware;
+import org.apache.velocity.util.introspection.Info;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>Stores the event handlers. Event handlers can be assigned on a per
+ * VelocityEngine instance basis by specifying the class names in the
+ * velocity.properties file. Event handlers may also be assigned on a per-page
+ * basis by creating a new instance of EventCartridge, adding the event
+ * handlers, and then calling attachToContext. For clarity, it's recommended
+ * that one approach or the other be followed, as the second method is primarily
+ * presented for backwards compatibility.</p>
+ * <p>Note that Event Handlers follow a filter pattern, with multiple event
+ * handlers allowed for each event. When the appropriate event occurs, all the
+ * appropriate event handlers are called in the sequence they were added to the
+ * Event Cartridge. See the javadocs of the specific event handler interfaces
+ * for more details.</p>
+ *
+ * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain </a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr. </a>
+ * @author <a href="mailto:j_a_fernandez@yahoo.com">Jose Alberto Fernandez </a>
+ * @version $Id$
+ */
+public class EventCartridge
+{
+ private List<ReferenceInsertionEventHandler> referenceHandlers = new ArrayList<>();
+ private MethodExceptionEventHandler methodExceptionHandler = null;
+ private List<IncludeEventHandler> includeHandlers = new ArrayList<>();
+ private List<InvalidReferenceEventHandler> invalidReferenceHandlers = new ArrayList<>();
+
+ /**
+ * Ensure that handlers are not initialized more than once.
+ */
+ Set<EventHandler> initializedHandlers = new HashSet<>();
+
+ protected RuntimeServices rsvc = null;
+
+ protected Logger getLog()
+ {
+ return rsvc == null ? LoggerFactory.getLogger(EventCartridge.class) : rsvc.getLog();
+ }
+
+ /**
+ * runtime services setter, called during initialization
+ *
+ * @param rs runtime services
+ * @since 2.0
+ */
+ public synchronized void setRuntimeServices(RuntimeServices rs)
+ {
+ if (rsvc == null)
+ {
+ rsvc = rs;
+ /* allow for this method to be called *after* adding event handlers */
+ for (EventHandler handler : referenceHandlers)
+ {
+ if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler))
+ {
+ ((RuntimeServicesAware) handler).setRuntimeServices(rs);
+ initializedHandlers.add(handler);
+ }
+ }
+ if (methodExceptionHandler != null &&
+ methodExceptionHandler instanceof RuntimeServicesAware &&
+ !initializedHandlers.contains(methodExceptionHandler))
+ {
+ ((RuntimeServicesAware) methodExceptionHandler).setRuntimeServices(rs);
+ initializedHandlers.add(methodExceptionHandler);
+ }
+ for (EventHandler handler : includeHandlers)
+ {
+ if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler))
+ {
+ ((RuntimeServicesAware) handler).setRuntimeServices(rs);
+ initializedHandlers.add(handler);
+ }
+ }
+ for (EventHandler handler : invalidReferenceHandlers)
+ {
+ if (handler instanceof RuntimeServicesAware && !initializedHandlers.contains(handler))
+ {
+ ((RuntimeServicesAware) handler).setRuntimeServices(rs);
+ initializedHandlers.add(handler);
+ }
+ }
+ }
+ else if (rsvc != rs)
+ {
+ throw new VelocityException("an event cartridge cannot be used by several different runtime services instances");
+ }
+ }
+
+ /**
+ * Adds an event handler(s) to the Cartridge. This method
+ * will find all possible event handler interfaces supported
+ * by the passed in object.
+ *
+ * @param ev object implementing a valid EventHandler-derived interface
+ * @return true if a supported interface, false otherwise or if null
+ */
+ public boolean addEventHandler(EventHandler ev)
+ {
+ if (ev == null)
+ {
+ return false;
+ }
+
+ boolean found = false;
+
+ if (ev instanceof ReferenceInsertionEventHandler)
+ {
+ addReferenceInsertionEventHandler((ReferenceInsertionEventHandler) ev);
+ found = true;
+ }
+
+ if (ev instanceof MethodExceptionEventHandler)
+ {
+ addMethodExceptionHandler((MethodExceptionEventHandler) ev);
+ found = true;
+ }
+
+ if (ev instanceof IncludeEventHandler)
+ {
+ addIncludeEventHandler((IncludeEventHandler) ev);
+ found = true;
+ }
+
+ if (ev instanceof InvalidReferenceEventHandler)
+ {
+ addInvalidReferenceEventHandler((InvalidReferenceEventHandler) ev);
+ found = true;
+ }
+
+ if (found && rsvc != null && ev instanceof RuntimeServicesAware && !initializedHandlers.contains(ev))
+ {
+ ((RuntimeServicesAware) ev).setRuntimeServices(rsvc);
+ initializedHandlers.add(ev);
+ }
+
+ return found;
+ }
+
+ /**
+ * Add a reference insertion event handler to the Cartridge.
+ *
+ * @param ev ReferenceInsertionEventHandler
+ * @since 1.5
+ */
+ public void addReferenceInsertionEventHandler(ReferenceInsertionEventHandler ev)
+ {
+ referenceHandlers.add(ev);
+ }
+
+ /**
+ * Add a method exception event handler to the Cartridge.
+ *
+ * @param ev MethodExceptionEventHandler
+ * @since 1.5
+ */
+ public void addMethodExceptionHandler(MethodExceptionEventHandler ev)
+ {
+ if (methodExceptionHandler == null)
+ {
+ methodExceptionHandler = ev;
+ }
+ else
+ {
+ getLog().warn("ignoring extra method exception handler");
+ }
+ }
+
+ /**
+ * Add an include event handler to the Cartridge.
+ *
+ * @param ev IncludeEventHandler
+ * @since 1.5
+ */
+ public void addIncludeEventHandler(IncludeEventHandler ev)
+ {
+ includeHandlers.add(ev);
+ }
+
+ /**
+ * Add an invalid reference event handler to the Cartridge.
+ *
+ * @param ev InvalidReferenceEventHandler
+ * @since 1.5
+ */
+ public void addInvalidReferenceEventHandler(InvalidReferenceEventHandler ev)
+ {
+ invalidReferenceHandlers.add(ev);
+ }
+
+
+ /**
+ * Removes an event handler(s) from the Cartridge. This method will find all
+ * possible event handler interfaces supported by the passed in object and
+ * remove them.
+ *
+ * @param ev object impementing a valid EventHandler-derived interface
+ * @return true if event handler was previously registered, false if not
+ * found
+ */
+ public boolean removeEventHandler(EventHandler ev)
+ {
+ if (ev == null)
+ {
+ return false;
+ }
+
+ if (ev instanceof ReferenceInsertionEventHandler)
+ {
+ return referenceHandlers.remove(ev);
+ }
+
+ if (ev instanceof MethodExceptionEventHandler)
+ {
+ if (ev == methodExceptionHandler)
+ {
+ methodExceptionHandler = null;
+ return true;
+ }
+ }
+
+ if (ev instanceof IncludeEventHandler)
+ {
+ return includeHandlers.remove(ev);
+ }
+
+ if (ev instanceof InvalidReferenceEventHandler)
+ {
+ return invalidReferenceHandlers.remove(ev);
+ }
+
+ return false;
+ }
+
+ /**
+ * Call reference insertion handlers
+ * @param context
+ * @param reference
+ * @param value
+ * @return value returned by handlers
+ * @since 2.0
+ */
+ public Object referenceInsert(InternalContextAdapter context, String reference, Object value)
+ {
+ for (ReferenceInsertionEventHandler handler : referenceHandlers)
+ {
+ value = handler.referenceInsert(context, reference, value);
+ }
+ return value;
+ }
+
+ /**
+ * Check whether this event cartridge has a method exception event handler
+ *
+ * @return true if a method exception event handler has been registered
+ * @since 2.0
+ */
+ boolean hasMethodExceptionEventHandler()
+ {
+ return methodExceptionHandler != null;
+ }
+
+ /**
+ * Call method exception event handler
+ * @param context
+ * @param claz
+ * @param method
+ * @param e exception
+ * @param info template name, line and column infos
+ * @return value returned by handler
+ * @since 2.0
+ */
+ public Object methodException(Context context, Class<?> claz, String method, Exception e, Info info)
+ {
+ if (methodExceptionHandler != null)
+ {
+ return methodExceptionHandler.methodException(context, claz, method, e, info);
+ }
+ return null;
+ }
+
+ /**
+ * Call include event handlers
+ *
+ * @param context
+ * @param includeResourcePath
+ * @param currentResourcePath
+ * @param directiveName
+ * @return include path
+ * @since 2.0
+ */
+ public String includeEvent(Context context, String includeResourcePath, String currentResourcePath, String directiveName)
+ {
+ for (IncludeEventHandler handler : includeHandlers)
+ {
+ includeResourcePath = handler.includeEvent(context, includeResourcePath, currentResourcePath, directiveName);
+ /* reflect 1.x behavior: exit after at least one execution whenever a null include path has been found */
+ if (includeResourcePath == null)
+ {
+ break;
+ }
+ }
+ return includeResourcePath;
+ }
+
+ /**
+ * Call invalid reference handlers for an invalid getter
+ *
+ * @param context
+ * @param reference
+ * @param object
+ * @param property
+ * @param info
+ * @return value returned by handlers
+ * @since 2.0
+ */
+ public Object invalidGetMethod(Context context, String reference, Object object, String property, Info info)
+ {
+ Object result = null;
+ for (InvalidReferenceEventHandler handler : invalidReferenceHandlers)
+ {
+ result = handler.invalidGetMethod(context, reference, object, property, info);
+ /* reflect 1.x behavior: exit after at least one execution whenever a non-null value has been found */
+ if (result != null)
+ {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Call invalid reference handlers for an invalid setter
+ *
+ * @param context
+ * @param leftreference
+ * @param rightreference
+ * @param info
+ * @return whether to stop further chaining in the next cartridge
+ * @since 2.0
+ */
+ public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info)
+ {
+ for (InvalidReferenceEventHandler handler : invalidReferenceHandlers)
+ {
+ if (handler.invalidSetMethod(context, leftreference, rightreference, info))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Call invalid reference handlers for an invalid method call
+ *
+ * @param context
+ * @param reference
+ * @param object
+ * @param method
+ * @param info
+ * @return value returned by handlers
+ * @since 2.0
+ */
+ public Object invalidMethod(Context context, String reference, Object object, String method, Info info)
+ {
+ Object result = null;
+ for (InvalidReferenceEventHandler handler : invalidReferenceHandlers)
+ {
+ result = handler.invalidMethod(context, reference, object, method, info);
+ /* reflect 1.x behavior: exit after at least one execution whenever a non-null value has been found */
+ if (result != null)
+ {
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Attached the EventCartridge to the context
+ *
+ * Final because not something one should mess with lightly :)
+ *
+ * @param context context to attach to
+ * @return true if successful, false otherwise
+ */
+ public final boolean attachToContext(Context context)
+ {
+ if (context instanceof InternalEventContext)
+ {
+ InternalEventContext iec = (InternalEventContext) context;
+
+ iec.attachEventCartridge(this);
+
+ /*
+ * while it's tempting to call setContext on each handler from here,
+ * this needs to be done before each method call. This is
+ * because the specific context will change as inner contexts
+ * are linked in through macros, foreach, or directly by the user.
+ */
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandler.java
new file mode 100644
index 00000000..9adbb075
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandler.java
@@ -0,0 +1,31 @@
+package org.apache.velocity.app.event;
+
+/*
+ * 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 interface for all event handlers
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface EventHandler
+{
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandlerUtil.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandlerUtil.java
new file mode 100644
index 00000000..e5390020
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/EventHandlerUtil.java
@@ -0,0 +1,279 @@
+package org.apache.velocity.app.event;
+
+/*
+ * 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.util.introspection.Info;
+
+
+/**
+ * Calls on request all registered event handlers for a particular event. Each
+ * method accepts two event cartridges (typically one from the application and
+ * one from the context). All appropriate event handlers are executed in order
+ * until a stopping condition is met. See the docs for the individual methods to
+ * see what the stopping condition is for that method.
+ *
+ * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain </a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class EventHandlerUtil {
+
+
+ /**
+ * Called before a reference is inserted. All event handlers are called in
+ * sequence. The default implementation inserts the reference as is.
+ *
+ * This is a major hotspot method called by ASTReference render.
+ *
+ * @param reference reference from template about to be inserted
+ * @param value value about to be inserted (after toString() )
+ * @param rsvc current instance of RuntimeServices
+ * @param context The internal context adapter.
+ * @return Object on which toString() should be called for output.
+ */
+ public static Object referenceInsert(RuntimeServices rsvc,
+ InternalContextAdapter context, String reference, Object value)
+ {
+ try
+ {
+ value = rsvc.getApplicationEventCartridge().referenceInsert(context, reference, value);
+ EventCartridge contextCartridge = context.getEventCartridge();
+ if (contextCartridge != null)
+ {
+ contextCartridge.setRuntimeServices(rsvc);
+ value = contextCartridge.referenceInsert(context, reference, value);
+ }
+ return value;
+ }
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Exception in event handler.",e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+
+ /**
+ * Called when a method exception is generated during Velocity merge. Only
+ * the first valid event handler in the sequence is called. The default
+ * implementation simply rethrows the exception.
+ *
+ * @param claz
+ * Class that is causing the exception
+ * @param method
+ * method called that causes the exception
+ * @param e
+ * Exception thrown by the method
+ * @param rsvc current instance of RuntimeServices
+ * @param context The internal context adapter.
+ * @param info exception location informations
+ * @return Object to return as method result
+ * @throws Exception
+ * to be wrapped and propagated to app
+ */
+ public static Object methodException(RuntimeServices rsvc,
+ InternalContextAdapter context, Class<?> claz, String method,
+ Exception e, Info info) throws Exception
+ {
+ try
+ {
+ EventCartridge ev = rsvc.getApplicationEventCartridge();
+ if (ev.hasMethodExceptionEventHandler())
+ {
+ return ev.methodException(context, claz, method, e, info);
+ }
+ EventCartridge contextCartridge = context.getEventCartridge();
+ if (contextCartridge != null)
+ {
+ contextCartridge.setRuntimeServices(rsvc);
+ return contextCartridge.methodException(context, claz, method, e, info);
+ }
+ }
+ catch (RuntimeException re)
+ {
+ throw re;
+ }
+ catch (Exception ex)
+ {
+ throw new VelocityException("Exception in event handler.", ex, rsvc.getLogContext().getStackTrace());
+ }
+
+ /* default behaviour is to re-throw exception */
+ throw e;
+ }
+
+ /**
+ * Called when an include-type directive is encountered (#include or
+ * #parse). All the registered event handlers are called unless null is
+ * returned. The default implementation always processes the included
+ * resource.
+ *
+ * @param includeResourcePath
+ * the path as given in the include directive.
+ * @param currentResourcePath
+ * the path of the currently rendering template that includes the
+ * include directive.
+ * @param directiveName
+ * name of the directive used to include the resource. (With the
+ * standard directives this is either "parse" or "include").
+ * @param rsvc current instance of RuntimeServices
+ * @param context The internal context adapter.
+ *
+ * @return a new resource path for the directive, or null to block the
+ * include from occurring.
+ */
+ public static String includeEvent(RuntimeServices rsvc,
+ InternalContextAdapter context, String includeResourcePath,
+ String currentResourcePath, String directiveName)
+ {
+ try
+ {
+ includeResourcePath = rsvc.getApplicationEventCartridge().includeEvent(context, includeResourcePath, currentResourcePath, directiveName);
+ EventCartridge contextCartridge = context.getEventCartridge();
+ if (contextCartridge != null)
+ {
+ contextCartridge.setRuntimeServices(rsvc);
+ includeResourcePath = contextCartridge.includeEvent(context, includeResourcePath, currentResourcePath, directiveName);
+ }
+ return includeResourcePath;
+ }
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Exception in event handler.", e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+
+
+ /**
+ * Called when an invalid get method is encountered.
+ *
+ * @param rsvc current instance of RuntimeServices
+ * @param context the context when the reference was found invalid
+ * @param reference complete invalid reference
+ * @param object object from reference, or null if not available
+ * @param property name of property, or null if not relevant
+ * @param info contains info on template, line, col
+ * @return substitute return value for missing reference, or null if no substitute
+ */
+ public static Object invalidGetMethod(RuntimeServices rsvc,
+ InternalContextAdapter context, String reference,
+ Object object, String property, Info info)
+ {
+ try
+ {
+ Object result = rsvc.getApplicationEventCartridge().invalidGetMethod(context, reference, object, property, info);
+ EventCartridge contextCartridge = context.getEventCartridge();
+ if (contextCartridge != null)
+ {
+ contextCartridge.setRuntimeServices(rsvc);
+ result = contextCartridge.invalidGetMethod(context, reference, object, property, info);
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Exception in event handler.", e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+
+ /**
+ * Called when an invalid set method is encountered.
+ *
+ * @param rsvc current instance of RuntimeServices
+ * @param context the context when the reference was found invalid
+ * @param leftreference left reference being assigned to
+ * @param rightreference invalid reference on the right
+ * @param info contains info on template, line, col
+ */
+ public static void invalidSetMethod(RuntimeServices rsvc,
+ InternalContextAdapter context, String leftreference,
+ String rightreference, Info info)
+ {
+ try
+ {
+ if (!rsvc.getApplicationEventCartridge().invalidSetMethod(context, leftreference, rightreference, info))
+ {
+ EventCartridge contextCartridge = context.getEventCartridge();
+ if (contextCartridge != null)
+ {
+ contextCartridge.setRuntimeServices(rsvc);
+ contextCartridge.invalidSetMethod(context, leftreference, rightreference, info);
+ }
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Exception in event handler.", e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+
+ /**
+ * Called when an invalid method is encountered.
+ *
+ * @param rsvc current instance of RuntimeServices
+ * @param context the context when the reference was found invalid
+ * @param reference complete invalid reference
+ * @param object object from reference, or null if not available
+ * @param method name of method, or null if not relevant
+ * @param info contains info on template, line, col
+ * @return substitute return value for missing reference, or null if no substitute
+ */
+ public static Object invalidMethod(RuntimeServices rsvc,
+ InternalContextAdapter context, String reference,
+ Object object, String method, Info info)
+ {
+ try
+ {
+ Object result = rsvc.getApplicationEventCartridge().invalidMethod(context, reference, object, method, info);
+ EventCartridge contextCartridge = context.getEventCartridge();
+ if (contextCartridge != null)
+ {
+ contextCartridge.setRuntimeServices(rsvc);
+ result = contextCartridge.invalidMethod(context, reference, object, method, info);
+ }
+ return result;
+ }
+ catch (RuntimeException e)
+ {
+ throw e;
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Exception in event handler.", e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/IncludeEventHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/IncludeEventHandler.java
new file mode 100644
index 00000000..30726696
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/IncludeEventHandler.java
@@ -0,0 +1,52 @@
+package org.apache.velocity.app.event;
+
+import org.apache.velocity.context.Context;
+
+/*
+ * 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.
+ */
+
+/**
+ * Event handler for include type directives (e.g. <code>#include()</code>, <code>#parse()</code>)
+ * Allows the developer to modify the path of the resource returned.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public interface IncludeEventHandler extends EventHandler
+{
+ /**
+ * Called when an include-type directive is encountered (
+ * <code>#include</code> or <code>#parse</code>). May modify the path
+ * of the resource to be included or may block the include entirely. All the
+ * registered IncludeEventHandlers are called unless null is returned. If
+ * none are registered the template at the includeResourcePath is retrieved.
+ *
+ * @param context current context
+ * @param includeResourcePath the path as given in the include directive.
+ * @param currentResourcePath the path of the currently rendering template that includes the
+ * include directive.
+ * @param directiveName name of the directive used to include the resource. (With the
+ * standard directives this is either "parse" or "include").
+ *
+ * @return a new resource path for the directive, or null to block the
+ * include from occurring.
+ */
+ String includeEvent(Context context, String includeResourcePath, String currentResourcePath, String directiveName);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/InvalidReferenceEventHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/InvalidReferenceEventHandler.java
new file mode 100644
index 00000000..f02e09b6
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/InvalidReferenceEventHandler.java
@@ -0,0 +1,88 @@
+package org.apache.velocity.app.event;
+
+/*
+ * 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.Context;
+import org.apache.velocity.util.introspection.Info;
+
+/**
+ * Event handler called when an invalid reference is encountered. Allows
+ * the application to report errors or substitute return values. May be chained
+ * in sequence; the behavior will differ per method.
+ *
+ * <p>This feature should be regarded as experimental.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public interface InvalidReferenceEventHandler extends EventHandler
+{
+
+ /**
+ * Called when object is null or there is no getter for the given
+ * property. Also called for invalid references without properties.
+ * invalidGetMethod() will be called in sequence for
+ * each link in the chain until the first non-null value is
+ * returned.
+ *
+ * @param context the context when the reference was found invalid
+ * @param reference string with complete invalid reference
+ * @param object the object referred to, or null if not found
+ * @param property the property name from the reference
+ * @param info contains template, line, column details
+ * @return substitute return value for missing reference, or null if no substitute
+ */
+ Object invalidGetMethod(Context context, String reference,
+ Object object, String property, Info info);
+
+ /**
+ * Called when object is null or there is no setter for the given
+ * property. invalidSetMethod() will be called in sequence for
+ * each link in the chain until a true value is returned. It's
+ * recommended that false be returned as a default to allow
+ * for easy chaining.
+ *
+ * @param context the context when the reference was found invalid
+ * @param leftreference left reference being assigned to
+ * @param rightreference invalid reference on the right
+ * @param info contains info on template, line, col
+ *
+ * @return if true then stop calling invalidSetMethod along the
+ * chain.
+ */
+ boolean invalidSetMethod(Context context, String leftreference,
+ String rightreference, Info info);
+
+ /**
+ * Called when object is null or the given method does not exist.
+ * invalidMethod() will be called in sequence for each link in
+ * the chain until the first non-null value is returned.
+ *
+ * @param context the context when the reference was found invalid
+ * @param reference string with complete invalid reference
+ * @param object the object referred to, or null if not found
+ * @param method the name of the (non-existent) method
+ * @param info contains template, line, column details
+ * @return substitute return value for missing reference, or null if no substitute
+ */
+ Object invalidMethod(Context context, String reference,
+ Object object, String method, Info info);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/MethodExceptionEventHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/MethodExceptionEventHandler.java
new file mode 100644
index 00000000..9661a964
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/MethodExceptionEventHandler.java
@@ -0,0 +1,52 @@
+package org.apache.velocity.app.event;
+
+import org.apache.velocity.context.Context;
+import org.apache.velocity.util.introspection.Info;
+
+/*
+ * 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.
+ */
+
+/**
+ * Event handler called when a method throws an exception. This gives the
+ * application a chance to deal with it and either
+ * return something nice, or throw.
+ *
+ * Please return what you want rendered into the output stream.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface MethodExceptionEventHandler extends EventHandler
+{
+ /**
+ * Called when a method throws an exception.
+ * Only the first registered MethodExceptionEventHandler is called. If
+ * none are registered a MethodInvocationException is thrown.
+ *
+ * @param context current context
+ * @param claz the class of the object the method is being applied to
+ * @param method the method
+ * @param e the thrown exception
+ * @param info contains template, line, column details
+ * @return an object to insert in the page
+ * @throws RuntimeException an exception to be thrown instead inserting an object
+ */
+ Object methodException(Context context, Class<?> claz, String method, Exception e, Info info);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/ReferenceInsertionEventHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/ReferenceInsertionEventHandler.java
new file mode 100644
index 00000000..a9e1d660
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/ReferenceInsertionEventHandler.java
@@ -0,0 +1,53 @@
+package org.apache.velocity.app.event;
+
+/*
+ * 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.Context;
+
+/**
+ * Reference 'Stream insertion' event handler. Called with object
+ * that will be inserted into stream via value.toString().
+ *
+ * Please return an Object that will toString() nicely :)
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface ReferenceInsertionEventHandler extends EventHandler
+{
+ /**
+ * A call-back which is executed during Velocity merge before a reference
+ * value is inserted into the output stream. All registered
+ * ReferenceInsertionEventHandlers are called in sequence. If no
+ * ReferenceInsertionEventHandlers are are registered then reference value
+ * is inserted into the output stream as is.
+ *
+ *
+ * @param context current context
+ * @param reference Reference from template about to be inserted.
+ * @param value Value about to be inserted (after its <code>toString()</code>
+ * method is called).
+ * @return Object on which <code>toString()</code> should be called for
+ * output.
+ */
+ Object referenceInsert(Context context, String reference, Object value);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeHtmlReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeHtmlReference.java
new file mode 100644
index 00000000..4bdcff25
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeHtmlReference.java
@@ -0,0 +1,59 @@
+package org.apache.velocity.app.event.implement;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+
+/*
+ * 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>Escape all HTML entities.</p>
+ * <p>Warning: escaping references this way, without knowing if they land inside plain text, inside an attribute value or elsewhere, is not usable in production.</p>
+ *
+ * @see <a href="http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/StringEscapeUtils.html#escapeHtml4%28java.lang.String%29">StringEscapeUtils</a>
+ * @author wglass
+ * @since 1.5
+ * @deprecated impractical use
+ */
+@Deprecated
+public class EscapeHtmlReference extends EscapeReference
+{
+
+ /**
+ * Escape all HTML entities.
+ *
+ * @param text
+ * @return An escaped String.
+ * @see <a href="http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/StringEscapeUtils.html#escapeHtml4%28java.lang.String%29">StringEscapeUtils</a>
+ */
+ @Override
+ protected String escape(Object text)
+ {
+ return StringEscapeUtils.escapeHtml4(text.toString());
+ }
+
+ /**
+ * @return attribute "eventhandler.escape.html.match"
+ */
+ @Override
+ protected String getMatchAttribute()
+ {
+ return "eventhandler.escape.html.match";
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeJavaScriptReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeJavaScriptReference.java
new file mode 100644
index 00000000..f6468743
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeJavaScriptReference.java
@@ -0,0 +1,59 @@
+package org.apache.velocity.app.event.implement;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+
+/*
+ * 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>Escapes the characters in a String to be suitable for use in JavaScript.</p>
+ * <p>Warning: escaping references this way, without knowing if they land inside or outside Javascript simple-quoted or double-quoted strings, is not usable in production.</p>
+ *
+ * @see <a href="http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/StringEscapeUtils.html#escapeEcmaScript%28java.lang.String%29">StringEscapeUtils</a>
+ * @author wglass
+ * @since 1.5
+ * @deprecated impractical use
+ */
+@Deprecated
+public class EscapeJavaScriptReference extends EscapeReference
+{
+
+ /**
+ * Escapes the characters in a String to be suitable for use in JavaScript.
+ *
+ * @param text
+ * @return An escaped String.
+ * @see <a href="http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/StringEscapeUtils.html#escapeEcmaScript%28java.lang.String%29">StringEscapeUtils</a>
+ */
+ @Override
+ protected String escape(Object text)
+ {
+ return StringEscapeUtils.escapeEcmaScript(text.toString());
+ }
+
+ /**
+ * @return attribute "eventhandler.escape.javascript.match"
+ */
+ @Override
+ protected String getMatchAttribute()
+ {
+ return "eventhandler.escape.javascript.match";
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeReference.java
new file mode 100644
index 00000000..17738a04
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeReference.java
@@ -0,0 +1,163 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.ReferenceInsertionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.RuntimeServicesAware;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.slf4j.Logger;
+
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Base class for escaping references. To use it, override the following methods:
+ * <DL>
+ * <DT><code>String escape(String text)</code></DT>
+ * <DD>escape the provided text</DD>
+ * <DT><code>String getMatchAttribute()</code></DT>
+ * <DD>retrieve the configuration attribute used to match references (see below)</DD>
+ * </DL>
+ *
+ * <P>By default, all references are escaped. However, by setting the match attribute
+ * in the configuration file to a regular expression, users can specify which references
+ * to escape. For example the following configuration property tells the EscapeSqlReference
+ * event handler to only escape references that start with "sql".
+ * (e.g. <code>$sql</code>, <code>$sql.toString(),</code>, etc).
+ *
+ * <PRE>
+ * <CODE>eventhandler.escape.sql.match = sql.*</CODE>
+ * </PRE>
+ *
+ * Regular expressions should follow the format used by the Java language. More info in the
+ * <a href="http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">Pattern class documentation</a>.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain </a>
+ * @version $Id$
+ * @since 1.5
+ */
+public abstract class EscapeReference implements ReferenceInsertionEventHandler,RuntimeServicesAware {
+
+ private RuntimeServices rs;
+
+ private String matchRegExp = null;
+
+ protected Logger log;
+
+ /**
+ * Escape the given text. Override this in a subclass to do the actual
+ * escaping.
+ *
+ * @param text the text to escape
+ * @return the escaped text
+ */
+ protected abstract String escape(Object text);
+
+ /**
+ * Specify the configuration attribute that specifies the
+ * regular expression. Ideally should be in a form
+ * <pre><code>eventhandler.escape.XYZ.match</code></pre>
+ *
+ * <p>where <code>XYZ</code> is the type of escaping being done.
+ * @return configuration attribute
+ */
+ protected abstract String getMatchAttribute();
+
+ /**
+ * Escape the provided text if it matches the configured regular expression.
+ *
+ * @param reference
+ * @param value
+ * @return Escaped text.
+ */
+ @Override
+ public Object referenceInsert(Context context, String reference, Object value)
+ {
+ if(value == null)
+ {
+ return value;
+ }
+
+ if (matchRegExp == null)
+ {
+ return escape(value);
+ }
+
+ else if (reference.matches(matchRegExp))
+ {
+ return escape(value);
+ }
+
+ else
+ {
+ return value;
+ }
+ }
+
+ /**
+ * Called automatically when event cartridge is initialized.
+ *
+ * @param rs instance of RuntimeServices
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs)
+ {
+ this.rs = rs;
+ log = rs.getLog("event");
+
+ // Get the regular expression pattern.
+ matchRegExp = StringUtils.trim(rs.getString(getMatchAttribute()));
+ if (org.apache.commons.lang3.StringUtils.isEmpty(matchRegExp))
+ {
+ matchRegExp = null;
+ }
+
+ // Test the regular expression for a well formed pattern
+ if (matchRegExp != null)
+ {
+ try
+ {
+ "".matches(matchRegExp);
+ }
+ catch (PatternSyntaxException E)
+ {
+ log.error("Invalid regular expression '{}'. No escaping will be performed.",
+ matchRegExp, E);
+ matchRegExp = null;
+ }
+ }
+
+ }
+
+ /**
+ * Retrieve a reference to RuntimeServices. Use this for checking additional
+ * configuration properties.
+ *
+ * @return The current runtime services object.
+ */
+ protected RuntimeServices getRuntimeServices()
+ {
+ return rs;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeSqlReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeSqlReference.java
new file mode 100644
index 00000000..46be4c69
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeSqlReference.java
@@ -0,0 +1,52 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.
+ */
+
+/**
+ * Escapes the characters in a String to be suitable to pass to an SQL query.
+ *
+ * @author wglass
+ * @since 1.5
+ */
+public class EscapeSqlReference extends EscapeReference
+{
+
+ /**
+ * Escapes the characters in a String to be suitable to pass to an SQL query.
+ *
+ * @param text
+ * @return An escaped string.
+ */
+ @Override
+ protected String escape(Object text)
+ {
+ return text.toString().replaceAll("'", "''");
+ }
+
+ /**
+ * @return attribute "eventhandler.escape.sql.match"
+ */
+ @Override
+ protected String getMatchAttribute()
+ {
+ return "eventhandler.escape.sql.match";
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeXmlReference.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeXmlReference.java
new file mode 100644
index 00000000..0905c513
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/EscapeXmlReference.java
@@ -0,0 +1,58 @@
+package org.apache.velocity.app.event.implement;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+
+/*
+ * 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>Escape all XML entities, suitable for placing the output inside an XML (1.0) text node or attribute value.</p>
+ * <p>Warning: escaping references this way, without knowing if they land inside plain text, inside an attribute value or elsewhere, is not usable in production.</p>
+ *
+ * @see <a href="http://jakarta.apache.org/commons/lang/api/org/apache/commons/lang/StringEscapeUtils.html#escapeSql(java.lang.String)">StringEscapeUtils</a>
+ * @author wglass
+ * @since 1.5
+ * @deprecated impractical use
+ */
+public class EscapeXmlReference extends EscapeReference
+{
+
+ /**
+ * Escape all XML entities.
+ *
+ * @param text
+ * @return An escaped String.
+ * @see <a href="http://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons/lang3/StringEscapeUtils.html#escapeXml10%28java.lang.String%29">StringEscapeUtils</a>
+ */
+ @Override
+ protected String escape(Object text)
+ {
+ return StringEscapeUtils.escapeXml10(text.toString());
+ }
+
+ /**
+ * @return attribute "eventhandler.escape.xml.match"
+ */
+ @Override
+ protected String getMatchAttribute()
+ {
+ return "eventhandler.escape.xml.match";
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeNotFound.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeNotFound.java
new file mode 100644
index 00000000..40fcf5d6
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeNotFound.java
@@ -0,0 +1,124 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.IncludeEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.ContextAware;
+import org.apache.velocity.util.RuntimeServicesAware;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.slf4j.Logger;
+
+/**
+ * Simple event handler that checks to see if an included page is available.
+ * If not, it includes a designated replacement page instead.
+ *
+ * <P>By default, the name of the replacement page is "notfound.vm", however this
+ * page name can be changed by setting the Velocity property
+ * <code>eventhandler.include.notfound</code>, for example:</p>
+ * <pre><code>
+ * eventhandler.include.notfound = error.vm
+ * </code></pre>
+ * <p>The name of the missing resource is put into the Velocity context, under the
+ * key "missingResource", so that the "notfound" template can report the missing
+ * resource with a Velocity reference, like:
+ * <code>$missingResource</code>
+ * </p>
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class IncludeNotFound implements IncludeEventHandler, RuntimeServicesAware, ContextAware {
+
+ private static final String DEFAULT_NOT_FOUND = "notfound.vm";
+ private static final String PROPERTY_NOT_FOUND = "eventhandler.include.notfound";
+ private RuntimeServices rs = null;
+ String notfound;
+ Context context;
+
+ protected Logger log;
+
+ /**
+ * Check to see if included file exists, and display "not found" page if it
+ * doesn't. If "not found" page does not exist, log an error and return
+ * null.
+ *
+ * @param includeResourcePath
+ * @param currentResourcePath
+ * @param directiveName
+ * @return message.
+ */
+ @Override
+ public String includeEvent(
+ Context context,
+ String includeResourcePath,
+ String currentResourcePath,
+ String directiveName)
+ {
+
+ /*
+ * check to see if page exists
+ */
+ boolean exists = (rs.getLoaderNameForResource(includeResourcePath) != null);
+ if (!exists)
+ {
+ context.put("missingResource", includeResourcePath);
+ if (rs.getLoaderNameForResource(notfound) != null)
+ {
+ return notfound;
+ }
+ else
+ {
+ /*
+ * can't find not found, so display nothing
+ */
+ log.error("Can't find include not found page: {}", notfound);
+ return null;
+ }
+ }
+ else
+ return includeResourcePath;
+ }
+
+
+ /**
+ * @see org.apache.velocity.util.RuntimeServicesAware#setRuntimeServices(org.apache.velocity.runtime.RuntimeServices)
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs)
+ {
+ this.rs = rs;
+ log = rs.getLog("event");
+ notfound = StringUtils.trim(rs.getString(PROPERTY_NOT_FOUND, DEFAULT_NOT_FOUND));
+ }
+
+ /**
+ * @see org.apache.velocity.util.ContextAware#setContext(org.apache.velocity.context.Context)
+ */
+ @Override
+ public void setContext(Context context)
+ {
+ this.context = context;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeRelativePath.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeRelativePath.java
new file mode 100644
index 00000000..1c7e0b2d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/IncludeRelativePath.java
@@ -0,0 +1,72 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.IncludeEventHandler;
+import org.apache.velocity.context.Context;
+
+/**
+ * <p>Event handler that looks for included files relative to the path of the
+ * current template. The handler assumes that paths are separated by a forward
+ * slash "/" or backwards slash "\".
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain </a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class IncludeRelativePath implements IncludeEventHandler {
+
+ /**
+ * Return path relative to the current template's path.
+ *
+ * @param includeResourcePath the path as given in the include directive.
+ * @param currentResourcePath the path of the currently rendering template that includes the
+ * include directive.
+ * @param directiveName name of the directive used to include the resource. (With the
+ * standard directives this is either "parse" or "include").
+
+ * @return new path relative to the current template's path
+ */
+ @Override
+ public String includeEvent(
+ Context context,
+ String includeResourcePath,
+ String currentResourcePath,
+ String directiveName)
+ {
+ // if the resource name starts with a slash, it's not a relative path
+ if (includeResourcePath.startsWith("/") || includeResourcePath.startsWith("\\") ) {
+ return includeResourcePath;
+ }
+
+ int lastslashpos = Math.max(
+ currentResourcePath.lastIndexOf("/"),
+ currentResourcePath.lastIndexOf("\\")
+ );
+
+ // root of resource tree
+ if (lastslashpos == -1) {
+ return includeResourcePath;
+ }
+
+ // prepend path to the include path
+ return currentResourcePath.substring(0,lastslashpos) + "/" + includeResourcePath;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/InvalidReferenceInfo.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/InvalidReferenceInfo.java
new file mode 100644
index 00000000..3529226a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/InvalidReferenceInfo.java
@@ -0,0 +1,64 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.util.introspection.Info;
+
+/**
+ * Convenience class to use when reporting out invalid syntax
+ * with line, column, and template name.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain </a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class InvalidReferenceInfo extends Info
+{
+ private String invalidReference;
+
+ public InvalidReferenceInfo(String invalidReference, Info info)
+ {
+ super(info.getTemplateName(),info.getLine(),info.getColumn());
+ this.invalidReference = invalidReference;
+ }
+
+ /**
+ * Get the specific invalid reference string.
+ * @return the invalid reference string
+ */
+ public String getInvalidReference()
+ {
+ return invalidReference;
+ }
+
+
+
+ /**
+ * Formats a textual representation of this object as <code>SOURCE
+ * [line X, column Y]: invalidReference</code>.
+ *
+ * @return String representing this object.
+ */
+ public String toString()
+ {
+ return getTemplateName() + " [line " + getLine() + ", column " +
+ getColumn() + "]: " + invalidReference;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/PrintExceptions.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/PrintExceptions.java
new file mode 100644
index 00000000..ea88eafe
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/PrintExceptions.java
@@ -0,0 +1,121 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.MethodExceptionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.RuntimeServicesAware;
+import org.apache.velocity.util.introspection.Info;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Simple event handler that renders method exceptions in the page
+ * rather than throwing the exception. Useful for debugging.
+ *
+ * <P>By default this event handler renders an error message containing the class and method which generated
+ * the exception, the exception name and its message.
+ *
+ * To render the reference and the location in the template, set the property <code>eventhandler.methodexception.templateinfo</code>
+ * to <code>true</code>.
+ *
+ * To render the stack trace, set the property <code>eventhandler.methodexception.stacktrace</code>
+ * to <code>true</code>.
+ *
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class PrintExceptions implements MethodExceptionEventHandler, RuntimeServicesAware
+{
+
+ private static String SHOW_TEMPLATE_INFO = "eventhandler.methodexception.templateinfo";
+ private static String SHOW_STACK_TRACE = "eventhandler.methodexception.stacktrace";
+
+ /** Reference to the runtime service */
+ private RuntimeServices rs = null;
+
+ /**
+ * Render the method exception, and optionally the exception message and stack trace.
+ *
+ * @param context current context
+ * @param claz the class of the object the method is being applied to
+ * @param method the method
+ * @param e the thrown exception
+ * @param info template name and line, column informations
+ * @return an object to insert in the page
+ */
+ @Override
+ public Object methodException(Context context, Class<?> claz, String method, Exception e, Info info)
+ {
+ boolean showTemplateInfo = rs.getBoolean(SHOW_TEMPLATE_INFO, false);
+ boolean showStackTrace = rs.getBoolean(SHOW_STACK_TRACE,false);
+
+ StringBuilder st = new StringBuilder();
+ st.append("Exception while executing method ").append(claz.toString()).append(".").append(method);
+ st.append(": ").append(e.getClass().getName()).append(": ").append(e.getMessage());
+
+ if (showTemplateInfo)
+ {
+ st.append(" at ").append(info.getTemplateName()).append(" (line ").append(info.getLine()).append(", column ").append(info.getColumn()).append(")");
+ }
+ if (showStackTrace)
+ {
+ st.append(System.lineSeparator()).append(getStackTrace(e));
+ }
+ return st.toString();
+
+ }
+
+ private static String getStackTrace(Throwable throwable)
+ {
+ PrintWriter printWriter = null;
+ try
+ {
+ StringWriter stackTraceWriter = new StringWriter();
+ printWriter = new PrintWriter(stackTraceWriter);
+ throwable.printStackTrace(printWriter);
+ printWriter.flush();
+ return stackTraceWriter.toString();
+ }
+ finally
+ {
+ if (printWriter != null)
+ {
+ printWriter.close();
+ }
+ }
+ }
+
+
+ /**
+ * @see org.apache.velocity.util.RuntimeServicesAware#setRuntimeServices(org.apache.velocity.runtime.RuntimeServices)
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs)
+ {
+ this.rs = rs;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/ReportInvalidReferences.java b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/ReportInvalidReferences.java
new file mode 100644
index 00000000..2f9074f7
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/app/event/implement/ReportInvalidReferences.java
@@ -0,0 +1,193 @@
+package org.apache.velocity.app.event.implement;
+
+/*
+ * 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.InvalidReferenceEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.RuntimeServicesAware;
+import org.apache.velocity.util.introspection.Info;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Use this event handler to flag invalid references. Since this
+ * is intended to be used for a specific request, this should be
+ * used as a local event handler attached to a specific context
+ * instead of being globally defined in the Velocity properties file.
+ *
+ * <p>
+ * Note that InvalidReferenceHandler can be used
+ * in two modes. If the Velocity properties file contains the following:
+ * <pre>
+ * event_handler.invalid_references.exception = true
+ * </pre>
+ * then the event handler will throw a ParseErrorRuntimeException upon
+ * hitting the first invalid reference. This stops processing and is
+ * passed through to the application code. The ParseErrorRuntimeException
+ * contain information about the template name, line number, column number,
+ * and invalid reference.
+ *
+ * <p>
+ * If this configuration setting is false or omitted then the page
+ * will be processed as normal, but all invalid references will be collected
+ * in a List of InvalidReferenceInfo objects.
+ *
+ * <p>This feature should be regarded as experimental.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class ReportInvalidReferences implements
+ InvalidReferenceEventHandler, RuntimeServicesAware
+{
+ public static final String EVENTHANDLER_INVALIDREFERENCE_EXCEPTION = "event_handler.invalid_references.exception";
+
+ @Deprecated
+ public static final String OLD_EVENTHANDLER_INVALIDREFERENCE_EXCEPTION = "eventhandler.invalidreference.exception";
+
+ /**
+ * List of InvalidReferenceInfo objects
+ */
+ List<InvalidReferenceInfo> invalidReferences = new ArrayList<>();
+
+ /**
+ * If true, stop at the first invalid reference and throw an exception.
+ */
+ private boolean stopOnFirstInvalidReference = false;
+
+
+ /**
+ * Collect the error and/or throw an exception, depending on configuration.
+ *
+ * @param context the context when the reference was found invalid
+ * @param reference string with complete invalid reference
+ * @param object the object referred to, or null if not found
+ * @param property the property name from the reference
+ * @param info contains template, line, column details
+ * @return always returns null
+ * @throws ParseErrorException
+ */
+ @Override
+ public Object invalidGetMethod(Context context, String reference, Object object,
+ String property, Info info)
+ {
+ reportInvalidReference(reference, info);
+ return null;
+ }
+
+ /**
+ * Collect the error and/or throw an exception, depending on configuration.
+ *
+ * @param context the context when the reference was found invalid
+ * @param reference complete invalid reference
+ * @param object the object referred to, or null if not found
+ * @param method the property name from the reference
+ * @param info contains template, line, column details
+ * @return always returns null
+ * @throws ParseErrorException
+ */
+ @Override
+ public Object invalidMethod(Context context, String reference, Object object,
+ String method, Info info)
+ {
+ if (reference == null)
+ {
+ reportInvalidReference(object.getClass().getName() + "." + method, info);
+ }
+ else
+ {
+ reportInvalidReference(reference, info);
+ }
+ return null;
+ }
+
+ /**
+ * Collect the error and/or throw an exception, depending on configuration.
+ *
+ * @param context the context when the reference was found invalid
+ * @param leftreference left reference being assigned to
+ * @param rightreference invalid reference on the right
+ * @param info contains info on template, line, col
+ * @return loop to end -- always returns false
+ */
+ @Override
+ public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info)
+ {
+ reportInvalidReference(leftreference, info);
+ return false;
+ }
+
+
+ /**
+ * Check for an invalid reference and collect the error or throw an exception
+ * (depending on configuration).
+ *
+ * @param reference the invalid reference
+ * @param info line, column, template name
+ */
+ private void reportInvalidReference(String reference, Info info)
+ {
+ InvalidReferenceInfo invalidReferenceInfo = new InvalidReferenceInfo(reference, info);
+ invalidReferences.add(invalidReferenceInfo);
+
+ if (stopOnFirstInvalidReference)
+ {
+ throw new ParseErrorException(
+ "Error in page - invalid reference. ",
+ info,
+ invalidReferenceInfo.getInvalidReference());
+ }
+ }
+
+
+ /**
+ * All invalid references during the processing of this page.
+ * @return a List of InvalidReferenceInfo objects
+ */
+ public List<InvalidReferenceInfo> getInvalidReferences()
+ {
+ return invalidReferences;
+ }
+
+
+ /**
+ * Called automatically when event cartridge is initialized.
+ * @param rs RuntimeServices object assigned during initialization
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs)
+ {
+ Boolean b = rs.getConfiguration().getBoolean(OLD_EVENTHANDLER_INVALIDREFERENCE_EXCEPTION, null);
+ if (b == null)
+ {
+ b = rs.getConfiguration().getBoolean(EVENTHANDLER_INVALIDREFERENCE_EXCEPTION, false);
+ }
+ else
+ {
+ rs.getLog().warn("configuration key '{}' has been deprecated in favor of '{}'", OLD_EVENTHANDLER_INVALIDREFERENCE_EXCEPTION, EVENTHANDLER_INVALIDREFERENCE_EXCEPTION);
+ }
+ stopOnFirstInvalidReference = b;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/AbstractContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/AbstractContext.java
new file mode 100644
index 00000000..2fba374a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/AbstractContext.java
@@ -0,0 +1,274 @@
+package org.apache.velocity.context;
+
+/*
+ * 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 is the abstract base class for all conventional
+ * Velocity Context implementations. Simply extend this class
+ * and implement the abstract routines that access your preferred
+ * storage method.
+ *
+ * Takes care of context chaining.
+ *
+ * Also handles / enforces policy on null keys and values :
+ *
+ * <ul>
+ * <li> Null keys and values are accepted and basically dropped.
+ * <li> If you place an object into the context with a null key, it
+ * will be ignored and logged.
+ * <li> If you try to place a null into the context with any key, it
+ * will be dropped and logged.
+ * </ul>
+ *
+ * The default implementation of this for application use is
+ * org.apache.velocity.VelocityContext.
+ *
+ * All thanks to Fedor for the chaining idea.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:fedor.karpelevitch@home.com">Fedor Karpelevitch</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+
+public abstract class AbstractContext extends InternalContextBase
+ implements Context
+{
+ /**
+ * the chained Context if any
+ */
+ private Context innerContext = null;
+
+ /**
+ * Implement to return a value from the context storage.
+ * <br><br>
+ * The implementation of this method is required for proper
+ * operation of a Context implementation in general
+ * Velocity use.
+ *
+ * @param key key whose associated value is to be returned
+ * @return object stored in the context
+ */
+ public abstract Object internalGet( String key );
+
+ /**
+ * Implement to put a value into the context storage.
+ * <br><br>
+ * The implementation of this method is required for
+ * proper operation of a Context implementation in
+ * general Velocity use.
+ *
+ * @param key key with which to associate the value
+ * @param value value to be associated with the key
+ * @return previously stored value if exists, or null
+ */
+ public abstract Object internalPut( String key, Object value );
+
+ /**
+ * Implement to determine if a key is in the storage.
+ * <br><br>
+ * Currently, this method is not used internally by
+ * the Velocity engine.
+ *
+ * @param key key to test for existence
+ * @return true if found, false if not
+ */
+ public abstract boolean internalContainsKey(String key);
+
+ /**
+ * Implement to return an object array of key
+ * strings from your storage.
+ * <br><br>
+ * Currently, this method is not used internally by
+ * the Velocity engine.
+ *
+ * @return array of keys
+ */
+ public abstract String[] internalGetKeys();
+
+ /**
+ * Implement to remove an item from your storage.
+ * <br><br>
+ * Currently, this method is not used internally by
+ * the Velocity engine.
+ *
+ * @param key key to remove
+ * @return object removed if exists, else null
+ */
+ public abstract Object internalRemove(String key);
+
+ /**
+ * default CTOR
+ */
+ public AbstractContext()
+ {
+ }
+
+ /**
+ * Chaining constructor accepts a Context argument.
+ * It will relay get() operations into this Context
+ * in the even the 'local' get() returns null.
+ *
+ * @param inner context to be chained
+ */
+ public AbstractContext( Context inner )
+ {
+ innerContext = inner;
+
+ /*
+ * now, do a 'forward pull' of event cartridge so
+ * it's accessible, bringing to the top level.
+ */
+
+ if (innerContext instanceof InternalEventContext )
+ {
+ attachEventCartridge( ( (InternalEventContext) innerContext).getEventCartridge() );
+ }
+ }
+
+ /**
+ * Adds a name/value pair to the context.
+ *
+ * @param key The name to key the provided value with.
+ * @param value The corresponding value.
+ * @return Object that was replaced in the the Context if
+ * applicable or null if not.
+ */
+ @Override
+ public Object put(String key, Object value)
+ {
+ /*
+ * don't even continue if key is null
+ */
+ if (key == null)
+ {
+ return null;
+ }
+
+ /*
+ * We always use string interning here:
+ * 1) speed is generally less critical when populating the context than during parsing or rendering
+ * 2) a context key is very likely to be used several times, or even a lot of times, in the template (but does it save memory if runtime.string_interning is false?)
+ * 3) last but not least: we don't have access to RuntimeServices from here, the reengineering would be painful...
+ */
+ return internalPut(key.intern(), value);
+ }
+
+ /**
+ * Gets the value corresponding to the provided key from the context.
+ *
+ * Supports the chaining context mechanism. If the 'local' context
+ * doesn't have the value, we try to get it from the chained context.
+ *
+ * @param key The name of the desired value.
+ * @return The value corresponding to the provided key or null if
+ * the key param is null.
+ */
+ @Override
+ public Object get(String key)
+ {
+ /*
+ * punt if key is null
+ */
+
+ if (key == null)
+ {
+ return null;
+ }
+
+ /*
+ * get the object for this key. If null, and we are chaining another Context
+ * call the get() on it.
+ */
+
+ Object o = internalGet( key );
+
+ if (o == null && innerContext != null)
+ {
+ o = innerContext.get( key );
+ }
+
+ return o;
+ }
+
+ /**
+ * Indicates whether the specified key is in the context. Provided for
+ * debugging purposes.
+ *
+ * @param key The key to look for.
+ * @return true if the key is in the context, false if not.
+ */
+ @Override
+ public boolean containsKey(String key)
+ {
+ if (key == null)
+ {
+ return false;
+ }
+
+ boolean exists = internalContainsKey(key);
+ if (!exists && innerContext != null)
+ {
+ exists = innerContext.containsKey(key);
+ }
+
+ return exists;
+ }
+
+ /**
+ * Get all the keys for the values in the context
+ * @return Object[] of keys in the Context. Does not return
+ * keys in chained context.
+ */
+ @Override
+ public String[] getKeys()
+ {
+ return internalGetKeys();
+ }
+
+ /**
+ * Removes the value associated with the specified key from the context.
+ *
+ * @param key The name of the value to remove.
+ * @return The value that the key was mapped to, or <code>null</code>
+ * if unmapped.
+ */
+ @Override
+ public Object remove(String key)
+ {
+ if (key == null)
+ {
+ return null;
+ }
+
+ return internalRemove(key);
+ }
+
+ /**
+ * returns innerContext if one is chained
+ *
+ * @return Context if chained, <code>null</code> if not
+ */
+ public Context getChainedContext()
+ {
+ return innerContext;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/ChainedInternalContextAdapter.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/ChainedInternalContextAdapter.java
new file mode 100755
index 00000000..46c4f018
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/ChainedInternalContextAdapter.java
@@ -0,0 +1,284 @@
+package org.apache.velocity.context;
+
+/*
+ * 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.runtime.resource.Resource;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+
+import java.util.List;
+
+/**
+ * This is an abstract internal-use-only context implementation to be
+ * used as a subclass for other internal-use-only contexts that wrap
+ * other internal-use-only contexts.
+ *
+ * We use this context to make it easier to chain an existing context
+ * as part of a new context implementation. It just delegates everything
+ * to the inner/parent context. Subclasses then only need to override
+ * the methods relevant to them.
+ *
+ * @author Nathan Bubna
+ * @version $Id: ChainedInternalContextAdapter.java 685724 2008-08-13 23:12:12Z nbubna $
+ * @since 1.6
+ */
+public abstract class ChainedInternalContextAdapter implements InternalContextAdapter
+{
+ /** the parent context */
+ protected InternalContextAdapter wrappedContext = null;
+
+ /**
+ * CTOR, wraps an ICA
+ * @param inner context
+ */
+ public ChainedInternalContextAdapter(InternalContextAdapter inner)
+ {
+ wrappedContext = inner;
+ }
+
+ /**
+ * Return the inner / user context.
+ * @return The inner / user context.
+ */
+ @Override
+ public Context getInternalUserContext()
+ {
+ return wrappedContext.getInternalUserContext();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalWrapperContext#getBaseContext()
+ */
+ @Override
+ public InternalContextAdapter getBaseContext()
+ {
+ return wrappedContext.getBaseContext();
+ }
+
+ /**
+ * Retrieves from parent context.
+ *
+ * @param key name of item to get
+ * @return stored object or null
+ */
+ @Override
+ public Object get(String key)
+ {
+ return wrappedContext.get(key);
+ }
+
+ /**
+ * Put method also stores values in parent context
+ *
+ * @param key name of item to set
+ * @param value object to set to key
+ * @return old stored object
+ */
+ @Override
+ public Object put(String key, Object value)
+ {
+ /*
+ * just put in the local context
+ */
+ return wrappedContext.put(key, value);
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#containsKey(java.lang.String)
+ */
+ @Override
+ public boolean containsKey(String key)
+ {
+ return wrappedContext.containsKey(key);
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#getKeys()
+ */
+ @Override
+ public String[] getKeys()
+ {
+ return wrappedContext.getKeys();
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#remove(java.lang.String)
+ */
+ @Override
+ public Object remove(String key)
+ {
+ return wrappedContext.remove(key);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#pushCurrentTemplateName(java.lang.String)
+ */
+ @Override
+ public void pushCurrentTemplateName(String s)
+ {
+ wrappedContext.pushCurrentTemplateName(s);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#popCurrentTemplateName()
+ */
+ @Override
+ public void popCurrentTemplateName()
+ {
+ wrappedContext.popCurrentTemplateName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentTemplateName()
+ */
+ @Override
+ public String getCurrentTemplateName()
+ {
+ return wrappedContext.getCurrentTemplateName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getTemplateNameStack()
+ */
+ @Override
+ public String[] getTemplateNameStack()
+ {
+ return wrappedContext.getTemplateNameStack();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#pushCurrentMacroName(java.lang.String)
+ */
+ @Override
+ public void pushCurrentMacroName(String s)
+ {
+ wrappedContext.pushCurrentMacroName(s);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#popCurrentMacroName()
+ */
+ @Override
+ public void popCurrentMacroName()
+ {
+ wrappedContext.popCurrentMacroName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentMacroName()
+ */
+ @Override
+ public String getCurrentMacroName()
+ {
+ return wrappedContext.getCurrentMacroName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentMacroCallDepth()
+ */
+ @Override
+ public int getCurrentMacroCallDepth()
+ {
+ return wrappedContext.getCurrentMacroCallDepth();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getMacroNameStack()
+ */
+ @Override
+ public String[] getMacroNameStack()
+ {
+ return wrappedContext.getMacroNameStack();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#icacheGet(java.lang.Object)
+ */
+ @Override
+ public IntrospectionCacheData icacheGet(Object key)
+ {
+ return wrappedContext.icacheGet(key);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#icachePut(java.lang.Object, org.apache.velocity.util.introspection.IntrospectionCacheData)
+ */
+ @Override
+ public void icachePut(Object key, IntrospectionCacheData o)
+ {
+ wrappedContext.icachePut(key, o);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#setMacroLibraries(List)
+ */
+ @Override
+ public void setMacroLibraries(List<Template> macroLibraries)
+ {
+ wrappedContext.setMacroLibraries(macroLibraries);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getMacroLibraries()
+ */
+ @Override
+ public List<Template> getMacroLibraries()
+ {
+ return wrappedContext.getMacroLibraries();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalEventContext#attachEventCartridge(org.apache.velocity.app.event.EventCartridge)
+ */
+ @Override
+ public EventCartridge attachEventCartridge(EventCartridge ec)
+ {
+ return wrappedContext.attachEventCartridge(ec);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalEventContext#getEventCartridge()
+ */
+ @Override
+ public EventCartridge getEventCartridge()
+ {
+ return wrappedContext.getEventCartridge();
+ }
+
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#setCurrentResource(org.apache.velocity.runtime.resource.Resource)
+ */
+ @Override
+ public void setCurrentResource(Resource r)
+ {
+ wrappedContext.setCurrentResource(r);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentResource()
+ */
+ @Override
+ public Resource getCurrentResource()
+ {
+ return wrappedContext.getCurrentResource();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/Context.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/Context.java
new file mode 100644
index 00000000..f0d51f8f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/Context.java
@@ -0,0 +1,79 @@
+package org.apache.velocity.context;
+
+/*
+ * 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 describing the application data context. This set of
+ * routines is used by the application to set and remove 'named' data
+ * object to pass them to the template engine to use when rendering
+ * a template.
+ *
+ * This is the same set of methods supported by the original Context
+ * class
+ *
+ * @see org.apache.velocity.context.AbstractContext
+ * @see org.apache.velocity.VelocityContext
+ *
+ * @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 interface Context
+{
+ /**
+ * Adds a name/value pair to the context.
+ *
+ * @param key The name to key the provided value with.
+ * @param value The corresponding value.
+ * @return The old object or null if there was no old object.
+ */
+ Object put(String key, Object value);
+
+ /**
+ * Gets the value corresponding to the provided key from the context.
+ *
+ * @param key The name of the desired value.
+ * @return The value corresponding to the provided key.
+ */
+ Object get(String key);
+
+ /**
+ * Indicates whether the specified key is in the context.
+ *
+ * @param key The key to look for.
+ * @return Whether the key is in the context.
+ */
+ boolean containsKey(String key);
+
+ /**
+ * Get all the keys for the values in the context.
+ * @return All the keys for the values in the context.
+ */
+ String[] getKeys();
+
+ /**
+ * Removes the value associated with the specified key from the context.
+ *
+ * @param key The name of the value to remove.
+ * @return The value that the key was mapped to, or <code>null</code>
+ * if unmapped.
+ */
+ Object remove(String key);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapter.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapter.java
new file mode 100644
index 00000000..99f96d8c
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapter.java
@@ -0,0 +1,36 @@
+package org.apache.velocity.context;
+
+/*
+ * 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 to bring all necessary internal and user contexts together.
+ * this is what the AST expects to deal with. If anything new comes
+ * along, add it here.
+ *
+ * I will rename soon :)
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+
+public interface InternalContextAdapter
+ extends InternalHousekeepingContext, Context, InternalWrapperContext, InternalEventContext
+{
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapterImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapterImpl.java
new file mode 100644
index 00000000..9587e510
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextAdapterImpl.java
@@ -0,0 +1,363 @@
+package org.apache.velocity.context;
+
+/*
+ * 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.runtime.resource.Resource;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+
+import java.util.List;
+
+/**
+ * This adapter class is the container for all context types for internal
+ * use. The AST now uses this class rather than the app-level Context
+ * interface to allow flexibility in the future.
+ *
+ * Currently, we have two context interfaces which must be supported :
+ * <ul>
+ * <li> Context : used for application/template data access
+ * <li> InternalHousekeepingContext : used for internal housekeeping and caching
+ * <li> InternalWrapperContext : used for getting root cache context and other
+ * such.
+ * <li> InternalEventContext : for event handling.
+ * </ul>
+ *
+ * This class implements the two interfaces to ensure that all methods are
+ * supported. When adding to the interfaces, or adding more context
+ * functionality, the interface is the primary definition, so alter that first
+ * and then all classes as necessary. As of this writing, this would be
+ * the only class affected by changes to InternalContext
+ *
+ * This class ensures that an InternalContextBase is available for internal
+ * use. If an application constructs their own Context-implementing
+ * object w/o subclassing AbstractContext, it may be that support for
+ * InternalContext is not available. Therefore, InternalContextAdapter will
+ * create an InternalContextBase if necessary for this support. Note that
+ * if this is necessary, internal information such as node-cache data will be
+ * lost from use to use of the context. This may or may not be important,
+ * depending upon application.
+ *
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public final class InternalContextAdapterImpl implements InternalContextAdapter
+{
+ /**
+ * the user data Context that we are wrapping
+ */
+ Context context = null;
+
+ /**
+ * the ICB we are wrapping. We may need to make one
+ * if the user data context implementation doesn't
+ * support one. The default AbstractContext-derived
+ * VelocityContext does, and it's recommended that
+ * people derive new contexts from AbstractContext
+ * rather than piecing things together
+ */
+ InternalHousekeepingContext icb = null;
+
+ /**
+ * The InternalEventContext that we are wrapping. If
+ * the context passed to us doesn't support it, no
+ * biggie. We don't make it for them - since its a
+ * user context thing, nothing gained by making one
+ * for them now
+ */
+ InternalEventContext iec = null;
+
+ /**
+ * CTOR takes a Context and wraps it, delegating all 'data' calls
+ * to it.
+ *
+ * For support of internal contexts, it will create an InternalContextBase
+ * if need be.
+ * @param c
+ */
+ public InternalContextAdapterImpl( Context c )
+ {
+ context = c;
+
+ if ( !( c instanceof InternalHousekeepingContext ))
+ {
+ icb = new InternalContextBase();
+ }
+ else
+ {
+ icb = (InternalHousekeepingContext) context;
+ }
+
+ if ( c instanceof InternalEventContext)
+ {
+ iec = ( InternalEventContext) context;
+ }
+ }
+
+ /* --- InternalHousekeepingContext interface methods --- */
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#pushCurrentTemplateName(java.lang.String)
+ */
+ @Override
+ public void pushCurrentTemplateName(String s )
+ {
+ icb.pushCurrentTemplateName( s );
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#popCurrentTemplateName()
+ */
+ @Override
+ public void popCurrentTemplateName()
+ {
+ icb.popCurrentTemplateName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentTemplateName()
+ */
+ @Override
+ public String getCurrentTemplateName()
+ {
+ return icb.getCurrentTemplateName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getTemplateNameStack()
+ */
+ @Override
+ public String[] getTemplateNameStack()
+ {
+ return icb.getTemplateNameStack();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#pushCurrentMacroName(java.lang.String)
+ * @since 1.6
+ */
+ @Override
+ public void pushCurrentMacroName(String s )
+ {
+ icb.pushCurrentMacroName( s );
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#popCurrentMacroName()
+ * @since 1.6
+ */
+ @Override
+ public void popCurrentMacroName()
+ {
+ icb.popCurrentMacroName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentMacroName()
+ * @since 1.6
+ */
+ @Override
+ public String getCurrentMacroName()
+ {
+ return icb.getCurrentMacroName();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentMacroCallDepth()
+ * @since 1.6
+ */
+ @Override
+ public int getCurrentMacroCallDepth()
+ {
+ return icb.getCurrentMacroCallDepth();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getMacroNameStack()
+ * @since 1.6
+ */
+ @Override
+ public String[] getMacroNameStack()
+ {
+ return icb.getMacroNameStack();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#icacheGet(java.lang.Object)
+ */
+ @Override
+ public IntrospectionCacheData icacheGet(Object key )
+ {
+ return icb.icacheGet( key );
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#icachePut(java.lang.Object, org.apache.velocity.util.introspection.IntrospectionCacheData)
+ */
+ @Override
+ public void icachePut(Object key, IntrospectionCacheData o )
+ {
+ icb.icachePut( key, o );
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#setCurrentResource(org.apache.velocity.runtime.resource.Resource)
+ */
+ @Override
+ public void setCurrentResource(Resource r )
+ {
+ icb.setCurrentResource(r);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentResource()
+ */
+ @Override
+ public Resource getCurrentResource()
+ {
+ return icb.getCurrentResource();
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#setMacroLibraries(List)
+ * @since 1.6
+ */
+ @Override
+ public void setMacroLibraries(List<Template> macroLibraries)
+ {
+ icb.setMacroLibraries(macroLibraries);
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getMacroLibraries()
+ * @since 1.6
+ */
+ @Override
+ public List<Template> getMacroLibraries()
+ {
+ return icb.getMacroLibraries();
+ }
+
+ /* --- Context interface methods --- */
+
+ /**
+ * @see org.apache.velocity.context.Context#put(java.lang.String, java.lang.Object)
+ */
+ @Override
+ public Object put(String key, Object value)
+ {
+ return context.put( key , value );
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#get(java.lang.String)
+ */
+ @Override
+ public Object get(String key)
+ {
+ return context.get( key );
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#containsKey(java.lang.String)
+ */
+ @Override
+ public boolean containsKey(String key)
+ {
+ return context.containsKey( key );
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#getKeys()
+ */
+ @Override
+ public String[] getKeys()
+ {
+ return context.getKeys();
+ }
+
+ /**
+ * @see org.apache.velocity.context.Context#remove(java.lang.String)
+ */
+ @Override
+ public Object remove(String key)
+ {
+ return context.remove( key );
+ }
+
+
+ /* ---- InternalWrapperContext --- */
+
+ /**
+ * returns the user data context that
+ * we are wrapping
+ * @return The internal user data context.
+ */
+ @Override
+ public Context getInternalUserContext()
+ {
+ return context;
+ }
+
+ /**
+ * Returns the base context that we are
+ * wrapping. Here, its this, but for other thing
+ * like VM related context contortions, it can
+ * be something else
+ * @return The base context.
+ */
+ @Override
+ public InternalContextAdapter getBaseContext()
+ {
+ return this;
+ }
+
+ /* ----- InternalEventContext ---- */
+
+ /**
+ * @see org.apache.velocity.context.InternalEventContext#attachEventCartridge(org.apache.velocity.app.event.EventCartridge)
+ */
+ @Override
+ public EventCartridge attachEventCartridge(EventCartridge ec )
+ {
+ if (iec != null)
+ {
+ return iec.attachEventCartridge( ec );
+ }
+
+ return null;
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalEventContext#getEventCartridge()
+ */
+ @Override
+ public EventCartridge getEventCartridge()
+ {
+ if ( iec != null)
+ {
+ return iec.getEventCartridge( );
+ }
+
+ return null;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextBase.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextBase.java
new file mode 100644
index 00000000..7d58e0e5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalContextBase.java
@@ -0,0 +1,275 @@
+package org.apache.velocity.context;
+
+/*
+ * 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.runtime.resource.Resource;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * class to encapsulate the 'stuff' for internal operation of velocity.
+ * We use the context as a thread-safe storage : we take advantage of the
+ * fact that it's a visitor of sorts to all nodes (that matter) of the
+ * AST during init() and render().
+ * Currently, it carries the template name for namespace
+ * support, as well as node-local context data introspection caching.
+ *
+ * Note that this is not a public class. It is for package access only to
+ * keep application code from accessing the internals, as AbstractContext
+ * is derived from this.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+class InternalContextBase implements InternalHousekeepingContext, InternalEventContext
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = -245905472770843470L;
+
+ /**
+ * cache for node/context specific introspection information
+ */
+ private Map<Object, IntrospectionCacheData> introspectionCache = new HashMap<>(33);
+
+ /**
+ * Template name stack. The stack top contains the current template name.
+ */
+ private Stack<String> templateNameStack = new Stack<>();
+
+ /**
+ * Velocimacro name stack. The stack top contains the current macro name.
+ */
+ private Stack<String> macroNameStack = new Stack<>();
+
+ /**
+ * EventCartridge we are to carry. Set by application
+ */
+ private EventCartridge eventCartridge = null;
+
+ /**
+ * Current resource - used for carrying encoding and other
+ * information down into the rendering process
+ */
+ private Resource currentResource = null;
+
+ /**
+ * List for holding the macro libraries. Contains the macro library
+ * template name as strings.
+ */
+ private List<Template> macroLibraries = null;
+
+ /**
+ * set the current template name on top of stack
+ *
+ * @param s current template name
+ */
+ @Override
+ public void pushCurrentTemplateName(String s )
+ {
+ templateNameStack.push(s);
+ }
+
+ /**
+ * remove the current template name from stack
+ */
+ @Override
+ public void popCurrentTemplateName()
+ {
+ templateNameStack.pop();
+ }
+
+ /**
+ * get the current template name
+ *
+ * @return String current template name
+ */
+ @Override
+ public String getCurrentTemplateName()
+ {
+ if ( templateNameStack.empty() )
+ return "<undef>";
+ else
+ return templateNameStack.peek();
+ }
+
+ /**
+ * get the current template name stack
+ *
+ * @return String[] with the template name stack contents.
+ */
+ @Override
+ public String[] getTemplateNameStack()
+ {
+ return templateNameStack.toArray(new String[templateNameStack.size()]);
+ }
+
+ /**
+ * set the current macro name on top of stack
+ *
+ * @param s current macro name
+ */
+ @Override
+ public void pushCurrentMacroName(String s )
+ {
+ macroNameStack.push(s);
+ }
+
+ /**
+ * remove the current macro name from stack
+ */
+ @Override
+ public void popCurrentMacroName()
+ {
+ macroNameStack.pop();
+ }
+
+ /**
+ * get the current macro name
+ *
+ * @return String current macro name
+ */
+ @Override
+ public String getCurrentMacroName()
+ {
+ if (macroNameStack.empty())
+ {
+ return "<undef>";
+ }
+ else
+ {
+ return macroNameStack.peek();
+ }
+ }
+
+ /**
+ * get the current macro call depth
+ *
+ * @return int current macro call depth
+ */
+ @Override
+ public int getCurrentMacroCallDepth()
+ {
+ return macroNameStack.size();
+ }
+
+ /**
+ * get the current macro name stack
+ *
+ * @return String[] with the macro name stack contents.
+ */
+ @Override
+ public String[] getMacroNameStack()
+ {
+ return macroNameStack.toArray(new String[macroNameStack.size()]);
+ }
+
+ /**
+ * returns an IntrospectionCache Data (@see IntrospectionCacheData)
+ * object if exists for the key
+ *
+ * @param key key to find in cache
+ * @return cache object
+ */
+ @Override
+ public IntrospectionCacheData icacheGet(Object key )
+ {
+ return ( IntrospectionCacheData ) introspectionCache.get( key );
+ }
+
+ /**
+ * places an IntrospectionCache Data (@see IntrospectionCacheData)
+ * element in the cache for specified key
+ *
+ * @param key key
+ * @param o IntrospectionCacheData object to place in cache
+ */
+ @Override
+ public void icachePut(Object key, IntrospectionCacheData o )
+ {
+ introspectionCache.put( key, o );
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#setCurrentResource(org.apache.velocity.runtime.resource.Resource)
+ */
+ @Override
+ public void setCurrentResource(Resource r )
+ {
+ currentResource = r;
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getCurrentResource()
+ */
+ @Override
+ public Resource getCurrentResource()
+ {
+ return currentResource;
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#setMacroLibraries(List)
+ */
+ @Override
+ public void setMacroLibraries(List<Template> macroLibraries)
+ {
+ this.macroLibraries = macroLibraries;
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalHousekeepingContext#getMacroLibraries()
+ */
+ @Override
+ public List<Template> getMacroLibraries()
+ {
+ return macroLibraries;
+ }
+
+
+ /**
+ * @see org.apache.velocity.context.InternalEventContext#attachEventCartridge(org.apache.velocity.app.event.EventCartridge)
+ */
+ @Override
+ public EventCartridge attachEventCartridge(EventCartridge ec )
+ {
+ EventCartridge temp = eventCartridge;
+
+ eventCartridge = ec;
+
+ return temp;
+ }
+
+ /**
+ * @see org.apache.velocity.context.InternalEventContext#getEventCartridge()
+ */
+ @Override
+ public EventCartridge getEventCartridge()
+ {
+ return eventCartridge;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalEventContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalEventContext.java
new file mode 100644
index 00000000..61602468
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalEventContext.java
@@ -0,0 +1,44 @@
+package org.apache.velocity.context;
+
+/*
+ * 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.EventCartridge;
+
+/**
+ * Interface for event support. Note that this is a public internal
+ * interface, as it is something that will be accessed from outside
+ * of the .context package.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface InternalEventContext
+{
+ /**
+ * @param ec
+ * @return The old EventCartridge.
+ */
+ EventCartridge attachEventCartridge(EventCartridge ec);
+
+ /**
+ * @return The current EventCartridge.
+ */
+ EventCartridge getEventCartridge();
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalHousekeepingContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalHousekeepingContext.java
new file mode 100644
index 00000000..11b03166
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalHousekeepingContext.java
@@ -0,0 +1,148 @@
+package org.apache.velocity.context;
+
+/*
+ * 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.resource.Resource;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+
+import java.util.List;
+
+/**
+ * interface to encapsulate the 'stuff' for internal operation of velocity.
+ * We use the context as a thread-safe storage : we take advantage of the
+ * fact that it's a visitor of sorts to all nodes (that matter) of the
+ * AST during init() and render().
+ *
+ * Currently, it carries the template name for namespace
+ * support, as well as node-local context data introspection caching.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @version $Id$
+ */
+interface InternalHousekeepingContext
+{
+ /**
+ * set the current template name on top of stack
+ *
+ * @param s current template name
+ */
+ void pushCurrentTemplateName( String s );
+
+ /**
+ * remove the current template name from stack
+ */
+ void popCurrentTemplateName();
+
+ /**
+ * get the current template name
+ *
+ * @return String current template name
+ */
+ String getCurrentTemplateName();
+
+ /**
+ * Returns the template name stack in form of an array.
+ *
+ * @return String[] with the template name stack contents.
+ */
+ String[] getTemplateNameStack();
+
+ /**
+ * set the current macro name on top of stack
+ *
+ * @param s current macro name
+ */
+ void pushCurrentMacroName( String s );
+
+ /**
+ * remove the current macro name from stack
+ */
+ void popCurrentMacroName();
+
+ /**
+ * get the current macro name
+ *
+ * @return String current macro name
+ */
+ String getCurrentMacroName();
+
+ /**
+ * get the current macro call depth
+ *
+ * @return int current macro call depth
+ */
+ int getCurrentMacroCallDepth();
+
+ /**
+ * Returns the macro name stack in form of an array.
+ *
+ * @return String[] with the macro name stack contents.
+ */
+ String[] getMacroNameStack();
+
+ /**
+ * returns an IntrospectionCache Data (@see IntrospectionCacheData)
+ * object if exists for the key
+ *
+ * @param key key to find in cache
+ * @return cache object
+ */
+ IntrospectionCacheData icacheGet( Object key );
+
+ /**
+ * places an IntrospectionCache Data (@see IntrospectionCacheData)
+ * element in the cache for specified key
+ *
+ * @param key key
+ * @param o IntrospectionCacheData object to place in cache
+ */
+ void icachePut( Object key, IntrospectionCacheData o );
+
+ /**
+ * temporary fix to enable #include() to figure out
+ * current encoding.
+ *
+ * @return The current resource.
+ */
+ Resource getCurrentResource();
+
+
+ /**
+ * @param r
+ */
+ void setCurrentResource( Resource r );
+
+ /**
+ * Set the macro library list for the current template.
+ *
+ * @param macroLibraries list of macro libraries to set
+ */
+ void setMacroLibraries(List<Template> macroLibraries);
+
+ /**
+ * Get the macro library list for the current template.
+ *
+ * @return List of macro library names
+ */
+ List<Template> getMacroLibraries();
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalWrapperContext.java b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalWrapperContext.java
new file mode 100644
index 00000000..0092f517
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/context/InternalWrapperContext.java
@@ -0,0 +1,66 @@
+package org.apache.velocity.context;
+
+/*
+ * 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 for internal context wrapping functionality
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface InternalWrapperContext
+{
+
+ /**
+ * Returns the wrapped user context.
+ * @return The wrapped user context.
+ */
+ Context getInternalUserContext();
+
+ /**
+ * Returns the base full context impl.
+ * @return The base full context impl.
+ *
+ */
+ InternalContextAdapter getBaseContext();
+
+ /**
+ * Place a key value pair into the context.
+ * @param key
+ * @param value
+ * @return previous value
+ */
+ Object put(String key, Object value);
+
+ /**
+ * Retrieve the specified valuefrom the given key.
+ * @param key
+ * @return found value
+ */
+ Object get(String key);
+
+ /**
+ * Tests if the key exists in the specified scope
+ * @param key
+ * @return true if key exists
+ */
+ boolean containsKey(String key);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/ExtendedParseException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/ExtendedParseException.java
new file mode 100644
index 00000000..17f46416
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/ExtendedParseException.java
@@ -0,0 +1,51 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.
+ */
+
+/**
+ * All Exceptions that can provide additional information about the place
+ * where the error happened (template name, column and line number) can
+ * implement this interface and the ParseErrorException will then be able
+ * to deal with this information.
+ *
+ * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public interface ExtendedParseException
+{
+ /**
+ * returns the Template name where this exception occurred.
+ * @return The Template name where this exception occurred.
+ */
+ String getTemplateName();
+
+ /**
+ * returns the line number where this exception occurred.
+ * @return The line number where this exception occurred.
+ */
+ int getLineNumber();
+
+ /**
+ * returns the column number where this exception occurred.
+ * @return The column number where this exception occurred.
+ */
+ int getColumnNumber();
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/MacroOverflowException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/MacroOverflowException.java
new file mode 100644
index 00000000..77cde148
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/MacroOverflowException.java
@@ -0,0 +1,81 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.
+ */
+
+/**
+ * Application-level exception thrown when macro calls within macro calls
+ * exceeds the maximum allowed depth. The maximum allowable depth is given
+ * in the configuration as velocimacro.max.depth.
+ * @since 1.6
+ */
+public class MacroOverflowException extends VelocityException
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = 7305635093478106342L;
+
+ /**
+ * @param exceptionMessage The message to register.
+ */
+ public MacroOverflowException(final String exceptionMessage)
+ {
+ super(exceptionMessage);
+ }
+
+ /**
+ * @param exceptionMessage The message to register.
+ * @param wrapped A throwable object that caused the Exception.
+ */
+ public MacroOverflowException(final String exceptionMessage, final Throwable wrapped)
+ {
+ super(exceptionMessage, wrapped);
+ }
+
+ /**
+ * @param exceptionMessage The message to register.
+ * @param wrapped A throwable object that caused the Exception.
+ * @param stacktrace VTL stacktrace
+ * @since 2.2
+ */
+ public MacroOverflowException(final String exceptionMessage, final Throwable wrapped, final String[] stacktrace)
+ {
+ super(exceptionMessage, wrapped, stacktrace);
+ }
+
+ /**
+ * @param wrapped A throwable object that caused the Exception.
+ */
+ public MacroOverflowException(final Throwable wrapped)
+ {
+ super(wrapped);
+ }
+
+ /**
+ * @param wrapped A throwable object that caused the Exception.
+ * @param stacktrace VTL stacktrace
+ * @since 2.2
+ */
+ public MacroOverflowException(final Throwable wrapped, final String[] stacktrace)
+ {
+ super(wrapped, stacktrace);
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/MathException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/MathException.java
new file mode 100755
index 00000000..14b4f3e4
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/MathException.java
@@ -0,0 +1,50 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.
+ */
+
+/**
+ * Separate exception class to distinguish math problems.
+ *
+ * @author Nathan Bubna
+ * @since 1.6
+ * @version $Id: MathException.java 685685 2008-08-13 21:43:27Z nbubna $
+ */
+public class MathException extends VelocityException
+{
+ private static final long serialVersionUID = -7966507088645215583L;
+
+ /**
+ * @param exceptionMessage The message to register.
+ */
+ public MathException(final String exceptionMessage)
+ {
+ super(exceptionMessage);
+ }
+
+ /**
+ * @param exceptionMessage The message to register.
+ * @param stacktrace VTL stacktrace
+ * @since 2.2
+ */
+ public MathException(final String exceptionMessage, final String[] stacktrace)
+ {
+ super(exceptionMessage, null, stacktrace);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/MethodInvocationException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/MethodInvocationException.java
new file mode 100644
index 00000000..a47f6e92
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/MethodInvocationException.java
@@ -0,0 +1,168 @@
+package org.apache.velocity.exception;
+
+import org.apache.velocity.util.StringUtils;
+
+/*
+ * 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.
+ */
+
+/**
+ * Application-level exception thrown when a reference method is
+ * invoked and an exception is thrown.
+ * <br>
+ * When this exception is thrown, a best effort will be made to have
+ * useful information in the exception's message. For complete
+ * information, consult the runtime log.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class MethodInvocationException extends VelocityException implements ExtendedParseException
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = 7305685093478106342L;
+
+ private String referenceName = "";
+
+ private final String methodName;
+
+ private final int lineNumber;
+ private final int columnNumber;
+ private final String templateName;
+
+ /**
+ * CTOR - wraps the passed in exception for
+ * examination later
+ *
+ * @param message
+ * @param e Throwable that we are wrapping
+ * @param methodName name of method that threw the exception
+ * @param templateName The name of the template where the exception occurred
+ * @param lineNumber line number
+ * @param columnNumber column number
+ */
+ public MethodInvocationException(final String message, final Throwable e, final String methodName, final String templateName, final int lineNumber, final int columnNumber)
+ {
+ super(message, e);
+
+ this.methodName = methodName;
+ this.templateName = templateName;
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ }
+
+ /**
+ * CTOR - wraps the passed in exception for
+ * examination later
+ *
+ * @param message
+ * @param e Throwable that we are wrapping
+ * @param stacktrace VTL stacktrace
+ * @param methodName name of method that threw the exception
+ * @param templateName The name of the template where the exception occurred
+ * @param lineNumber line number
+ * @param columnNumber column number
+ */
+ public MethodInvocationException(final String message, final Throwable e, final String[] stacktrace, final String methodName, final String templateName, final int lineNumber, final int columnNumber)
+ {
+ super(message, e, stacktrace);
+
+ this.methodName = methodName;
+ this.templateName = templateName;
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ }
+
+ /**
+ * Returns the name of the method that threw the
+ * exception.
+ *
+ * @return String name of method
+ */
+ public String getMethodName()
+ {
+ return methodName;
+ }
+
+ /**
+ * Sets the reference name that threw this exception.
+ *
+ * @param ref name of reference
+ */
+ public void setReferenceName(String ref)
+ {
+ referenceName = ref;
+ }
+
+ /**
+ * Retrieves the name of the reference that caused the
+ * exception.
+ *
+ * @return name of reference.
+ */
+ public String getReferenceName()
+ {
+ return referenceName;
+ }
+
+ /**
+ * @see ExtendedParseException#getColumnNumber()
+ * @since 1.5
+ */
+ @Override
+ public int getColumnNumber()
+ {
+ return columnNumber;
+ }
+
+ /**
+ * @see ExtendedParseException#getLineNumber()
+ * @since 1.5
+ */
+ @Override
+ public int getLineNumber()
+ {
+ return lineNumber;
+ }
+
+ /**
+ * @see ExtendedParseException#getTemplateName()
+ * @since 1.5
+ */
+ @Override
+ public String getTemplateName()
+ {
+ return templateName;
+ }
+
+ /**
+ * @see Exception#getMessage()
+ * @since 1.5
+ */
+ @Override
+ public String getMessage()
+ {
+ StringBuilder message = new StringBuilder();
+ message.append(super.getMessage());
+ message.append(" at ");
+ message.append(StringUtils.formatFileString(templateName, lineNumber, columnNumber));
+ return message.toString();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/ParseErrorException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/ParseErrorException.java
new file mode 100644
index 00000000..e86d11fd
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/ParseErrorException.java
@@ -0,0 +1,274 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.ParseException;
+import org.apache.velocity.util.StringUtils;
+import org.apache.velocity.util.introspection.Info;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Application-level exception thrown when a resource of any type
+ * has a syntax or other error which prevents it from being parsed.
+ * <br>
+ * When this resource is thrown, a best effort will be made to have
+ * useful information in the exception's message. For complete
+ * information, consult the runtime log.
+ *
+ * @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 ParseErrorException extends VelocityException
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = -6665197935086306472L;
+
+ /**
+ * The column number of the parsing error, or -1 if not defined.
+ */
+ private int columnNumber = -1;
+
+ /**
+ * The line number of the parsing error, or -1 if not defined.
+ */
+ private int lineNumber = -1;
+
+ /**
+ * The name of the template containing the error, or null if not defined.
+ */
+ private String templateName = "*unset*";
+
+ /**
+ * If applicable, contains the invalid syntax or reference that triggered this exception
+ */
+ private String invalidSyntax;
+
+ /**
+ * If we modify the message, then we set this
+ */
+ private String msg = null;
+
+ /**
+ * Create a ParseErrorException with the given message.
+ *
+ * @param exceptionMessage the error exception message
+ */
+ public ParseErrorException(String exceptionMessage)
+ {
+ super(exceptionMessage);
+ }
+
+ private static final Pattern lexError = Pattern.compile("Lexical error.*TokenMgrError.*line (\\d+),.*column (\\d+)\\.(.*)");
+
+ /**
+ * Create a ParseErrorException with the given ParseException.
+ *
+ * @param pex the parsing exception
+ * @param templName
+ * @since 1.5
+ */
+ public ParseErrorException(ParseException pex, String templName)
+ {
+ super(pex.getMessage());
+
+ if (templName != null) templateName = templName;
+
+ // Don't use a second C'tor, TemplateParseException is a subclass of
+ // ParseException...
+ if (pex instanceof ExtendedParseException)
+ {
+ ExtendedParseException xpex = (ExtendedParseException) pex;
+
+ columnNumber = xpex.getColumnNumber();
+ lineNumber = xpex.getLineNumber();
+ templateName = xpex.getTemplateName();
+ }
+ else
+ {
+ // We get here if the the Parser has thrown an exception. Unfortunately,
+ // the error message created is hard coded by javacc, so here we alter
+ // the error message, so that it is in our standard format.
+ Matcher match = lexError.matcher(pex.getMessage());
+ if (match.matches())
+ {
+ lineNumber = Integer.parseInt(match.group(1));
+ columnNumber = Integer.parseInt(match.group(2));
+ String restOfMsg = match.group(3);
+ msg = "Lexical error, " + restOfMsg + " at "
+ + StringUtils.formatFileString(templateName, lineNumber, columnNumber);
+ }
+
+ // ugly, ugly, ugly...
+
+ if (pex.currentToken != null && pex.currentToken.next != null)
+ {
+ columnNumber = pex.currentToken.next.beginColumn;
+ lineNumber = pex.currentToken.next.beginLine;
+ }
+ }
+ }
+
+ /**
+ * Create a ParseErrorException with the given ParseException.
+ *
+ * @param pex the parsing exception
+ * @param templName
+ * @since 1.5
+ */
+ public ParseErrorException(VelocityException pex, String templName)
+ {
+ super(pex.getMessage());
+
+ if (templName != null) templateName = templName;
+
+ // Don't use a second C'tor, TemplateParseException is a subclass of
+ // ParseException...
+ if (pex instanceof ExtendedParseException)
+ {
+ ExtendedParseException xpex = (ExtendedParseException) pex;
+
+ columnNumber = xpex.getColumnNumber();
+ lineNumber = xpex.getLineNumber();
+ templateName = xpex.getTemplateName();
+ }
+ else if (pex.getCause() instanceof ParseException)
+ {
+ ParseException pex2 = (ParseException) pex.getCause();
+
+ if (pex2.currentToken != null && pex2.currentToken.next != null)
+ {
+ columnNumber = pex2.currentToken.next.beginColumn;
+ lineNumber = pex2.currentToken.next.beginLine;
+ }
+ }
+ }
+
+
+ /**
+ * Create a ParseErrorRuntimeException with the given message and info
+ *
+ * @param exceptionMessage the error exception message
+ * @param info an Info object with the current template info
+ * @since 1.5
+ */
+ public ParseErrorException(String exceptionMessage, Info info)
+ {
+ super(exceptionMessage);
+ columnNumber = info.getColumn();
+ lineNumber = info.getLine();
+ templateName = info.getTemplateName();
+ }
+
+ /**
+ * Create a ParseErrorRuntimeException with the given message and info
+ *
+ * @param exceptionMessage the error exception message
+ * @param info an Info object with the current template info
+ * @since 2.2
+ */
+ public ParseErrorException(String exceptionMessage, Info info, String[] stacktrace)
+ {
+ super(exceptionMessage, null, stacktrace);
+ columnNumber = info.getColumn();
+ lineNumber = info.getLine();
+ templateName = info.getTemplateName();
+ }
+
+ /**
+ * Create a ParseErrorRuntimeException with the given message and info
+ *
+ * @param exceptionMessage the error exception message
+ * @param info an Info object with the current template info
+ * @param invalidSyntax the invalid syntax or reference triggering this exception
+ * @since 1.5
+ */
+ public ParseErrorException(String exceptionMessage, Info info, String invalidSyntax)
+ {
+ super(exceptionMessage);
+ columnNumber = info.getColumn();
+ lineNumber = info.getLine();
+ templateName = info.getTemplateName();
+ this.invalidSyntax = invalidSyntax;
+ }
+
+
+ /**
+ * Return the column number of the parsing error, or -1 if not defined.
+ *
+ * @return column number of the parsing error, or -1 if not defined
+ * @since 1.5
+ */
+ public int getColumnNumber()
+ {
+ return columnNumber;
+ }
+
+ /**
+ * Return the line number of the parsing error, or -1 if not defined.
+ *
+ * @return line number of the parsing error, or -1 if not defined
+ * @since 1.5
+ */
+ public int getLineNumber()
+ {
+ return lineNumber;
+ }
+
+ /**
+ * Return the name of the template containing the error, or null if not
+ * defined.
+ *
+ * @return the name of the template containing the parsing error, or null
+ * if not defined
+ * @since 1.5
+ */
+ public String getTemplateName()
+ {
+ return templateName;
+ }
+
+ /**
+ * Return the invalid syntax or reference that triggered this error, or null
+ * if not defined.
+ *
+ * @return Return the invalid syntax or reference that triggered this error, or null
+ * if not defined
+ * @since 1.5
+ */
+ public String getInvalidSyntax()
+ {
+ return invalidSyntax;
+ }
+
+ /**
+ * Return our custom message if we have one, else return the default message
+ */
+ @Override
+ public String getMessage()
+ {
+ if (msg != null) return msg;
+ return super.getMessage();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/ResourceNotFoundException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/ResourceNotFoundException.java
new file mode 100644
index 00000000..7bc2b4cf
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/ResourceNotFoundException.java
@@ -0,0 +1,93 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.
+ */
+
+/**
+ * Application-level exception thrown when a resource of any type
+ * isn't found by the Velocity engine.
+ * <br>
+ * When this exception is thrown, a best effort will be made to have
+ * useful information in the exception's message. For complete
+ * information, consult the runtime log.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @version $Id$
+ */
+public class ResourceNotFoundException extends VelocityException
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = -4287732191458420347L;
+
+ /**
+ * @param exceptionMessage
+ * @see VelocityException#VelocityException(String)
+ */
+ public ResourceNotFoundException(final String exceptionMessage)
+ {
+ super(exceptionMessage);
+ }
+
+ /**
+ * @param exceptionMessage
+ * @param t
+ * @see VelocityException#VelocityException(String, Throwable)
+ * @since 1.5
+ */
+ public ResourceNotFoundException(final String exceptionMessage, final Throwable t)
+ {
+ super(exceptionMessage, t);
+ }
+
+ /**
+ * @param exceptionMessage
+ * @param t
+ * @param stacktrace VTL stacktrace
+ * @see VelocityException#VelocityException(String, Throwable)
+ * @since 2.2
+ */
+ public ResourceNotFoundException(final String exceptionMessage, final Throwable t, final String[] stacktrace)
+ {
+ super(exceptionMessage, t, stacktrace);
+ }
+
+ /**
+ * @param t
+ * @see VelocityException#VelocityException(Throwable)
+ * @since 1.5
+ */
+ public ResourceNotFoundException(final Throwable t)
+ {
+ super(t);
+ }
+
+ /**
+ * @param t
+ * @param stacktrace VTL stacktrace
+ * @see VelocityException#VelocityException(Throwable)
+ * @since 2.2
+ */
+ public ResourceNotFoundException(final Throwable t, String[] stacktrace)
+ {
+ super(t, stacktrace);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/TemplateInitException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/TemplateInitException.java
new file mode 100644
index 00000000..20a60ec8
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/TemplateInitException.java
@@ -0,0 +1,128 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.ParseException;
+
+/**
+ * Exception generated to indicate parse errors caught during
+ * directive initialization (e.g. wrong number of arguments)
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class TemplateInitException extends VelocityException
+ implements ExtendedParseException
+{
+ private final String templateName;
+ private final int col;
+ private final int line;
+
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = -4985224672336070621L;
+
+ /**
+ * @param msg
+ * @param templateName
+ * @param col
+ * @param line
+ */
+ public TemplateInitException(final String msg,
+ final String templateName, final int col, final int line)
+ {
+ super(msg);
+ this.templateName = templateName;
+ this.col = col;
+ this.line = line;
+ }
+
+ /**
+ *
+ * @param msg
+ * @param parseException
+ * @param templateName
+ * @param col
+ * @param line
+ */
+ public TemplateInitException(final String msg, ParseException parseException,
+ final String templateName, final int col, final int line)
+ {
+ super(msg,parseException);
+ this.templateName = templateName;
+ this.col = col;
+ this.line = line;
+ }
+
+ /**
+ *
+ * @param msg
+ * @param parseException
+ * @param stacktrace
+ * @param templateName
+ * @param col
+ * @param line
+ * @since 2.2
+ */
+ public TemplateInitException(final String msg, ParseException parseException, String[] stacktrace,
+ final String templateName, final int col, final int line)
+ {
+ super(msg,parseException, stacktrace);
+ this.templateName = templateName;
+ this.col = col;
+ this.line = line;
+ }
+
+ /**
+ * Returns the Template name where this exception occurred.
+ * @return the template name
+ */
+ @Override
+ public String getTemplateName()
+ {
+ return templateName;
+ }
+
+ /**
+ * Returns the line number where this exception occurred.
+ * @return the line number
+ */
+ @Override
+ public int getLineNumber()
+ {
+ return line;
+ }
+
+ /**
+ * Returns the column number where this exception occurred.
+ * @return the line number
+ */
+ @Override
+ public int getColumnNumber()
+ {
+ return col;
+ }
+
+
+
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/exception/VelocityException.java b/velocity-engine-core/src/main/java/org/apache/velocity/exception/VelocityException.java
new file mode 100644
index 00000000..dc6de702
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/exception/VelocityException.java
@@ -0,0 +1,115 @@
+package org.apache.velocity.exception;
+
+/*
+ * 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.LogContext;
+
+/**
+* Base class for Velocity runtime exceptions thrown to the
+ * application layer.
+ *
+ * @author <a href="mailto:kdowney@amberarcher.com">Kyle F. Downey</a>
+ * @version $Id$
+ */
+public class VelocityException extends RuntimeException
+{
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = 1251243065134956045L;
+
+ /**
+ * LogContext VTL location tracking context
+ */
+ private LogContext logContext = null;
+
+ /**
+ * VTL vtlStackTrace, populated at construction when runtime.log.track_location is true
+ */
+ private String vtlStackTrace[] = null;
+
+ /**
+ * @param exceptionMessage The message to register.
+ */
+ public VelocityException(final String exceptionMessage)
+ {
+ super(exceptionMessage);
+ }
+
+ /**
+ * @param exceptionMessage The message to register.
+ * @param wrapped A throwable object that caused the Exception.
+ * @since 1.5
+ */
+ public VelocityException(final String exceptionMessage, final Throwable wrapped)
+ {
+ super(exceptionMessage, wrapped);
+ }
+
+ /**
+ * @param exceptionMessage The message to register.
+ * @param wrapped A throwable object that caused the Exception.
+ * @param vtlStackTrace VTL stacktrace
+ * @since 2.2
+ */
+ public VelocityException(final String exceptionMessage, final Throwable wrapped, final String[] vtlStackTrace)
+ {
+ super(exceptionMessage, wrapped);
+ this.vtlStackTrace = vtlStackTrace;
+ }
+
+ /**
+ * @param wrapped A throwable object that caused the Exception.
+ * @since 1.5
+ */
+ public VelocityException(final Throwable wrapped)
+ {
+ super(wrapped);
+ }
+
+ /**
+ * @param wrapped A throwable object that caused the Exception.
+ * @param vtlStackTrace VTL stacktrace
+ * @since 2.2
+ */
+ public VelocityException(final Throwable wrapped, final String[] vtlStackTrace)
+ {
+ super(wrapped);
+ this.vtlStackTrace = vtlStackTrace;
+ }
+
+ /**
+ * returns the wrapped Throwable that caused this
+ * MethodInvocationException to be thrown
+ *
+ * @return Throwable thrown by method invocation
+ * @since 1.5
+ * @deprecated Use {@link java.lang.RuntimeException#getCause()}
+ */
+ public Throwable getWrappedThrowable()
+ {
+ return getCause();
+ }
+
+ public String[] getVtlStackTrace()
+ {
+ return vtlStackTrace;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/io/Filter.java b/velocity-engine-core/src/main/java/org/apache/velocity/io/Filter.java
new file mode 100644
index 00000000..021e43e7
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/io/Filter.java
@@ -0,0 +1,32 @@
+package org.apache.velocity.io;
+
+/*
+ * 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;
+
+/**
+ * Velocity will call the writeReference method on any Writer that
+ * implements this interface when rendering references within a template.
+ */
+
+public interface Filter
+{
+ void writeReference(String ref) throws IOException;
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/io/UnicodeInputStream.java b/velocity-engine-core/src/main/java/org/apache/velocity/io/UnicodeInputStream.java
new file mode 100644
index 00000000..43abfef5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/io/UnicodeInputStream.java
@@ -0,0 +1,411 @@
+package org.apache.velocity.io;
+
+/*
+ * 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.InputStream;
+import java.io.PushbackInputStream;
+
+import java.util.Locale;
+
+/**
+ * This is an input stream that is unicode BOM aware. This allows you to e.g. read
+ * Windows Notepad Unicode files as Velocity templates.
+ *
+ * It allows you to check the actual encoding of a file by calling {@link #getEncodingFromStream()} on
+ * the input stream reader.
+ *
+ * This class is not thread safe! When more than one thread wants to use an instance of UnicodeInputStream,
+ * the caller must provide synchronization.
+ *
+ * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class UnicodeInputStream
+ extends InputStream
+{
+
+ /** BOM Marker for UTF 8. See http://www.unicode.org/unicode/faq/utf_bom.html */
+ public static final UnicodeBOM UTF8_BOM = new UnicodeBOM("UTF-8", new byte [] { (byte)0xef, (byte)0xbb, (byte)0xbf });
+
+ /** BOM Marker for UTF 16, little endian. See http://www.unicode.org/unicode/faq/utf_bom.html */
+ public static final UnicodeBOM UTF16LE_BOM = new UnicodeBOM("UTF-16LE", new byte [] { (byte)0xff, (byte)0xfe });
+
+ /** BOM Marker for UTF 16, big endian. See http://www.unicode.org/unicode/faq/utf_bom.html */
+ public static final UnicodeBOM UTF16BE_BOM = new UnicodeBOM("UTF-16BE", new byte [] { (byte)0xfe, (byte)0xff });
+
+ /**
+ * BOM Marker for UTF 32, little endian. See http://www.unicode.org/unicode/faq/utf_bom.html
+ *
+ */
+ public static final UnicodeBOM UTF32LE_BOM = new UnicodeBOM("UTF-32LE", new byte [] { (byte)0xff, (byte)0xfe, (byte)0x00, (byte)0x00 });
+
+ /**
+ * BOM Marker for UTF 32, big endian. See http://www.unicode.org/unicode/faq/utf_bom.html
+ *
+ */
+ public static final UnicodeBOM UTF32BE_BOM = new UnicodeBOM("UTF-32BE", new byte [] { (byte)0x00, (byte)0x00, (byte)0xfe, (byte)0xff });
+
+ /** The maximum amount of bytes to read for a BOM */
+ private static final int MAX_BOM_SIZE = 4;
+
+ /** Buffer for BOM reading */
+ private byte [] buf = new byte[MAX_BOM_SIZE];
+
+ /** Buffer pointer. */
+ private int pos = 0;
+
+ /** The stream encoding as read from the BOM or null. */
+ private final String encoding;
+
+ /** True if the BOM itself should be skipped and not read. */
+ private final boolean skipBOM;
+
+ private final PushbackInputStream inputStream;
+
+ /**
+ * Creates a new UnicodeInputStream object. Skips a BOM which defines the file encoding.
+ *
+ * @param inputStream The input stream to use for reading.
+ * @throws IllegalStateException
+ * @throws IOException
+ */
+ public UnicodeInputStream(final InputStream inputStream)
+ throws IllegalStateException, IOException
+ {
+ this(inputStream, true);
+ }
+
+ /**
+ * Creates a new UnicodeInputStream object.
+ *
+ * @param inputStream The input stream to use for reading.
+ * @param skipBOM If this is set to true, a BOM read from the stream is discarded. This parameter should normally be true.
+ * @throws IllegalStateException
+ * @throws IOException
+ */
+ public UnicodeInputStream(final InputStream inputStream, boolean skipBOM)
+ throws IllegalStateException, IOException
+ {
+ super();
+
+ this.skipBOM = skipBOM;
+ this.inputStream = new PushbackInputStream(inputStream, MAX_BOM_SIZE);
+
+ try
+ {
+ this.encoding = readEncoding();
+ }
+ catch (IOException ioe)
+ {
+ throw new IllegalStateException("Could not read BOM from Stream", ioe);
+ }
+ }
+
+ /**
+ * Returns true if the input stream discards the BOM.
+ *
+ * @return True if the input stream discards the BOM.
+ */
+ public boolean isSkipBOM()
+ {
+ return skipBOM;
+ }
+
+ /**
+ * Read encoding based on BOM.
+ *
+ * @return The encoding based on the BOM.
+ *
+ * @throws IllegalStateException When a problem reading the BOM occured.
+ */
+ public String getEncodingFromStream()
+ {
+ return encoding;
+ }
+
+ /**
+ * This method gets the encoding from the stream contents if a BOM exists. If no BOM exists, the encoding
+ * is undefined.
+ *
+ * @return The encoding of this streams contents as decided by the BOM or null if no BOM was found.
+ * @throws IOException
+ */
+ protected String readEncoding()
+ throws IOException
+ {
+ pos = 0;
+
+ UnicodeBOM encoding = null;
+
+ // read first byte.
+ if (readByte())
+ {
+ // Build a list of matches
+ //
+ // 00 00 FE FF --> UTF 32 BE
+ // EF BB BF --> UTF 8
+ // FE FF --> UTF 16 BE
+ // FF FE --> UTF 16 LE
+ // FF FE 00 00 --> UTF 32 LE
+
+ switch (buf[0])
+ {
+ case (byte)0x00: // UTF32 BE
+ encoding = match(UTF32BE_BOM, null);
+ break;
+ case (byte)0xef: // UTF8
+ encoding = match(UTF8_BOM, null);
+ break;
+ case (byte)0xfe: // UTF16 BE
+ encoding = match(UTF16BE_BOM, null);
+ break;
+ case (byte)0xff: // UTF16/32 LE
+ encoding = match(UTF16LE_BOM, null);
+
+ if (encoding != null)
+ {
+ encoding = match(UTF32LE_BOM, encoding);
+ }
+ break;
+
+ default:
+ encoding = null;
+ break;
+ }
+ }
+
+ pushback(encoding);
+
+ return (encoding != null) ? encoding.getEncoding() : null;
+ }
+
+ private UnicodeBOM match(final UnicodeBOM matchEncoding, final UnicodeBOM noMatchEncoding)
+ throws IOException
+ {
+ byte [] bom = matchEncoding.getBytes();
+
+ for (int i = 0; i < bom.length; i++)
+ {
+ if (pos <= i) // Byte has not yet been read
+ {
+ if (!readByte())
+ {
+ return noMatchEncoding;
+ }
+ }
+
+ if (bom[i] != buf[i])
+ {
+ return noMatchEncoding;
+ }
+ }
+
+ return matchEncoding;
+ }
+
+ private boolean readByte()
+ throws IOException
+ {
+ int res = inputStream.read();
+ if (res == -1)
+ {
+ return false;
+ }
+
+ if (pos >= buf.length)
+ {
+ throw new IOException("BOM read error");
+ }
+
+ buf[pos++] = (byte) res;
+ return true;
+ }
+
+ private void pushback(final UnicodeBOM matchBOM)
+ throws IOException
+ {
+ int count = pos; // By default, all bytes are pushed back.
+ int start = 0;
+
+ if (matchBOM != null && skipBOM)
+ {
+ // We have a match (some bytes are part of the BOM)
+ // and we want to skip the BOM. Push back only the bytes
+ // after the BOM.
+ start = matchBOM.getBytes().length;
+ count = (pos - start);
+
+ if (count < 0)
+ {
+ throw new IllegalStateException("Match has more bytes than available!");
+ }
+ }
+
+ inputStream.unread(buf, start, count);
+ }
+
+ /**
+ * @throws IOException
+ * @see java.io.InputStream#close()
+ */
+ @Override
+ public void close()
+ throws IOException
+ {
+ inputStream.close();
+ }
+
+ /**
+ * @throws IOException
+ * @see java.io.InputStream#available()
+ */
+ @Override
+ public int available()
+ throws IOException
+ {
+ return inputStream.available();
+ }
+
+ /**
+ * @param readlimit
+ * @see java.io.InputStream#mark(int)
+ */
+ @Override
+ public void mark(final int readlimit)
+ {
+ inputStream.mark(readlimit);
+ }
+
+ /**
+ * @return mark supported
+ * @see java.io.InputStream#markSupported()
+ */
+ @Override
+ public boolean markSupported()
+ {
+ return inputStream.markSupported();
+ }
+
+ /**
+ * @return read char
+ * @see java.io.InputStream#read()
+ */
+ @Override
+ public int read()
+ throws IOException
+ {
+ return inputStream.read();
+ }
+
+ /**
+ * @param b buffer
+ * @return read chars count
+ * @see java.io.InputStream#read(byte[])
+ */
+ @Override
+ public int read(final byte [] b)
+ throws IOException
+ {
+ return inputStream.read(b);
+ }
+
+ /**
+ * @param b buffer
+ * @param off offset
+ * @param len length
+ * @return reac char
+ * @see java.io.InputStream#read(byte[], int, int)
+ */
+ @Override
+ public int read(final byte [] b, final int off, final int len)
+ throws IOException
+ {
+ return inputStream.read(b, off, len);
+ }
+
+ /**
+ * @see java.io.InputStream#reset()
+ */
+ @Override
+ public void reset()
+ throws IOException
+ {
+ inputStream.reset();
+ }
+
+ /**
+ * @param n
+ * @return skipped count
+ * @see java.io.InputStream#skip(long)
+ */
+ @Override
+ public long skip(final long n)
+ throws IOException
+ {
+ return inputStream.skip(n);
+ }
+
+
+ /**
+ * Helper function to compare encodings
+ * @param left
+ * @param right
+ * @return true for same encoding
+ */
+ public static boolean sameEncoding(String left, String right)
+ {
+ left = left.toUpperCase(Locale.ROOT).replace("-", "").replace("_","");
+ right = right.toUpperCase(Locale.ROOT).replace("-", "").replace("_","");
+ return left.equals(right);
+ }
+
+ /**
+ * Helper class to bundle encoding and BOM marker.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+ static final class UnicodeBOM
+ {
+ private final String encoding;
+
+ private final byte [] bytes;
+
+ private UnicodeBOM(final String encoding, final byte [] bytes)
+ {
+ this.encoding = encoding;
+ this.bytes = bytes;
+ }
+
+ String getEncoding()
+ {
+ return encoding;
+ }
+
+ byte [] getBytes()
+ {
+ return bytes;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/io/VelocityWriter.java b/velocity-engine-core/src/main/java/org/apache/velocity/io/VelocityWriter.java
new file mode 100644
index 00000000..b9419689
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/io/VelocityWriter.java
@@ -0,0 +1,355 @@
+package org.apache.velocity.io;
+
+/*
+ * 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.Writer;
+
+/**
+ * Implementation of a fast Writer. It was originally taken from JspWriter
+ * and modified to have less synchronization going on.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ * @author Anil K. Vijendran
+ * @version $Id$
+ */
+public final class VelocityWriter extends Writer implements Filter
+{
+ /**
+ * constant indicating that the Writer is not buffering output
+ */
+ public static final int NO_BUFFER = 0;
+
+ /**
+ * constant indicating that the Writer is buffered and is using the
+ * implementation default buffer size
+ */
+ public static final int DEFAULT_BUFFER = -1;
+
+ /**
+ * constant indicating that the Writer is buffered and is unbounded;
+ * this is used in BodyContent
+ */
+ public static final int UNBOUNDED_BUFFER = -2;
+
+ private Writer writer = null;
+
+ private int bufferSize;
+ private boolean autoFlush;
+
+ private char cb[];
+ private int nextChar;
+
+ private static int defaultCharBufferSize = 8 * 1024;
+
+ /**
+ * Create a buffered character-output stream that uses a default-sized
+ * output buffer.
+ *
+ * @param writer Writer to wrap around
+ */
+ public VelocityWriter(Writer writer)
+ {
+ this(writer, defaultCharBufferSize, true);
+ }
+
+ /**
+ * private constructor.
+ */
+ private VelocityWriter(int bufferSize, boolean autoFlush)
+ {
+ this.bufferSize = bufferSize;
+ this.autoFlush = autoFlush;
+ }
+
+ /**
+ * This method returns the size of the buffer used by the JspWriter.
+ *
+ * @return the size of the buffer in bytes, or 0 is unbuffered.
+ */
+ public int getBufferSize() { return bufferSize; }
+
+ /**
+ * This method indicates whether the JspWriter is autoFlushing.
+ *
+ * @return if this JspWriter is auto flushing or throwing IOExceptions on
+ * buffer overflow conditions
+ */
+ public boolean isAutoFlush() { return autoFlush; }
+
+ /**
+ * Create a new buffered character-output stream that uses an output
+ * buffer of the given size.
+ *
+ * @param writer Writer to wrap around
+ * @param sz Output-buffer size, a positive integer
+ * @param autoFlush
+ *
+ * @exception IllegalArgumentException If sz is &lt;= 0
+ */
+ public VelocityWriter(Writer writer, int sz, boolean autoFlush)
+ {
+ this(sz, autoFlush);
+ if (sz < 0)
+ throw new IllegalArgumentException("Buffer size <= 0");
+ this.writer = writer;
+ cb = sz == 0 ? null : new char[sz];
+ nextChar = 0;
+ }
+
+ /**
+ * Flush the output buffer to the underlying character stream, without
+ * flushing the stream itself. This method is non-private only so that it
+ * may be invoked by PrintStream.
+ */
+ private void flushBuffer() throws IOException
+ {
+ if (bufferSize == 0)
+ return;
+ if (nextChar == 0)
+ return;
+ writer.write(cb, 0, nextChar);
+ nextChar = 0;
+ }
+
+ /**
+ * Discard the output buffer.
+ */
+ public final void clear()
+ {
+ nextChar = 0;
+ }
+
+ private void bufferOverflow() throws IOException
+ {
+ throw new IOException("overflow");
+ }
+
+ /**
+ * Flush the stream.
+ * @throws IOException
+ *
+ */
+ @Override
+ public final void flush() throws IOException
+ {
+ flushBuffer();
+ if (writer != null)
+ {
+ writer.flush();
+ }
+ }
+
+ /**
+ * Close the stream.
+ * @throws IOException
+ *
+ */
+ @Override
+ public final void close() throws IOException {
+ if (writer == null)
+ return;
+ flush();
+ }
+
+ /**
+ * @return the number of bytes unused in the buffer
+ */
+ public final int getRemaining()
+ {
+ return bufferSize - nextChar;
+ }
+
+ /**
+ * Write a single character.
+ * @param c
+ * @throws IOException
+ *
+ */
+ @Override
+ public final void write(int c) throws IOException
+ {
+ if (bufferSize == 0)
+ {
+ writer.write(c);
+ }
+ else
+ {
+ if (nextChar >= bufferSize)
+ if (autoFlush)
+ flushBuffer();
+ else
+ bufferOverflow();
+ cb[nextChar++] = (char) c;
+ }
+ }
+
+ /**
+ * Our own little min method, to avoid loading
+ * <code>java.lang.Math</code> if we've run out of file
+ * descriptors and we're trying to print a stack trace.
+ */
+ private int min(int a, int b)
+ {
+ return (a < b ? a : b);
+ }
+
+ /**
+ * Write a portion of an array of characters.
+ *
+ * <p> Ordinarily this method stores characters from the given array into
+ * this stream's buffer, flushing the buffer to the underlying stream as
+ * needed. If the requested length is at least as large as the buffer,
+ * however, then this method will flush the buffer and write the characters
+ * directly to the underlying stream. Thus redundant
+ * <code>DiscardableBufferedWriter</code>s will not copy data unnecessarily.
+ *
+ * @param cbuf A character array
+ * @param off Offset from which to start reading characters
+ * @param len Number of characters to write
+ * @throws IOException
+ *
+ */
+ @Override
+ public final void write(char cbuf[], int off, int len)
+ throws IOException
+ {
+ if (bufferSize == 0)
+ {
+ writer.write(cbuf, off, len);
+ return;
+ }
+
+ if (len == 0)
+ {
+ return;
+ }
+
+ if (len >= bufferSize)
+ {
+ /* If the request length exceeds the size of the output buffer,
+ flush the buffer and then write the data directly. In this
+ way buffered streams will cascade harmlessly. */
+ if (autoFlush)
+ flushBuffer();
+ else
+ bufferOverflow();
+ writer.write(cbuf, off, len);
+ return;
+ }
+
+ int b = off, t = off + len;
+ while (b < t)
+ {
+ int d = min(bufferSize - nextChar, t - b);
+ System.arraycopy(cbuf, b, cb, nextChar, d);
+ b += d;
+ nextChar += d;
+ if (nextChar >= bufferSize)
+ if (autoFlush)
+ flushBuffer();
+ else
+ bufferOverflow();
+ }
+ }
+
+ /**
+ * Write an array of characters. This method cannot be inherited from the
+ * Writer class because it must suppress I/O exceptions.
+ * @param buf
+ * @throws IOException
+ */
+ @Override
+ public final void write(char buf[]) throws IOException
+ {
+ write(buf, 0, buf.length);
+ }
+
+ /**
+ * Write a portion of a String.
+ *
+ * @param s String to be written
+ * @param off Offset from which to start reading characters
+ * @param len Number of characters to be written
+ * @throws IOException
+ *
+ */
+ @Override
+ public final void write(String s, int off, int len) throws IOException
+ {
+ if (bufferSize == 0)
+ {
+ writer.write(s, off, len);
+ return;
+ }
+ int b = off, t = off + len;
+ while (b < t)
+ {
+ int d = min(bufferSize - nextChar, t - b);
+ s.getChars(b, b + d, cb, nextChar);
+ b += d;
+ nextChar += d;
+ if (nextChar >= bufferSize)
+ if (autoFlush)
+ flushBuffer();
+ else
+ bufferOverflow();
+ }
+ }
+
+ /**
+ * Write a string. This method cannot be inherited from the Writer class
+ * because it must suppress I/O exceptions.
+ * @param s
+ * @throws IOException
+ */
+ @Override
+ public final void write(String s) throws IOException
+ {
+ if (s != null)
+ {
+ write(s, 0, s.length());
+ }
+ }
+
+ /**
+ * resets this class so that it can be reused
+ * @param writer
+ *
+ */
+ public final void recycle(Writer writer)
+ {
+ this.writer = writer;
+ clear();
+ }
+
+ /**
+ * Send the content of a reference, e.g.; $foo, to the writer.
+ * The default implementation is to call the wrapped Writer's
+ * write(String) method.
+ */
+ @Override
+ public void writeReference(String ref) throws IOException
+ {
+ write(ref);
+ }
+
+}
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.&lt;scope_name&gt; = 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.&lt;scope_name&gt; = 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)
+ * &lt;strong&gt;$bodyContent&lt;/strong&gt; $txt
+ * #end
+ *
+ * #@strong($foobar)
+ * &lt;u&gt;This text is underlined and bold&lt;/u&gt;
+ * #end
+ * </pre>
+ * Will print:
+ * <pre>
+ * &lt;strong&gt;&lt;u&gt;This text is underlined and bold&lt;u&gt;&lt;/strong&gt; 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=&lt;!-- #include error :
+ * include.output.errormsg.end= --&gt;
+ * 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: &lt;result of getMessage&gt;
+ * </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: &lt;result of getMessage&gt;
+ * </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 &amp;&amp; right = false
+ * left &amp;&amp; null = false
+ * null &amp;&amp; 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 &gt;= 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 &gt; 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 &lt;= 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 &lt; 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 -&gt; left
+ * null || right -&gt; right
+ * null || null -&gt; false
+ * left || right -&gt; 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 -&gt; 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&lt;property&gt; when executed.
+ *
+ * We do this separately as to preserve the current
+ * quasi-broken semantics of get&lt;as is property&gt;
+ * get&lt;flip 1st char&gt; get("property") and now followed
+ * by is&lt;Property&gt;
+ *
+ * @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 &gt; n2, -1 if n1 &lt; 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 &lt;SPECIAL_TOKEN&gt;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&lt;foo&gt;(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.&lt;loader-id&gt;.&lt;property&gt; = &lt;value&gt;</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>
+ * &lt;resource-ref&gt;
+ * &lt;description&gt;Velocity template DataSource&lt;/description&gt;
+ * &lt;res-ref-name&gt;jdbc/Velocity&lt;/res-ref-name&gt;
+ * &lt;res-type&gt;javax.sql.DataSource&lt;/res-type&gt;
+ * &lt;res-auth&gt;Container&lt;/res-auth&gt;
+ * &lt;/resource-ref&gt;
+ * </code></pre>
+ * <br>
+ * and Tomcat 4 server.xml file: <br>
+ * <pre><code>
+ * [...]
+ * &lt;Context path="/exampleVelocity" docBase="exampleVelocity" debug="0"&gt;
+ * [...]
+ * &lt;ResourceParams name="jdbc/Velocity"&gt;
+ * &lt;parameter&gt;
+ * &lt;name&gt;driverClassName&lt;/name&gt;
+ * &lt;value&gt;org.hsql.jdbcDriver&lt;/value&gt;
+ * &lt;/parameter&gt;
+ * &lt;parameter&gt;
+ * &lt;name&gt;driverName&lt;/name&gt;
+ * &lt;value&gt;jdbc:HypersonicSQL:database&lt;/value&gt;
+ * &lt;/parameter&gt;
+ * &lt;parameter&gt;
+ * &lt;name&gt;user&lt;/name&gt;
+ * &lt;value&gt;database_username&lt;/value&gt;
+ * &lt;/parameter&gt;
+ * &lt;parameter&gt;
+ * &lt;name&gt;password&lt;/name&gt;
+ * &lt;value&gt;database_password&lt;/value&gt;
+ * &lt;/parameter&gt;
+ * &lt;/ResourceParams&gt;
+ * [...]
+ * &lt;/Context&gt;
+ * [...]
+ * </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 &lt;URL&gt;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);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayIterator.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayIterator.java
new file mode 100644
index 00000000..476b092d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayIterator.java
@@ -0,0 +1,120 @@
+package org.apache.velocity.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 java.lang.reflect.Array;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+/**
+ * <p>
+ * An Iterator wrapper for an Object[]. This will
+ * allow us to deal with all array like structures
+ * in a consistent manner.
+ * </p>
+ * <p>
+ * WARNING : this class's operations are NOT synchronized.
+ * It is meant to be used in a single thread, newly created
+ * for each use in the #foreach() directive.
+ * If this is used or shared, synchronize in the
+ * next() method.
+ * </p>
+ *
+ * @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 class ArrayIterator implements Iterator
+{
+ /**
+ * The objects to iterate.
+ */
+ private Object array;
+
+ /**
+ * The current position and size in the array.
+ */
+ private int pos;
+ private int size;
+
+ /**
+ * Creates a new iterator instance for the specified array.
+ *
+ * @param array The array for which an iterator is desired.
+ */
+ public ArrayIterator(Object array)
+ {
+ /*
+ * if this isn't an array, then throw. Note that this is
+ * for internal use - so this should never happen - if it does
+ * we screwed up.
+ */
+
+ if ( !array.getClass().isArray() )
+ {
+ throw new IllegalArgumentException(
+ "Programmer error : internal ArrayIterator invoked w/o array");
+ }
+
+ this.array = array;
+ pos = 0;
+ size = Array.getLength( this.array );
+ }
+
+ /**
+ * Move to next element in the array.
+ *
+ * @return The next object in the array.
+ */
+ @Override
+ public Object next()
+ {
+ if (pos < size )
+ return Array.get( array, pos++);
+
+ /*
+ * we screwed up...
+ */
+
+ throw new NoSuchElementException("No more elements: " + pos +
+ " / " + size);
+ }
+
+ /**
+ * Check to see if there is another element in the array.
+ *
+ * @return Whether there is another element.
+ */
+ @Override
+ public boolean hasNext()
+ {
+ return (pos < size );
+ }
+
+ /**
+ * No op--merely added to satisfy the <code>Iterator</code> interface.
+ */
+ @Override
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayListWrapper.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayListWrapper.java
new file mode 100644
index 00000000..43afffcb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/ArrayListWrapper.java
@@ -0,0 +1,67 @@
+package org.apache.velocity.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 java.lang.reflect.Array;
+import java.util.AbstractList;
+
+/**
+ * A class that wraps an array with a List interface.
+ *
+ * @author Chris Schultz &lt;chris@christopherschultz.net$gt;
+ * @version $Revision$ $Date: 2006-04-14 19:40:41 $
+ * @since 1.6
+ */
+public class ArrayListWrapper extends AbstractList
+{
+ private Object array;
+
+ public ArrayListWrapper(Object array)
+ {
+ this.array = array;
+ }
+
+ @Override
+ public Object get(int index)
+ {
+ return Array.get(array, index);
+ }
+
+ @Override
+ public Object set(int index, Object element)
+ {
+ Object old = get(index);
+ Array.set(array, index, element);
+ return old;
+ }
+
+ @Override
+ public int size()
+ {
+ return Array.getLength(array);
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return Array.getLength(array) == 0;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/ClassUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/ClassUtils.java
new file mode 100644
index 00000000..34ddf556
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/ClassUtils.java
@@ -0,0 +1,271 @@
+package org.apache.velocity.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.context.InternalContextAdapter;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.parser.node.ASTMethod.MethodCacheKey;
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+import org.apache.velocity.util.introspection.Info;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+import org.apache.velocity.util.introspection.VelMethod;
+
+import java.io.InputStream;
+
+
+
+/**
+ * Simple utility functions for manipulating classes and resources
+ * from the classloader.
+ *
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class ClassUtils {
+
+ /**
+ * Utility class; cannot be instantiated.
+ */
+ private ClassUtils()
+ {
+ }
+
+ /**
+ * Return the specified class. Checks the ThreadContext classloader first,
+ * then uses the System classloader. Should replace all calls to
+ * <code>Class.forName( claz )</code> (which only calls the System class
+ * loader) when the class might be in a different classloader (e.g. in a
+ * webapp).
+ *
+ * @param clazz the name of the class to instantiate
+ * @return the requested Class object
+ * @throws ClassNotFoundException
+ */
+ public static Class<?> getClass(String clazz) throws ClassNotFoundException
+ {
+ /**
+ * Use the Thread context classloader if possible
+ */
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ if (loader != null)
+ {
+ try
+ {
+ return Class.forName(clazz, true, loader);
+ }
+ catch (ClassNotFoundException E)
+ {
+ /*
+ * If not found with ThreadContext loader, fall thru to
+ * try System classloader below (works around bug in ant).
+ */
+ }
+ }
+ /*
+ * Thread context classloader isn't working out, so use system loader.
+ */
+ return Class.forName(clazz);
+ }
+
+ /**
+ * Return a new instance of the given class. Checks the ThreadContext
+ * classloader first, then uses the System classloader. Should replace all
+ * calls to <code>Class.forName( claz ).newInstance()</code> (which only
+ * calls the System class loader) when the class might be in a different
+ * classloader (e.g. in a webapp).
+ *
+ * @param clazz the name of the class to instantiate
+ * @return an instance of the specified class
+ * @throws ClassNotFoundException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ public static Object getNewInstance(String clazz)
+ throws ClassNotFoundException,IllegalAccessException,InstantiationException
+ {
+ return getClass(clazz).newInstance();
+ }
+
+ /**
+ * Finds a resource with the given name. Checks the Thread Context
+ * classloader, then uses the System classloader. Should replace all
+ * calls to <code>Class.getResourceAsString</code> when the resource
+ * might come from a different classloader. (e.g. a webapp).
+ * @param claz Class to use when getting the System classloader (used if no Thread
+ * Context classloader available or fails to get resource).
+ * @param name name of the resource
+ * @return InputStream for the resource.
+ */
+ public static InputStream getResourceAsStream(Class<?> claz, String name)
+ {
+ InputStream result = null;
+
+ /*
+ * remove leading slash so path will work with classes in a JAR file
+ */
+ while (name.startsWith("/"))
+ {
+ name = name.substring(1);
+ }
+
+ ClassLoader classLoader = Thread.currentThread()
+ .getContextClassLoader();
+
+ if (classLoader == null)
+ {
+ classLoader = claz.getClassLoader();
+ result = classLoader.getResourceAsStream( name );
+ }
+ else
+ {
+ result= classLoader.getResourceAsStream( name );
+
+ /*
+ * for compatibility with texen / ant tasks, fall back to
+ * old method when resource is not found.
+ */
+
+ if (result == null)
+ {
+ classLoader = claz.getClassLoader();
+ if (classLoader != null)
+ result = classLoader.getResourceAsStream( name );
+ }
+ }
+
+ return result;
+
+ }
+
+ /**
+ * Lookup a VelMethod object given the method signature that is specified in
+ * the passed in parameters. This method first searches the cache, if not found in
+ * the cache then uses reflections to inspect Object o, for the given method.
+ * @param methodName Name of method
+ * @param params Array of objects that are parameters to the method
+ * @param paramClasses Array of Classes corresponding to the types in params.
+ * @param o Object to introspect for the given method.
+ * @param context Context from which the method cache is acquired
+ * @param node ASTNode, used for error reporting.
+ * @param strictRef If no method is found, throw an exception, never return null in this case
+ * @return VelMethod object if the object is found, null if not matching method is found
+ */
+ public static VelMethod getMethod(String methodName, Object[] params,
+ Class<?>[] paramClasses, Object o, InternalContextAdapter context,
+ SimpleNode node, boolean strictRef)
+ {
+ VelMethod method = null;
+ try
+ {
+ /*
+ * check the cache
+ */
+ boolean classObject = (o instanceof Class);
+ MethodCacheKey mck = new MethodCacheKey(methodName, paramClasses, classObject);
+ IntrospectionCacheData icd = context.icacheGet(mck);
+ Class<?> clazz = classObject ? (Class<?>)o : o.getClass();
+
+ /*
+ * like ASTIdentifier, if we have cache information, and the Class of
+ * Object o is the same as that in the cache, we are safe.
+ */
+ if (icd != null && icd.contextData == clazz)
+ {
+ /*
+ * get the method from the cache
+ */
+ method = (VelMethod) icd.thingy;
+ }
+ else
+ {
+ /*
+ * otherwise, do the introspection, and then cache it
+ */
+ method = node.getRuntimeServices().getUberspect().getMethod(o, methodName, params,
+ new Info(node.getTemplateName(), node.getLine(), node.getColumn()));
+
+ if (method != null)
+ {
+ icd = new IntrospectionCacheData();
+ icd.contextData = clazz;
+ icd.thingy = method;
+ context.icachePut(mck, icd);
+ }
+ }
+
+ /*
+ * if we still haven't gotten the method, either we are calling a method
+ * that doesn't exist (which is fine...) or I screwed it up.
+ */
+ if (method == null)
+ {
+ if (strictRef)
+ {
+ // Create a parameter list for the exception error message
+ 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(", ");
+ }
+ throw new MethodInvocationException("Object '"
+ + o.getClass().getName() + "' does not contain method "
+ + methodName + "(" + plist + ")", null, methodName, node
+ .getTemplateName(), node.getLine(), node.getColumn());
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+ catch (MethodInvocationException mie)
+ {
+ /*
+ * this can come from the doIntrospection(), as the arg values are
+ * evaluated to find the right method signature. We just want to propagate
+ * it here, not do anything fancy
+ */
+ throw mie;
+ }
+ catch (RuntimeException e)
+ {
+ /**
+ * pass through application level runtime exceptions
+ */
+ throw e;
+ }
+ catch (Exception e)
+ {
+ /*
+ * can come from the doIntropection() also, from Introspector
+ */
+ String msg = "ASTMethod.execute() : exception from introspection";
+ throw new VelocityException(msg, e);
+ }
+
+ return method;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/ContextAware.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/ContextAware.java
new file mode 100644
index 00000000..7e7c3cda
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/ContextAware.java
@@ -0,0 +1,49 @@
+package org.apache.velocity.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.context.Context;
+
+
+/**
+ * Event handlers implementing this interface will automatically
+ * have the method setContext called before each event. This
+ * allows the event handler to use information in the latest context
+ * when responding to the event.
+ *
+ * <P>Important Note: Only local event handlers attached to the context
+ * (as opposed to global event handlers initialized in the velocity.properties
+ * file) should implement ContextAware. Since global event handlers are
+ * singletons individual requests will not be able to count on the
+ * correct context being loaded before a request.
+ *
+ * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public interface ContextAware
+{
+ /**
+ * Initialize the EventHandler.
+ * @param context
+ */
+ void setContext(Context context);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/DeprecationAwareExtProperties.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/DeprecationAwareExtProperties.java
new file mode 100644
index 00000000..a67454a5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/DeprecationAwareExtProperties.java
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.velocity.util;
+
+
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.DeprecatedRuntimeConstants;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This class extends ExtProperties to handle deprecated propery key names.
+ * @since 2.1
+ * @version $Revision: $
+ * @version $Id: DeprecationAwareExtProperties.java$
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @deprecated it will disappear along with deprecated key names in 3.0.
+ */
+@Deprecated
+public class DeprecationAwareExtProperties extends Hashtable<String, Object>
+{
+ /**
+ * <p>Logger used to log the use of deprecated properties names.</p>
+ * <p>Since at the time Velocity properties are set Velocity is not yet initialized,
+ * this logger namespace can only be a child of the default logger name: <code>org.apache.velocity.deprecation</code>.</p>
+ * <p>It won't honor the <code>runtime.log.instance</code> or <code>runtime.log.name</code> settings.</p>
+ */
+ protected static Logger logger = LoggerFactory.getLogger(RuntimeConstants.DEFAULT_RUNTIME_LOG_NAME + ".deprecation");
+
+ /**
+ * Emit a warning in the log for adeprecated property name
+ * @param oldName old property name
+ * @param newName new property name
+ */
+ protected void warnDeprecated(String oldName, String newName)
+ {
+ if (warned.add(oldName))
+ {
+ logger.warn("configuration key '{}' has been deprecated in favor of '{}'", oldName, newName);
+ }
+ }
+
+ /**
+ * Translate if needed a deprecated key into its replacement key, and emit a warning for deprecated keys
+ * @param key provided key
+ * @return translated key
+ */
+ protected String translateKey(String key)
+ {
+ // check for a replacement key
+ String replacement = propertiesReplacementMap.get(key);
+ if (replacement != null)
+ {
+ warnDeprecated(key, replacement);
+ return replacement;
+ }
+ // check for a resource loader property
+ int i = key.indexOf(".resource.loader.");
+ if (i != -1)
+ {
+ replacement = "resource.loader." + key.substring(0, i + 1) + key.substring(i + 17);
+ warnDeprecated(key, replacement);
+ return replacement;
+ }
+ // check for a control scope property
+ if (key.endsWith(".provide.scope.control"))
+ {
+ replacement = RuntimeConstants.CONTEXT_SCOPE_CONTROL + "." + key.substring(0, key.length() - 22);
+ warnDeprecated(key, replacement);
+ return replacement;
+ }
+ // looks good
+ return key;
+ }
+
+ /**
+ * Property getter which checks deprecated property keys
+ * @param key provided key
+ * @return found value under this key or under the corresponding deprecated one, if any
+ */
+ public Object get(String key)
+ {
+ return super.get(translateKey(key));
+ }
+
+ /**
+ * Property setter which checks deprecated property keys
+ * @param key provided key
+ * @param value provided value
+ * @return previous found value, if any
+ */
+ @Override
+ public Object put(String key, Object value)
+ {
+ return super.put(translateKey(key), value);
+ }
+
+ /**
+ * Property getter which checks deprecated property keys
+ * @param key provided key
+ * @return found value under this key or under the corresponding deprecated one, if any
+ */
+ public boolean containsKey(String key)
+ {
+ return super.containsKey(translateKey(key));
+ }
+
+ /**
+ * Set of old property names for which a warning has already been emitted
+ */
+ private Set<String> warned = new HashSet<>();
+
+ /**
+ * Property keys replacement map, from old key name to new key name
+ */
+ static private Map<String, String> propertiesReplacementMap = new HashMap<>();
+
+ static
+ {
+ {
+ try
+ {
+ Field oldFields[] = DeprecatedRuntimeConstants.class.getDeclaredFields();
+ for (Field oldField : oldFields)
+ {
+ String name = oldField.getName();
+ if (!name.startsWith("OLD_")) throw new VelocityException("Could not initialize property keys deprecation map because DeprecatedRuntimeConstants." + name + " field isn't properly named");
+ if (oldField.getType() != String.class) continue;
+ String oldValue = (String)oldField.get(null);
+ if (oldValue == null) throw new VelocityException("Could not initialize property keys deprecation map because DeprecatedRuntimeConstants." + name + " field isn't initialized");
+ name = name.substring(4);
+ Field newField = RuntimeConstants.class.getDeclaredField(name);
+ String newValue = (String)newField.get(null);
+ if (newValue == null) throw new VelocityException("Could not initialize property keys deprecation map because RuntimeConstants." + name + " field isn't initialized");
+ if (!newValue.equals(oldValue))
+ {
+ propertiesReplacementMap.put(oldValue, newValue);
+ }
+ }
+ }
+ catch (IllegalAccessException | NoSuchFieldException e)
+ {
+ throw new VelocityException("Could not initialize property keys deprecation map", e);
+ }
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
new file mode 100644
index 00000000..7eaddd32
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/DuckType.java
@@ -0,0 +1,326 @@
+package org.apache.velocity.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 java.lang.reflect.Array;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.velocity.runtime.parser.node.MathUtils.isZero;
+
+/**
+ * Support for getAs&lt;java.lang.reflect.Type&gt;() convention for rendering (String), evaluating (Boolean)
+ * or doing math with (Number) references.
+ *
+ * @author Nathan Bubna
+ * @since 2.0
+ */
+public class DuckType
+{
+ protected enum Types
+ {
+ STRING("getAsString"),
+ NUMBER("getAsNumber"),
+ BOOLEAN("getAsBoolean"),
+ EMPTY("isEmpty"),
+ LENGTH("length"),
+ SIZE("size");
+
+ final String name;
+ final Map<Class<?>, Object> cache = new HashMap<>();
+
+ Types(String name)
+ {
+ this.name = name;
+ }
+
+ void set(Class<?> c, Object o)
+ {
+ cache.put(c, o);
+ }
+
+ Object get(Class<?> c)
+ {
+ return cache.get(c);
+ }
+ }
+
+ protected static final Object NO_METHOD = new Object();
+
+ /**
+ * Clears the internal cache of all the underlying Types.
+ */
+ public static void clearCache()
+ {
+ for(Types type : Types.values())
+ {
+ type.cache.clear();
+ }
+ }
+
+ public static String asString(Object value)
+ {
+ return asString(value, true);
+ }
+
+ public static String asString(Object value, boolean coerceType)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+ if (value instanceof String)
+ {
+ return (String)value;
+ }
+ if (coerceType && value.getClass().isArray())
+ {
+ // nicify arrays string representation
+ StringBuilder builder = new StringBuilder();
+ builder.append('[');
+ int len = Array.getLength(value);
+ for (int i = 0; i < len; ++i)
+ {
+ if (i > 0) builder.append(", ");
+ builder.append(asString(Array.get(value, i)));
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+ Object got = get(value, Types.STRING);
+ if (got == NO_METHOD)
+ {
+ return coerceType ? value.toString() : null;
+ }
+ return (String)got;
+ }
+
+ public static boolean asNull(Object value)
+ {
+ return value == null ||
+ get(value, Types.STRING) == null ||
+ get(value, Types.NUMBER) == null;
+ }
+
+ public static boolean asBoolean(Object value, boolean coerceType)
+ {
+ if (value == null)
+ {
+ return false;
+ }
+ if (value instanceof Boolean)
+ {
+ return (Boolean) value;
+ }
+ Object got = get(value, Types.BOOLEAN);
+ if (got != NO_METHOD)
+ {
+ return (Boolean) got;
+ }
+ if (coerceType)
+ {
+ return !asEmpty(value);
+ }
+ return true;
+ }
+
+ // see VELOCITY-692 for discussion about empty values
+ public static boolean asEmpty(Object value)
+ {
+ // empty variable
+ if (value == null)
+ {
+ return true;
+ }
+
+ // empty array
+ if (value.getClass().isArray())
+ {
+ return Array.getLength(value) == 0;// [] is false
+ }
+
+ // isEmpty() for object / string
+ Object isEmpty = get(value, Types.EMPTY);
+ if (isEmpty != NO_METHOD)
+ {
+ return (Boolean)isEmpty;
+ }
+
+ // isEmpty() for object / other char sequences
+ Object length = get(value, Types.LENGTH);
+ if (length != NO_METHOD && length instanceof Number)
+ {
+ return isZero((Number)length);
+ }
+
+ // size() object / collection
+ Object size = get(value, Types.SIZE);
+ if (size != NO_METHOD && size instanceof Number)
+ {
+ return isZero((Number)size);
+ }
+
+ // zero numbers are false
+ if (value instanceof Number)
+ {
+ return isZero((Number)value);
+ }
+
+ // null getAsString()
+ Object asString = get(value, Types.STRING);
+ if (asString == null)
+ {
+ return true;// duck null
+ }
+ // empty getAsString()
+ else if (asString != NO_METHOD)
+ {
+ return ((String)asString).length() == 0;
+ }
+
+ // null getAsNumber()
+ Object asNumber = get(value, Types.NUMBER);
+ if (asNumber == null)
+ {
+ return true;
+ }
+ // zero numbers are false
+ else if (asNumber != NO_METHOD && asNumber instanceof Number)
+ {
+ return isZero((Number)asNumber);
+ }
+
+ return false;
+ }
+
+ public static Number asNumber(Object value)
+ {
+ return asNumber(value, true);
+ }
+
+ public static Number asNumber(Object value, boolean coerceType)
+ {
+ if (value == null)
+ {
+ return null;
+ }
+ if (value instanceof Number)
+ {
+ return (Number)value;
+ }
+ Object got = get(value, Types.NUMBER);
+ if (got != NO_METHOD)
+ {
+ return (Number)got;
+ }
+ if (coerceType)
+ {
+ String string = asString(value);// coerce to string
+ if (string != null)
+ {
+ return new BigDecimal(string);
+ }
+ }
+ return null;
+ }
+
+ protected static Object get(Object value, Types type)
+ {
+ try
+ {
+ // check cache
+ Class<?> c = value.getClass();
+ Object cached = type.get(c);
+ if (cached == NO_METHOD)
+ {
+ return cached;
+ }
+ if (cached != null)
+ {
+ return ((Method)cached).invoke(value);
+ }
+ // ok, search the class
+ Method method = findMethod(c, type);
+ if (method == null)
+ {
+ type.set(c, NO_METHOD);
+ return NO_METHOD;
+ }
+ type.set(c, method);
+ return method.invoke(value);
+ }
+ catch (RuntimeException re)
+ {
+ throw re;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);// no checked exceptions, please
+ }
+ }
+
+ protected static Method findMethod(Class<?> c, Types type)
+ {
+ if (c == null || c == Object.class)
+ {
+ return null;
+ }
+ Method m = getMethod(c, type.name);
+ if (m != null)
+ {
+ return m;
+ }
+ for (Class<?> i : c.getInterfaces())
+ {
+ m = findMethod(i, type);
+ if (m != null)
+ {
+ return m;
+ }
+ }
+ m = findMethod(c.getSuperclass(), type);
+ if (m != null)
+ {
+ return m;
+ }
+ return null;
+ }
+
+ private static Method getMethod(Class<?> c, String name)
+ {
+ if (Modifier.isPublic(c.getModifiers()))
+ {
+ try
+ {
+ Method m = c.getDeclaredMethod(name);
+ if (Modifier.isPublic(m.getModifiers()))
+ {
+ return m;
+ }
+ }
+ catch (NoSuchMethodException nsme) {}
+ }
+ return null;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/EnumerationIterator.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/EnumerationIterator.java
new file mode 100644
index 00000000..ac88b093
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/EnumerationIterator.java
@@ -0,0 +1,80 @@
+package org.apache.velocity.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 java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * An Iterator wrapper for an Enumeration.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class EnumerationIterator implements Iterator
+{
+ /**
+ * The enumeration to iterate.
+ */
+ private Enumeration enumeration = null;
+
+ /**
+ * Creates a new iteratorwrapper instance for the specified
+ * Enumeration.
+ *
+ * @param enumeration The Enumeration to wrap.
+ */
+ public EnumerationIterator(Enumeration enumeration)
+ {
+ this.enumeration = enumeration;
+ }
+
+ /**
+ * Move to next element in the array.
+ *
+ * @return The next object in the array.
+ */
+ @Override
+ public Object next()
+ {
+ return enumeration.nextElement();
+ }
+
+ /**
+ * Check to see if there is another element in the array.
+ *
+ * @return Whether there is another element.
+ */
+ @Override
+ public boolean hasNext()
+ {
+ return enumeration.hasMoreElements();
+ }
+
+ /**
+ * Unimplemented. No analogy in Enumeration
+ */
+ @Override
+ public void remove()
+ {
+ // not implemented
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java
new file mode 100644
index 00000000..b0f3bfd2
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/ExtProperties.java
@@ -0,0 +1,2087 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.velocity.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Enumeration;
+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;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * This class extends normal Java properties by adding the possibility
+ * to use the same key many times concatenating the value strings
+ * instead of overwriting them.
+ * <p>
+ * <b>Please consider using the <code>PropertiesConfiguration</code> class in
+ * Commons-Configuration as soon as it is released.</b>
+ * <p>
+ * The Extended Properties syntax is explained here:
+ *
+ * <ul>
+ * <li>
+ * Each property has the syntax <code>key = value</code>
+ * </li>
+ * <li>
+ * The <i>key</i> may use any character but the equal sign '='.
+ * </li>
+ * <li>
+ * <i>value</i> may be separated on different lines if a backslash
+ * is placed at the end of the line that continues below.
+ * </li>
+ * <li>
+ * If <i>value</i> is a list of strings, each token is separated
+ * by a comma ','.
+ * </li>
+ * <li>
+ * Commas in each token are escaped placing a backslash right before
+ * the comma.
+ * </li>
+ * <li>
+ * Backslashes are escaped by using two consecutive backslashes i.e. \\
+ * </li>
+ * <li>
+ * If a <i>key</i> is used more than once, the values are appended
+ * as if they were on the same line separated with commas.
+ * </li>
+ * <li>
+ * Blank lines and lines starting with character '#' are skipped.
+ * </li>
+ * <li>
+ * If a property is named "include" (or whatever is defined by
+ * setInclude() and getInclude() and the value of that property is
+ * the full path to a file on disk, that file will be included into
+ * the ConfigurationsRepository. You can also pull in files relative
+ * to the parent configuration file. So if you have something
+ * like the following:
+ *
+ * include = additional.properties
+ *
+ * Then "additional.properties" is expected to be in the same
+ * directory as the parent configuration file.
+ *
+ * Duplicate name values will be replaced, so be careful.
+ *
+ * </li>
+ * </ul>
+ *
+ * <p>Here is an example of a valid extended properties file:</p>
+ *
+ * <pre><code>
+ * # lines starting with # are comments
+ *
+ * # This is the simplest property
+ * key = value
+ *
+ * # A long property may be separated on multiple lines
+ * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
+ * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ *
+ * # This is a property with many tokens
+ * tokens_on_a_line = first token, second token
+ *
+ * # This sequence generates exactly the same result
+ * tokens_on_multiple_lines = first token
+ * tokens_on_multiple_lines = second token
+ *
+ * # commas may be escaped in tokens
+ * commas.escaped = Hi\, what'up?
+ * </code></pre>
+ *
+ * <p><b>NOTE</b>: this class has <b>not</b> been written for
+ * performance nor low memory usage. In fact, it's way slower than it
+ * could be and generates too much memory garbage. But since
+ * performance is not an issue during intialization (and there is not
+ * much time to improve it), I wrote it this way. If you don't like
+ * it, go ahead and tune it up!
+ *
+ * This class is a clone of org.apache.commons.collections.ExtendedProperties
+ * (which has been removed from commons-collections-4.x)
+ *
+ * @since 2.0
+ * @version $Revision: $
+ * @version $Id: ExtProperties.java$
+ *
+ * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
+ * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
+ * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
+ * @author Janek Bogucki
+ * @author Mohan Kishore
+ * @author Stephen Colebourne
+ * @author Shinobu Kawai
+ * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ */
+@SuppressWarnings("deprecation")
+public class ExtProperties extends DeprecationAwareExtProperties
+{
+ /**
+ * Default configurations repository.
+ */
+ private ExtProperties defaults;
+
+ /**
+ * The file connected to this repository (holding comments and
+ * such).
+ *
+ * @serial
+ */
+ protected String file;
+
+ /**
+ * Base path of the configuration file used to create
+ * this ExtProperties object.
+ */
+ protected String basePath;
+
+ /**
+ * File separator.
+ */
+ protected String fileSeparator;
+ {
+ try
+ {
+ fileSeparator = (String) AccessController.doPrivileged(
+ (PrivilegedAction) () -> System.getProperty("file.separator"));
+ }
+ catch (SecurityException ex)
+ {
+ fileSeparator = File.separator;
+ }
+ }
+
+ /**
+ * Has this configuration been initialized.
+ */
+ protected boolean isInitialized = false;
+
+ /**
+ * This is the name of the property that can point to other
+ * properties file for including other properties files.
+ */
+ protected static String include = "include";
+
+ /**
+ * These are the keys in the order they listed
+ * in the configuration file. This is useful when
+ * you wish to perform operations with configuration
+ * information in a particular order.
+ */
+ protected ArrayList<String> keysAsListed = new ArrayList<>();
+
+ protected final static String START_TOKEN="${";
+ protected final static String END_TOKEN="}";
+
+
+ /**
+ * Interpolate key names to handle ${key} stuff
+ *
+ * @param base string to interpolate
+ * @return returns the key name with the ${key} substituted
+ */
+ protected String interpolate(String base)
+ {
+ // COPIED from [configuration] 2003-12-29
+ return (interpolateHelper(base, null));
+ }
+
+ /**
+ * Recursive handler for multiple levels of interpolation.
+ *
+ * When called the first time, priorVariables should be null.
+ *
+ * @param base string with the ${key} variables
+ * @param priorVariables serves two purposes: to allow checking for
+ * loops, and creating a meaningful exception message should a loop
+ * occur. It's 0'th element will be set to the value of base from
+ * the first call. All subsequent interpolated variables are added
+ * afterward.
+ *
+ * @return the string with the interpolation taken care of
+ */
+ protected String interpolateHelper(String base, List<String> priorVariables)
+ {
+ // COPIED from [configuration] 2003-12-29
+ if (base == null)
+ {
+ return null;
+ }
+
+ // on the first call initialize priorVariables
+ // and add base as the first element
+ if (priorVariables == null)
+ {
+ priorVariables = new ArrayList<>();
+ priorVariables.add(base);
+ }
+
+ int begin = -1;
+ int end = -1;
+ int prec = 0 - END_TOKEN.length();
+ String variable = null;
+ StringBuilder result = new StringBuilder();
+
+ // FIXME: we should probably allow the escaping of the start token
+ while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length())) > -1)
+ && ((end = base.indexOf(END_TOKEN, begin)) > -1))
+ {
+ result.append(base.substring(prec + END_TOKEN.length(), begin));
+ variable = base.substring(begin + START_TOKEN.length(), end);
+
+ // if we've got a loop, create a useful exception message and throw
+ if (priorVariables.contains(variable))
+ {
+ String initialBase = priorVariables.remove(0).toString();
+ priorVariables.add(variable);
+ StringBuilder priorVariableSb = new StringBuilder();
+
+ // create a nice trace of interpolated variables like so:
+ // var1->var2->var3
+ for (Iterator it = priorVariables.iterator(); it.hasNext();)
+ {
+ priorVariableSb.append(it.next());
+ if (it.hasNext())
+ {
+ priorVariableSb.append("->");
+ }
+ }
+
+ throw new IllegalStateException(
+ "infinite loop in property interpolation of " + initialBase + ": " + priorVariableSb.toString());
+ }
+ // otherwise, add this variable to the interpolation list.
+ else
+ {
+
+ priorVariables.add(variable);
+ }
+
+ //QUESTION: getProperty or getPropertyDirect
+ Object value = getProperty(variable);
+ if (value != null)
+ {
+ result.append(interpolateHelper(value.toString(), priorVariables));
+
+ // pop the interpolated variable off the stack
+ // this maintains priorVariables correctness for
+ // properties with multiple interpolations, e.g.
+ // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
+ priorVariables.remove(priorVariables.size() - 1);
+ }
+ else if (defaults != null && defaults.getString(variable, null) != null)
+ {
+ result.append(defaults.getString(variable));
+ }
+ else
+ {
+
+ //variable not defined - so put it back in the value
+ result.append(START_TOKEN).append(variable).append(END_TOKEN);
+ }
+ prec = end;
+ }
+ result.append(base.substring(prec + END_TOKEN.length(), base.length()));
+
+ return result.toString();
+ }
+
+ /**
+ * Inserts a backslash before every comma and backslash.
+ */
+ private static String escape(String s)
+ {
+ StringBuilder buf = new StringBuilder(s);
+ for (int i = 0; i < buf.length(); i++)
+ {
+ char c = buf.charAt(i);
+ if (c == ',' || c == '\\')
+ {
+ buf.insert(i, '\\');
+ i++;
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Removes a backslash from every pair of backslashes.
+ */
+ private static String unescape(String s)
+ {
+ StringBuilder buf = new StringBuilder(s);
+ for (int i = 0; i < buf.length() - 1; i++)
+ {
+ char c1 = buf.charAt(i);
+ char c2 = buf.charAt(i + 1);
+ if (c1 == '\\' && c2 == '\\')
+ {
+ buf.deleteCharAt(i);
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Counts the number of successive times 'ch' appears in the
+ * 'line' before the position indicated by the 'index'.
+ */
+ private static int countPreceding(String line, int index, char ch)
+ {
+ int i;
+ for (i = index - 1; i >= 0; i--)
+ {
+ if (line.charAt(i) != ch)
+ {
+ break;
+ }
+ }
+ return index - 1 - i;
+ }
+
+ /**
+ * Checks if the line ends with odd number of backslashes
+ */
+ private static boolean endsWithSlash(String line)
+ {
+ if (!line.endsWith("\\"))
+ {
+ return false;
+ }
+ return (countPreceding(line, line.length() - 1, '\\') % 2 == 0);
+ }
+
+ /**
+ * This class is used to read properties lines. These lines do
+ * not terminate with new-line chars but rather when there is no
+ * backslash sign a the end of the line. This is used to
+ * concatenate multiple lines for readability.
+ */
+ static class PropertiesReader extends LineNumberReader
+ {
+ /**
+ * Constructor.
+ *
+ * @param reader A Reader.
+ */
+ public PropertiesReader(Reader reader)
+ {
+ super(reader);
+ }
+
+ /**
+ * Read a property.
+ *
+ * @return a String property
+ * @throws IOException if there is difficulty reading the source.
+ */
+ public String readProperty() throws IOException
+ {
+ StringBuilder buffer = new StringBuilder();
+ String line = readLine();
+ while (line != null)
+ {
+ line = line.trim();
+ if ((line.length() != 0) && (line.charAt(0) != '#'))
+ {
+ if (endsWithSlash(line))
+ {
+ line = line.substring(0, line.length() - 1);
+ buffer.append(line);
+ }
+ else
+ {
+
+ buffer.append(line);
+ return buffer.toString(); // normal method end
+ }
+ }
+ line = readLine();
+ }
+ return null; // EOF reached
+ }
+ }
+
+ /**
+ * This class divides into tokens a property value. Token
+ * separator is "," but commas into the property value are escaped
+ * using the backslash in front.
+ */
+ static class PropertiesTokenizer extends StringTokenizer
+ {
+ /**
+ * The property delimiter used while parsing (a comma).
+ */
+ static final String DELIMITER = ",";
+
+ /**
+ * Constructor.
+ *
+ * @param string A String.
+ */
+ public PropertiesTokenizer(String string)
+ {
+ super(string, DELIMITER);
+ }
+
+ /**
+ * Check whether the object has more tokens.
+ *
+ * @return True if the object has more tokens.
+ */
+ @Override
+ public boolean hasMoreTokens()
+ {
+ return super.hasMoreTokens();
+ }
+
+ /**
+ * Get next token.
+ *
+ * @return A String.
+ */
+ @Override
+ public String nextToken()
+ {
+ StringBuilder buffer = new StringBuilder();
+
+ while (hasMoreTokens())
+ {
+ String token = super.nextToken();
+ if (endsWithSlash(token))
+ {
+ buffer.append(token.substring(0, token.length() - 1));
+ buffer.append(DELIMITER);
+ }
+ else
+ {
+
+ buffer.append(token);
+ break;
+ }
+ }
+
+ return buffer.toString().trim();
+ }
+ }
+
+ /**
+ * Creates an empty extended properties object.
+ */
+ public ExtProperties()
+ {
+ super();
+ }
+
+ /**
+ * Creates and loads the extended properties from the specified file.
+ *
+ * @param file the filename to load
+ * @throws IOException if a file error occurs
+ */
+ public ExtProperties(String file) throws IOException
+ {
+ this(file, null);
+ }
+
+ /**
+ * Creates and loads the extended properties from the specified file.
+ *
+ * @param file the filename to load
+ * @param defaultFile a second filename to load default values from
+ * @throws IOException if a file error occurs
+ */
+ public ExtProperties(String file, String defaultFile) throws IOException
+ {
+ this.file = file;
+
+ basePath = new File(file).getAbsolutePath();
+ basePath = basePath.substring(0, basePath.lastIndexOf(fileSeparator) + 1);
+
+ FileInputStream in = null;
+ try
+ {
+ in = new FileInputStream(file);
+ this.load(in);
+ }
+ finally
+ {
+ try
+ {
+ if (in != null)
+ {
+ in.close();
+ }
+ }
+ catch (IOException ex) {}
+ }
+
+ if (defaultFile != null)
+ {
+ defaults = new ExtProperties(defaultFile);
+ }
+ }
+
+ /**
+ * Indicate to client code whether property
+ * resources have been initialized or not.
+ * @return initialization status
+ */
+ public boolean isInitialized()
+ {
+ return isInitialized;
+ }
+
+ /**
+ * Gets the property value for including other properties files.
+ * By default it is "include".
+ *
+ * @return A String.
+ */
+ public String getInclude()
+ {
+ return include;
+ }
+
+ /**
+ * Sets the property value for including other properties files.
+ * By default it is "include".
+ *
+ * @param inc A String.
+ */
+ public void setInclude(String inc)
+ {
+ include = inc;
+ }
+
+ /**
+ * Load the properties from the given input stream.
+ *
+ * @param input the InputStream to load from
+ * @throws IOException if an IO error occurs
+ */
+ public void load(InputStream input) throws IOException
+ {
+ load(input, null);
+ }
+
+ /**
+ * Load the properties from the given input stream
+ * and using the specified encoding.
+ *
+ * @param input the InputStream to load from
+ * @param enc the encoding to use
+ * @throws IOException if an IO error occurs
+ */
+ public synchronized void load(InputStream input, String enc) throws IOException
+ {
+ PropertiesReader reader = null;
+ if (enc != null)
+ {
+ try
+ {
+ reader = new PropertiesReader(new InputStreamReader(input, enc));
+
+ }
+ catch (UnsupportedEncodingException ex)
+ {
+ // Another try coming up....
+ }
+ }
+
+ // fall back to UTF-8
+ if (reader == null)
+ {
+ reader = new PropertiesReader(new InputStreamReader(input, StandardCharsets.UTF_8));
+ }
+
+ try
+ {
+ while (true)
+ {
+ String line = reader.readProperty();
+ if (line == null)
+ {
+ return; // EOF
+ }
+ int equalSign = line.indexOf('=');
+
+ if (equalSign > 0)
+ {
+ String key = line.substring(0, equalSign).trim();
+ String value = line.substring(equalSign + 1).trim();
+
+ // Configure produces lines like this ... just ignore them
+ if ("".equals(value))
+ {
+ continue;
+ }
+
+ if (getInclude() != null && key.equalsIgnoreCase(getInclude()))
+ {
+ // Recursively load properties files.
+ File file = null;
+
+ if (value.startsWith(fileSeparator))
+ {
+ // We have an absolute path so we'll use this
+ file = new File(value);
+
+ }
+ else
+ {
+
+ // We have a relative path, and we have two
+ // possible forms here. If we have the "./" form
+ // then just strip that off first before continuing.
+ if (value.startsWith("." + fileSeparator))
+ {
+ value = value.substring(2);
+ }
+
+ file = new File(basePath + value);
+ }
+
+ if (file.exists() && file.canRead())
+ {
+ load(new FileInputStream(file));
+ }
+ }
+ else
+ {
+
+ addProperty(key, value);
+ }
+ }
+ }
+ }
+ finally
+ {
+ // Loading is initializing
+ isInitialized = true;
+ }
+ }
+
+ /**
+ * Gets a property from the configuration.
+ *
+ * @param key property to retrieve
+ * @return value as object. Will return user value if exists,
+ * if not then default value if exists, otherwise null
+ */
+ public Object getProperty(String key)
+ {
+ // first, try to get from the 'user value' store
+ Object obj = this.get(key);
+
+ if (obj == null)
+ {
+ // if there isn't a value there, get it from the
+ // defaults if we have them
+ if (defaults != null)
+ {
+ obj = defaults.get(key);
+ }
+ }
+
+ return obj;
+ }
+
+ /**
+ * 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
+ *
+ * <code>resource.loaders = file</code>
+ *
+ * is already present in the configuration and you
+ *
+ * <code>addProperty("resource.loaders", "classpath")</code>
+ *
+ * Then you will end up with a Vector like the
+ * following:
+ *
+ * <code>["file", "classpath"]</code>
+ *
+ * @param key the key to add
+ * @param value the value to add
+ */
+ public void addProperty(String key, Object value)
+ {
+ if (value instanceof String)
+ {
+ String str = (String) value;
+ if (str.indexOf(PropertiesTokenizer.DELIMITER) > 0)
+ {
+ // token contains commas, so must be split apart then added
+ PropertiesTokenizer tokenizer = new PropertiesTokenizer(str);
+ while (tokenizer.hasMoreTokens())
+ {
+ String token = tokenizer.nextToken();
+ addPropertyInternal(key, unescape(token));
+ }
+ }
+ else
+ {
+
+ // token contains no commas, so can be simply added
+ addPropertyInternal(key, unescape(str));
+ }
+ }
+ else
+ {
+
+ addPropertyInternal(key, value);
+ }
+
+ // Adding a property connotes initialization
+ isInitialized = true;
+ }
+
+ /**
+ * Adds a key/value pair to the map. This routine does
+ * no magic morphing. It ensures the keylist is maintained
+ *
+ * @param key the key to store at
+ * @param value the decoded object to store
+ */
+ private void addPropertyDirect(String key, Object value)
+ {
+ // safety check
+ if (!containsKey(key))
+ {
+ keysAsListed.add(translateKey(key));
+ }
+ put(key, value);
+ }
+
+ /**
+ * Adds a decoded property to the map w/o checking for commas - used
+ * internally when a property has been broken up into
+ * strings that could contain escaped commas to prevent
+ * the inadvertent vectorization.
+ * <p>
+ * Thanks to Leon Messerschmidt for this one.
+ *
+ * @param key the key to store at
+ * @param value the decoded object to store
+ */
+ private void addPropertyInternal(String key, Object value)
+ {
+ Object current = this.get(key);
+
+ if (current instanceof String)
+ {
+ // one object already in map - convert it to a vector
+ List<Object> values = new Vector<>(2);
+ values.add(current);
+ values.add(value);
+ put(key, values);
+
+ }
+ else if (current instanceof List)
+ {
+ // already a list - just add the new token
+ ((List) current).add(value);
+
+ }
+ else
+ {
+
+ // brand new key - store in keysAsListed to retain order
+ if (!containsKey(key))
+ {
+ keysAsListed.add(translateKey(key));
+ }
+ put(key, value);
+ }
+ }
+
+ /**
+ * Set a property, this will replace any previously
+ * set values. Set values is implicitly a call
+ * to clearProperty(key), addProperty(key,value).
+ *
+ * @param key the key to set
+ * @param value the value to set
+ */
+ public void setProperty(String key, Object value)
+ {
+ clearProperty(key);
+ addProperty(key, value);
+ }
+
+ /**
+ * Save the properties to the given output stream.
+ * <p>
+ * The stream is not closed, but it is flushed.
+ *
+ * @param output an OutputStream, may be null
+ * @param header a textual comment to act as a file header
+ * @throws IOException if an IO error occurs
+ */
+ public synchronized void save(OutputStream output, String header) throws IOException
+ {
+ if (output == null)
+ {
+ return;
+ }
+ PrintWriter theWrtr = new PrintWriter(output);
+ if (header != null)
+ {
+ theWrtr.println(header);
+ }
+
+ Enumeration theKeys = keys();
+ while (theKeys.hasMoreElements())
+ {
+ String key = (String) theKeys.nextElement();
+ Object value = get(key);
+ if (value != null)
+ {
+ if (value instanceof String)
+ {
+ StringBuilder currentOutput = new StringBuilder();
+ currentOutput.append(key);
+ currentOutput.append("=");
+ currentOutput.append(escape((String) value));
+ theWrtr.println(currentOutput.toString());
+
+ }
+ else if (value instanceof List)
+ {
+ List<String> values = (List<String>) value;
+ for (String currentElement : values)
+ {
+ StringBuilder currentOutput = new StringBuilder();
+ currentOutput.append(key);
+ currentOutput.append("=");
+ currentOutput.append(escape(currentElement));
+ theWrtr.println(currentOutput.toString());
+ }
+ }
+ }
+ theWrtr.println();
+ theWrtr.flush();
+ }
+ }
+
+ /**
+ * Combines an existing Hashtable with this Hashtable.
+ * <p>
+ * Warning: It will overwrite previous entries without warning.
+ *
+ * @param props the properties to combine
+ */
+ public void combine(ExtProperties props)
+ {
+ for (Iterator it = props.getKeys(); it.hasNext();)
+ {
+ String key = (String) it.next();
+ setProperty(key, props.get(key));
+ }
+ }
+
+ /**
+ * Clear a property in the configuration.
+ *
+ * @param key the property key to remove along with corresponding value
+ */
+ public void clearProperty(String key)
+ {
+ key = translateKey(key);
+ if (containsKey(key))
+ {
+ // we also need to rebuild the keysAsListed or else
+ // things get *very* confusing
+ for (int i = 0; i < keysAsListed.size(); i++)
+ {
+ if (( keysAsListed.get(i)).equals(key))
+ {
+ keysAsListed.remove(i);
+ break;
+ }
+ }
+ remove(key);
+ }
+ }
+
+ /**
+ * Get the list of the keys contained in the configuration
+ * repository.
+ *
+ * @return an Iterator over the keys
+ */
+ public Iterator<String> getKeys()
+ {
+ return keysAsListed.iterator();
+ }
+
+ /**
+ * Get the list of the keys contained in the configuration
+ * repository that match the specified prefix.
+ *
+ * @param prefix the prefix to match
+ * @return an Iterator of keys that match the prefix
+ */
+ public Iterator<String> getKeys(String prefix)
+ {
+ Iterator<String> keys = getKeys();
+ ArrayList<String> matchingKeys = new ArrayList<>();
+
+ while (keys.hasNext())
+ {
+ String key = keys.next();
+
+ if (key.startsWith(prefix))
+ {
+ matchingKeys.add(key);
+ }
+ }
+ return matchingKeys.iterator();
+ }
+
+ /**
+ * Create an ExtProperties object that is a subset
+ * of this one. Take into account duplicate keys
+ * by using the setProperty() in ExtProperties.
+ *
+ * @param prefix the prefix to get a subset for
+ * @return a new independent ExtProperties
+ */
+ public ExtProperties subset(String prefix)
+ {
+ ExtProperties c = new ExtProperties();
+ Iterator<String> keys = getKeys();
+ boolean validSubset = false;
+
+ while (keys.hasNext())
+ {
+ String key = keys.next();
+
+ if (key.startsWith(prefix))
+ {
+ if (!validSubset)
+ {
+ validSubset = true;
+ }
+
+ /*
+ * Check to make sure that c.subset(prefix) doesn't
+ * blow up when there is only a single property
+ * with the key prefix. This is not a useful
+ * subset but it is a valid subset.
+ */
+ String newKey = null;
+ if (key.length() == prefix.length())
+ {
+ newKey = prefix;
+ }
+ else
+ {
+
+ newKey = key.substring(prefix.length() + 1);
+ }
+
+ /*
+ * use addPropertyDirect() - this will plug the data as
+ * is into the Map, but will also do the right thing
+ * re key accounting
+ */
+ c.addPropertyDirect(newKey, get(key));
+ }
+ }
+
+ if (validSubset)
+ {
+ return c;
+ }
+ else
+ {
+
+ return null;
+ }
+ }
+
+ /**
+ * Display the configuration for debugging purposes to System.out.
+ */
+ public void display()
+ {
+ Iterator<String> i = getKeys();
+
+ while (i.hasNext())
+ {
+ String key = i.next();
+ Object value = get(key);
+ System.out.println(key + " => " + value);
+ }
+ }
+
+ /**
+ * Get a string associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated string.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a String.
+ */
+ public String getString(String key)
+ {
+ return getString(key, null);
+ }
+
+ /**
+ * Get a string associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated string if key is found,
+ * default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a String.
+ */
+ public String getString(String key, String defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof String)
+ {
+ return interpolate((String) value);
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return interpolate(defaults.getString(key, defaultValue));
+ }
+ else
+ {
+
+ return interpolate(defaultValue);
+ }
+ }
+ else if (value instanceof List)
+ {
+ return interpolate((String) ((List) value).get(0));
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a String object");
+ }
+ }
+
+ /**
+ * Get a list of properties associated with the given
+ * configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated properties if key is found.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a String/List.
+ * @throws IllegalArgumentException if one of the tokens is
+ * malformed (does not contain an equals sign).
+ */
+ public Properties getProperties(String key)
+ {
+ return getProperties(key, new Properties());
+ }
+
+ /**
+ * Get a list of properties associated with the given
+ * configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaults default values
+ * @return The associated properties if key is found.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a String/List.
+ * @throws IllegalArgumentException if one of the tokens is
+ * malformed (does not contain an equals sign).
+ */
+ public Properties getProperties(String key, Properties defaults)
+ {
+ /*
+ * Grab an array of the tokens for this key.
+ */
+ String[] tokens = getStringArray(key);
+
+ // Each token is of the form 'key=value'.
+ Properties props = new Properties(defaults);
+ for (String token : tokens)
+ {
+ int equalSign = token.indexOf('=');
+ if (equalSign > 0)
+ {
+ String pkey = token.substring(0, equalSign).trim();
+ String pvalue = token.substring(equalSign + 1).trim();
+ props.put(pkey, pvalue);
+ }
+ else
+ {
+ throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign");
+ }
+ }
+ return props;
+ }
+
+ /**
+ * Get an array of strings associated with the given configuration
+ * key.
+ *
+ * @param key The configuration key.
+ * @return The associated string array if key is found.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a String/List.
+ */
+ public String[] getStringArray(String key)
+ {
+ Object value = get(key);
+
+ List<Object> values;
+ if (value instanceof String)
+ {
+ values = new Vector<>(1);
+ values.add(value);
+
+ }
+ else if (value instanceof List)
+ {
+ values = (List) value;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getStringArray(key);
+ }
+ else
+ {
+
+ return new String[0];
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a String/List object");
+ }
+
+ String[] tokens = new String[values.size()];
+ for (int i = 0; i < tokens.length; i++)
+ {
+ tokens[i] = (String) values.get(i);
+ }
+
+ return tokens;
+ }
+
+ /**
+ * Get a Vector of strings associated with the given configuration
+ * key.
+ *
+ * @param key The configuration key.
+ * @return The associated Vector.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Vector.
+ */
+ public Vector getVector(String key)
+ {
+ return getVector(key, null);
+ }
+
+ /**
+ * Get a Vector of strings associated with the given configuration key.
+ * <p>
+ * The list is a copy of the internal data of this object, and as
+ * such you may alter it freely.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated Vector.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Vector.
+ */
+ public Vector getVector(String key, Vector defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof List)
+ {
+ return new Vector<Object>((List) value);
+
+ }
+ else if (value instanceof String)
+ {
+ Vector<Object> values = new Vector<>(1);
+ values.add(value);
+ put(key, values);
+ return values;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getVector(key, defaultValue);
+ }
+ else
+ {
+
+ return ((defaultValue == null) ? new Vector<>() : defaultValue);
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Vector object");
+ }
+ }
+
+ /**
+ * Get a List of strings associated with the given configuration key.
+ * <p>
+ * The list is a copy of the internal data of this object, and as
+ * such you may alter it freely.
+ *
+ * @param key The configuration key.
+ * @return The associated List object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a List.
+ * @since Commons Collections 3.2
+ */
+ public List getList(String key)
+ {
+ return getList(key, null);
+ }
+
+ /**
+ * Get a List of strings associated with the given configuration key.
+ * <p>
+ * The list is a copy of the internal data of this object, and as
+ * such you may alter it freely.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated List.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a List.
+ * @since Commons Collections 3.2
+ */
+ public List getList(String key, List defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof List)
+ {
+ return new ArrayList((List) value);
+
+ }
+ else if (value instanceof String)
+ {
+ List values = new ArrayList(1);
+ values.add(value);
+ put(key, values);
+ return values;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getList(key, defaultValue);
+ }
+ else
+ {
+
+ return ((defaultValue == null) ? new ArrayList() : defaultValue);
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a List object");
+ }
+ }
+
+ /**
+ * Get a boolean associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated boolean.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Boolean.
+ */
+ public boolean getBoolean(String key)
+ {
+ Boolean b = getBoolean(key, null);
+ if (b != null)
+ {
+ return b;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a boolean associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated boolean.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Boolean.
+ */
+ public boolean getBoolean(String key, boolean defaultValue)
+ {
+ return getBoolean(key, Boolean.valueOf(defaultValue));
+ }
+
+ /**
+ * Get a boolean associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated boolean if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Boolean.
+ */
+ public Boolean getBoolean(String key, Boolean defaultValue)
+ {
+
+ Object value = get(key);
+
+ if (value instanceof Boolean)
+ {
+ return (Boolean) value;
+
+ }
+ else if (value instanceof String)
+ {
+ String s = testBoolean(((String) value).trim());
+ Boolean b = Boolean.valueOf(s);
+ put(key, b);
+ return b;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getBoolean(key, defaultValue);
+ }
+ else
+ {
+
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Boolean object");
+ }
+ }
+
+ /**
+ * Test whether the string represent by value maps to a boolean
+ * value or not. We will allow <code>true</code>, <code>on</code>,
+ * and <code>yes</code> for a <code>true</code> boolean value, and
+ * <code>false</code>, <code>off</code>, and <code>no</code> for
+ * <code>false</code> boolean values. Case of value to test for
+ * boolean status is ignored.
+ *
+ * @param value the value to test for boolean state
+ * @return <code>true</code> or <code>false</code> if the supplied
+ * text maps to a boolean value, or <code>null</code> otherwise.
+ */
+ public String testBoolean(String value)
+ {
+ String s = value.toLowerCase(Locale.ROOT);
+
+ switch (s)
+ {
+ case "true":
+ case "on":
+ case "yes":
+ return "true";
+ case "false":
+ case "off":
+ case "no":
+ return "false";
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Get a byte associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated byte.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Byte.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public byte getByte(String key)
+ {
+ Byte b = getByte(key, null);
+ if (b != null)
+ {
+ return b;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a byte associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated byte.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Byte.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public byte getByte(String key, byte defaultValue)
+ {
+ return getByte(key, Byte.valueOf(defaultValue));
+ }
+
+ /**
+ * Get a byte associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated byte if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Byte.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public Byte getByte(String key, Byte defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof Byte)
+ {
+ return (Byte) value;
+
+ }
+ else if (value instanceof String)
+ {
+ Byte b = Byte.valueOf((String) value);
+ put(key, b);
+ return b;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getByte(key, defaultValue);
+ }
+ else
+ {
+
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Byte object");
+ }
+ }
+
+ /**
+ * Get a short associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated short.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Short.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public short getShort(String key)
+ {
+ Short s = getShort(key, null);
+ if (s != null)
+ {
+ return s;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a short associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated short.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Short.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public short getShort(String key, short defaultValue)
+ {
+ return getShort(key, Short.valueOf(defaultValue));
+ }
+
+ /**
+ * Get a short associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated short if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Short.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public Short getShort(String key, Short defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof Short)
+ {
+ return (Short) value;
+
+ }
+ else if (value instanceof String)
+ {
+ Short s = Short.valueOf((String) value);
+ put(key, s);
+ return s;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getShort(key, defaultValue);
+ }
+ else
+ {
+
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Short object");
+ }
+ }
+
+ /**
+ * The purpose of this method is to get the configuration resource
+ * with the given name as an integer.
+ *
+ * @param name The resource name.
+ * @return The value of the resource as an integer.
+ */
+ public int getInt(String name)
+ {
+ return getInteger(name);
+ }
+
+ /**
+ * The purpose of this method is to get the configuration resource
+ * with the given name as an integer, or a default value.
+ *
+ * @param name The resource name
+ * @param def The default value of the resource.
+ * @return The value of the resource as an integer.
+ */
+ public int getInt(String name, int def)
+ {
+ return getInteger(name, def);
+ }
+
+ /**
+ * Get a int associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated int.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Integer.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public int getInteger(String key)
+ {
+ Integer i = getInteger(key, null);
+ if (i != null)
+ {
+ return i;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a int associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated int.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Integer.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public int getInteger(String key, int defaultValue)
+ {
+ Integer i = getInteger(key, null);
+
+ if (i == null)
+ {
+ return defaultValue;
+ }
+ return i;
+ }
+
+ /**
+ * Get a int associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated int if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Integer.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public Integer getInteger(String key, Integer defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof Integer)
+ {
+ return (Integer) value;
+
+ }
+ else if (value instanceof Number)
+ {
+ return ((Number)value).intValue();
+ }
+ else if (value instanceof String)
+ {
+ Integer i = Integer.valueOf((String) value);
+ put(key, i);
+ return i;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getInteger(key, defaultValue);
+ }
+ else
+ {
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Integer object");
+ }
+ }
+
+ /**
+ * Get a long associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated long.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Long.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public long getLong(String key)
+ {
+ Long l = getLong(key, null);
+ if (l != null)
+ {
+ return l;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a long associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated long.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Long.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public long getLong(String key, long defaultValue)
+ {
+ return getLong(key, Long.valueOf(defaultValue));
+ }
+
+ /**
+ * Get a long associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated long if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Long.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public Long getLong(String key, Long defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof Long)
+ {
+ return (Long) value;
+ }
+ else if (value instanceof Number)
+ {
+ return ((Number)value).longValue();
+ }
+ else if (value instanceof String)
+ {
+ Long l = Long.valueOf((String) value);
+ put(key, l);
+ return l;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getLong(key, defaultValue);
+ }
+ else
+ {
+
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Long object");
+ }
+ }
+
+ /**
+ * Get a float associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated float.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Float.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public float getFloat(String key)
+ {
+ Float f = getFloat(key, null);
+ if (f != null)
+ {
+ return f;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a float associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated float.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Float.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public float getFloat(String key, float defaultValue)
+ {
+ return getFloat(key, Float.valueOf(defaultValue));
+ }
+
+ /**
+ * Get a float associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated float if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Float.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public Float getFloat(String key, Float defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof Float)
+ {
+ return (Float) value;
+
+ }
+ else if (value instanceof Number)
+ {
+ return ((Number)value).floatValue();
+ }
+ else if (value instanceof String)
+ {
+ Float f = new Float((String) value);
+ put(key, f);
+ return f;
+
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getFloat(key, defaultValue);
+ }
+ else
+ {
+
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Float object");
+ }
+ }
+
+ /**
+ * Get a double associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @return The associated double.
+ * @throws NoSuchElementException is thrown if the key doesn't
+ * map to an existing object.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Double.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public double getDouble(String key)
+ {
+ Double d = getDouble(key, null);
+ if (d != null)
+ {
+ return d;
+ }
+ else
+ {
+
+ throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
+ }
+ }
+
+ /**
+ * Get a double associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated double.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Double.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public double getDouble(String key, double defaultValue)
+ {
+ return getDouble(key, Double.valueOf(defaultValue));
+ }
+
+ /**
+ * Get a double associated with the given configuration key.
+ *
+ * @param key The configuration key.
+ * @param defaultValue The default value.
+ * @return The associated double if key is found and has valid
+ * format, default value otherwise.
+ * @throws ClassCastException is thrown if the key maps to an
+ * object that is not a Double.
+ * @throws NumberFormatException is thrown if the value mapped
+ * by the key has not a valid number format.
+ */
+ public Double getDouble(String key, Double defaultValue)
+ {
+ Object value = get(key);
+
+ if (value instanceof Double)
+ {
+ return (Double) value;
+ }
+ else if (value instanceof Number)
+ {
+ return ((Number)value).doubleValue();
+ }
+ else if (value instanceof String)
+ {
+ Double d = new Double((String) value);
+ put(key, d);
+ return d;
+ }
+ else if (value == null)
+ {
+ if (defaults != null)
+ {
+ return defaults.getDouble(key, defaultValue);
+ }
+ else
+ {
+
+ return defaultValue;
+ }
+ }
+ else
+ {
+
+ throw new ClassCastException('\'' + key + "' doesn't map to a Double object");
+ }
+ }
+
+ /**
+ * Convert a standard properties class into a configuration class.
+ * <p>
+ * NOTE: From Commons Collections 3.2 this method will pick up
+ * any default parent Properties of the specified input object.
+ *
+ * @param props the properties object to convert
+ * @return new ExtProperties created from props
+ */
+ public static ExtProperties convertProperties(Properties props)
+ {
+ ExtProperties c = new ExtProperties();
+
+ for (Enumeration e = props.propertyNames(); e.hasMoreElements();)
+ {
+ String s = (String) e.nextElement();
+ /*
+ * We use get() and not getProperty() in case the user didn't
+ * respect the Properties contract of only holding strings.
+ * Sun's fault.
+ */
+ c.setProperty(s, props.get(s));
+ }
+
+ return c;
+ }
+
+ /**
+ * Convert a Map into a configuration class.
+ * <p>
+ * NOTE: From Commons Collections 3.2 this method will pick up
+ * any default parent Properties of the specified input object.
+ *
+ * @param props the Map object to convert
+ * @return new ExtProperties created from props
+ */
+ public static ExtProperties convertProperties(Map props)
+ {
+ ExtProperties c = new ExtProperties();
+
+ for (Map.Entry entry : (Set<Map.Entry>)props.entrySet())
+ {
+ c.setProperty(String.valueOf(entry.getKey()), entry.getValue());
+ }
+ return c;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/RuntimeServicesAware.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/RuntimeServicesAware.java
new file mode 100644
index 00000000..739439fe
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/RuntimeServicesAware.java
@@ -0,0 +1,42 @@
+package org.apache.velocity.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.RuntimeServices;
+
+
+/**
+ * Use this interface to automatically
+ * have the method setRuntimeServices called at initialization.
+ * Applies to EventHandler and Uberspect implementations.
+ *
+ * @author <a href="mailto:wglass@wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public interface RuntimeServicesAware
+{
+ /**
+ * Called automatically when event cartridge is initialized.
+ * @param rs RuntimeServices object assigned during initialization
+ */
+ void setRuntimeServices(RuntimeServices rs);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/SimplePool.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/SimplePool.java
new file mode 100644
index 00000000..2fb9d34b
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/SimplePool.java
@@ -0,0 +1,137 @@
+package org.apache.velocity.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.
+ */
+
+/**
+ * Simple object pool. Based on ThreadPool and few other classes
+ *
+ * The pool will ignore overflow and return null if empty.
+ *
+ * @author Gal Shachor
+ * @author Costin
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public final class SimplePool
+{
+ /*
+ * Where the objects are held.
+ */
+ private Object pool[];
+
+ /**
+ * max amount of objects to be managed
+ * set via CTOR
+ */
+ private int max;
+
+ /**
+ * index of previous to next
+ * free slot
+ */
+ private int current=-1;
+
+ /**
+ * @param max
+ */
+ public SimplePool(int max)
+ {
+ this.max = max;
+ pool = new Object[max];
+ }
+
+ /**
+ * Add the object to the pool, silent nothing if the pool is full
+ * @param o
+ */
+ public void put(Object o)
+ {
+ int idx=-1;
+
+ synchronized(this)
+ {
+ /*
+ * if we aren't full
+ */
+
+ if (current < max - 1)
+ {
+ /*
+ * then increment the
+ * current index.
+ */
+ idx = ++current;
+ }
+
+ if (idx >= 0)
+ {
+ pool[idx] = o;
+ }
+ }
+ }
+
+ /**
+ * Get an object from the pool, null if the pool is empty.
+ * @return The object from the pool.
+ */
+ public Object get()
+ {
+ synchronized(this)
+ {
+ /*
+ * if we have any in the pool
+ */
+ if( current >= 0 )
+ {
+ /*
+ * remove the current one
+ */
+
+ Object o = pool[current];
+ pool[current] = null;
+
+ current--;
+
+ return o;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Return the size of the pool
+ * @return The pool size.
+ */
+ public int getMax()
+ {
+ return max;
+ }
+
+ /**
+ * for testing purposes, so we can examine the pool
+ *
+ * @return Array of Objects in the pool.
+ */
+ Object[] getPool()
+ {
+ return pool;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/StringBuilderWriter.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/StringBuilderWriter.java
new file mode 100644
index 00000000..5daf927f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/StringBuilderWriter.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.velocity.util;
+
+import java.io.Serializable;
+import java.io.Writer;
+
+/**
+ * {@link Writer} implementation that outputs to a {@link StringBuilder}.
+ * <p>
+ * <strong>NOTE:</strong> This implementation, as an alternative to
+ * <code>java.io.StringWriter</code>, provides an <i>un-synchronized</i>
+ * (i.e. for use in a single thread) implementation for better performance.
+ * For safe usage with multiple {@link Thread}s then
+ * <code>java.io.StringWriter</code> should be used.</p>
+ *
+ * <p>(borrowed from commons.io project)</p>
+ *
+ * @version $Id$
+ * @since 2.0
+ */
+public class StringBuilderWriter extends Writer implements Serializable {
+
+ private static final long serialVersionUID = -146927496096066153L;
+ private final StringBuilder builder;
+
+ /**
+ * Constructs a new {@link StringBuilder} instance with default capacity.
+ */
+ public StringBuilderWriter() {
+ this.builder = new StringBuilder();
+ }
+
+ /**
+ * Constructs a new {@link StringBuilder} instance with the specified capacity.
+ *
+ * @param capacity The initial capacity of the underlying {@link StringBuilder}
+ */
+ public StringBuilderWriter(final int capacity) {
+ this.builder = new StringBuilder(capacity);
+ }
+
+ /**
+ * Constructs a new instance with the specified {@link StringBuilder}.
+ *
+ * <p>If {@code builder} is null a new instance with default capacity will be created.</p>
+ *
+ * @param builder The String builder. May be null.
+ */
+ public StringBuilderWriter(final StringBuilder builder) {
+ this.builder = builder != null ? builder : new StringBuilder();
+ }
+
+ /**
+ * Appends a single character to this Writer.
+ *
+ * @param value The character to append
+ * @return This writer instance
+ */
+ @Override
+ public Writer append(final char value) {
+ builder.append(value);
+ return this;
+ }
+
+ /**
+ * Appends a character sequence to this Writer.
+ *
+ * @param value The character to append
+ * @return This writer instance
+ */
+ @Override
+ public Writer append(final CharSequence value) {
+ builder.append(value);
+ return this;
+ }
+
+ /**
+ * Appends a portion of a character sequence to the {@link StringBuilder}.
+ *
+ * @param value The character to append
+ * @param start The index of the first character
+ * @param end The index of the last character + 1
+ * @return This writer instance
+ */
+ @Override
+ public Writer append(final CharSequence value, final int start, final int end) {
+ builder.append(value, start, end);
+ return this;
+ }
+
+ /**
+ * Closing this writer has no effect.
+ */
+ @Override
+ public void close() {
+ // no-op
+ }
+
+ /**
+ * Flushing this writer has no effect.
+ */
+ @Override
+ public void flush() {
+ // no-op
+ }
+
+
+ /**
+ * Writes a String to the {@link StringBuilder}.
+ *
+ * @param value The value to write
+ */
+ @Override
+ public void write(final String value) {
+ if (value != null) {
+ builder.append(value);
+ }
+ }
+
+ /**
+ * Writes a portion of a character array to the {@link StringBuilder}.
+ *
+ * @param value The value to write
+ * @param offset The index of the first character
+ * @param length The number of characters to write
+ */
+ @Override
+ public void write(final char[] value, final int offset, final int length) {
+ if (value != null) {
+ builder.append(value, offset, length);
+ }
+ }
+
+ /**
+ * Returns the underlying builder.
+ *
+ * @return The underlying builder
+ */
+ public StringBuilder getBuilder() {
+ return builder;
+ }
+
+ /**
+ * Returns {@link StringBuilder#toString()}.
+ *
+ * @return The contents of the String builder.
+ */
+ @Override
+ public String toString() {
+ return builder.toString();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/StringUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/StringUtils.java
new file mode 100644
index 00000000..62e86dbf
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/StringUtils.java
@@ -0,0 +1,92 @@
+package org.apache.velocity.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.directive.Directive;
+import org.apache.velocity.runtime.parser.node.Node;
+import org.apache.velocity.util.introspection.Info;
+
+/**
+ * This class provides some methods for dynamically
+ * invoking methods in objects, and some string
+ * manipulation and formatting methods.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @version $Id$
+ */
+public class StringUtils
+{
+ /**
+ * Creates a string that formats the template filename with line number
+ * and column of the given Directive. We use this routine to provide a consistent format for displaying
+ * file errors.
+ * @param directive currrent directive
+ * @return formatted string
+ */
+ public static String formatFileString(Directive directive)
+ {
+ return formatFileString(directive.getTemplateName(), directive.getLine(), directive.getColumn());
+ }
+
+ /**
+ * Creates a string that formats the template filename with line number
+ * and column of the given Node. We use this routine to provide a consistent format for displaying
+ * file errors.
+ * @param node current node
+ * @return formatted string
+ */
+ public static String formatFileString(Node node)
+ {
+ return formatFileString(node.getTemplateName(), node.getLine(), node.getColumn());
+ }
+
+ /**
+ * Simply creates a string that formats the template filename with line number
+ * and column. We use this routine to provide a consistent format for displaying
+ * file errors.
+ * @param info template name, line and column infos
+ * @return formatted string
+ */
+ public static String formatFileString(Info info)
+ {
+ return formatFileString(info.getTemplateName(), info.getLine(), info.getColumn());
+ }
+
+ /**
+ * Simply creates a string that formats the template filename with line number
+ * and column. We use this routine to provide a consistent format for displaying
+ * file errors.
+ * @param template File name of template, can be null
+ * @param linenum Line number within the file
+ * @param colnum Column number withing the file at linenum
+ * @return formatted string
+ */
+ public static String formatFileString(String template, int linenum, int colnum)
+ {
+ StringBuilder buffer = new StringBuilder();
+ if (org.apache.commons.lang3.StringUtils.isEmpty(template))
+ {
+ template = "<unknown template>";
+ }
+ buffer.append(template).append("[line ").append(linenum).append(", column ").append(colnum).append(']');
+ return buffer.toString();
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateBoolean.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateBoolean.java
new file mode 100644
index 00000000..037d923f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateBoolean.java
@@ -0,0 +1,38 @@
+package org.apache.velocity.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.
+ */
+
+/**
+ * Any object in the context which implements TemplateBoolean will use the
+ * getAsBoolean() method for #if( $ref ) calls.
+ *
+ * @author Nathan Bubna
+ * @since 2.0
+ */
+public interface TemplateBoolean
+{
+
+ /**
+ * Returns a boolean that can be used in a template.
+ * @return A boolean that can be used in a template.
+ */
+ boolean getAsBoolean();
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateNumber.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateNumber.java
new file mode 100644
index 00000000..2997e2b9
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateNumber.java
@@ -0,0 +1,38 @@
+package org.apache.velocity.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.
+ */
+
+/**
+ * Any object in the context which implements TemplateNumber will be treated
+ * as a number for the purposes of arithmetic operations and comparison.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @since 1.5
+ */
+public interface TemplateNumber
+{
+
+ /**
+ * Returns a Number that can be used in a template.
+ * @return A Number that can be used in a template.
+ */
+ Number getAsNumber();
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateString.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateString.java
new file mode 100644
index 00000000..14adc9e4
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/TemplateString.java
@@ -0,0 +1,38 @@
+package org.apache.velocity.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.
+ */
+
+/**
+ * Any object in the context which implements TemplateString will use the
+ * getAsString() method for rendering and empty-checks instead of toString().
+ *
+ * @author Nathan Bubna
+ * @since 2.0
+ */
+public interface TemplateString
+{
+
+ /**
+ * Returns a String that can be used in a template.
+ * @return A String that can be used in a template.
+ */
+ String getAsString();
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/AbstractChainableUberspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/AbstractChainableUberspector.java
new file mode 100644
index 00000000..ea722be8
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/AbstractChainableUberspector.java
@@ -0,0 +1,120 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.util.Iterator;
+
+/**
+ * Default implementation of a {@link ChainableUberspector chainable uberspector} that forwards all calls to the wrapped
+ * uberspector (when that is possible). It should be used as the base class for all chainable uberspectors.
+ *
+ * @version $Id: $
+ * @since 1.6
+ * @see ChainableUberspector
+ */
+public abstract class AbstractChainableUberspector extends UberspectImpl implements ChainableUberspector
+{
+ /** The wrapped (decorated) uberspector. */
+ protected Uberspect inner;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see ChainableUberspector#wrap(org.apache.velocity.util.introspection.Uberspect)
+ * @see #inner
+ */
+ @Override
+ public void wrap(Uberspect inner)
+ {
+ this.inner = inner;
+ }
+
+ /**
+ * init - the chainable uberspector is responsible for the initialization of the wrapped uberspector
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#init()
+ */
+ //@Override
+ @Override
+ public void init()
+ {
+ if (this.inner != null) {
+ try {
+ this.inner.init();
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ this.inner = null;
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getIterator(java.lang.Object,
+ * org.apache.velocity.util.introspection.Info)
+ */
+ //@SuppressWarnings("unchecked")
+ //@Override
+ @Override
+ public Iterator getIterator(Object obj, Info i)
+ {
+ return (this.inner != null) ? this.inner.getIterator(obj, i) : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getMethod(java.lang.Object, java.lang.String,
+ * java.lang.Object[], org.apache.velocity.util.introspection.Info)
+ */
+ //@Override
+ @Override
+ public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
+ {
+ return (this.inner != null) ? this.inner.getMethod(obj, methodName, args, i) : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getPropertyGet(java.lang.Object, java.lang.String,
+ * org.apache.velocity.util.introspection.Info)
+ */
+ //@Override
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
+ {
+ return (this.inner != null) ? this.inner.getPropertyGet(obj, identifier, i) : null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getPropertySet(java.lang.Object, java.lang.String,
+ * java.lang.Object, org.apache.velocity.util.introspection.Info)
+ */
+ //@Override
+ @Override
+ public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i)
+ {
+ return (this.inner != null) ? this.inner.getPropertySet(obj, identifier, arg, i) : null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ChainableUberspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ChainableUberspector.java
new file mode 100644
index 00000000..7811ad3f
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ChainableUberspector.java
@@ -0,0 +1,37 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 that marks uberspectors as chainable, meaning that multiple uberspectors can be
+ * combined in a chain (using the Decorator pattern).
+ *
+ * @version $Id: $
+ * @since 1.6
+ */
+public interface ChainableUberspector extends Uberspect
+{
+ /**
+ * Specify the decorated Uberspector
+ *
+ * @param inner The decorated uberspector.
+ */
+ void wrap(Uberspect inner);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassFieldMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassFieldMap.java
new file mode 100644
index 00000000..37d3003a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassFieldMap.java
@@ -0,0 +1,175 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.Field;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A cache of introspection information for a specific class instance.
+ * Keys {@link java.lang.reflect.Field} objects by the field names.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @author Nathan Bubna
+ * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
+ */
+public class ClassFieldMap
+{
+ /** Set true if you want to debug the reflection code */
+ private static final boolean debugReflection = false;
+
+ /** Class logger */
+ private final Logger log;
+
+ /**
+ * Class passed into the constructor used to as
+ * the basis for the Field map.
+ */
+ private final Class<?> clazz;
+
+ /**
+ * String --&gt; Field map, the key is the field name
+ */
+ private final Map<String, Field> fieldCache;
+
+ /**
+ * Standard constructor
+ * @param clazz The class for which this ClassMap gets constructed.
+ * @param log logger
+ */
+ public ClassFieldMap(final Class<?> clazz, final Logger log)
+ {
+ this.clazz = clazz;
+ this.log = log;
+
+ if (debugReflection)
+ {
+ log.debug("=================================================================");
+ log.debug("== Class: {}", clazz);
+ }
+
+ fieldCache = createFieldCache();
+
+ if (debugReflection)
+ {
+ log.debug("=================================================================");
+ }
+ }
+
+ /**
+ * Returns the class object whose fields are cached by this map.
+ *
+ * @return The class object whose fields are cached by this map.
+ */
+ public Class<?> getCachedClass()
+ {
+ return clazz;
+ }
+
+ /**
+ * Find a Field using the field name.
+ *
+ * @param name The field name to look up.
+ * @return A Field object representing the field to invoke or null.
+ */
+ public Field findField(final String name)
+ {
+ return fieldCache.get(name);
+ }
+
+ /**
+ * Populate the Map of direct hits. These
+ * are taken from all the public fields
+ * that our class, its parents and their implemented interfaces provide.
+ */
+ private Map<String, Field> createFieldCache()
+ {
+ Map<String, Field> fieldCache = new ConcurrentHashMap<>();
+ //
+ // Looks through all elements in the class hierarchy.
+ //
+ // We ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
+ // hit with Tomcat 5.5).
+ // Ah, the miracles of Java for(;;) ...
+ for (Class<?> classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
+ {
+ if (Modifier.isPublic(classToReflect.getModifiers()))
+ {
+ populateFieldCacheWith(fieldCache, classToReflect);
+ }
+ Class<?> [] interfaces = classToReflect.getInterfaces();
+ for (Class<?> anInterface : interfaces)
+ {
+ populateFieldCacheWithInterface(fieldCache, anInterface);
+ }
+ }
+ // return the already initialized cache
+ return fieldCache;
+ }
+
+ /* recurses up interface hierarchy to get all super interfaces (VELOCITY-689) */
+ private void populateFieldCacheWithInterface(Map<String, Field> fieldCache, Class<?> iface)
+ {
+ if (Modifier.isPublic(iface.getModifiers()))
+ {
+ populateFieldCacheWith(fieldCache, iface);
+ }
+ Class<?>[] supers = iface.getInterfaces();
+ for (Class<?> aSuper : supers)
+ {
+ populateFieldCacheWithInterface(fieldCache, aSuper);
+ }
+ }
+
+ private void populateFieldCacheWith(Map<String, Field> fieldCache, Class<?> classToReflect)
+ {
+ if (debugReflection)
+ {
+ log.debug("Reflecting {}", classToReflect);
+ }
+
+ try
+ {
+ Field[] fields = classToReflect.getDeclaredFields();
+ for (Field field : fields)
+ {
+ int modifiers = field.getModifiers();
+ if (Modifier.isPublic(modifiers))
+ {
+ fieldCache.put(field.getName(), field);
+ }
+ }
+ }
+ catch (SecurityException se) // Everybody feels better with...
+ {
+ log.debug("While accessing fields of {}:", classToReflect, se);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
new file mode 100644
index 00000000..d46f7e1d
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ClassMap.java
@@ -0,0 +1,380 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.lang.reflect.Modifier;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A cache of introspection information for a specific class instance.
+ * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
+ * method name and the names of classes that make up the parameters.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @author Nathan Bubna
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id$
+ */
+public class ClassMap
+{
+ /** Set true if you want to debug the reflection code */
+ private static final boolean debugReflection = false;
+
+ /** Class logger */
+ private final Logger log;
+
+ /**
+ * Class passed into the constructor used to as
+ * the basis for the Method map.
+ */
+ private final Class<?> clazz;
+
+ private final MethodCache methodCache;
+
+ /**
+ * Standard constructor
+ * @param clazz The class for which this ClassMap gets constructed.
+ * @param log logger
+ */
+ public ClassMap(final Class<?> clazz, final Logger log)
+ {
+ this(clazz, log, null);
+ }
+
+ /**
+ * Standard constructor
+ * @param clazz The class for which this ClassMap gets constructed.
+ * @param log logger
+ * @param conversionHandler conversion handler
+ * @since 2.0
+ */
+ public ClassMap(final Class<?> clazz, final Logger log, final TypeConversionHandler conversionHandler)
+ {
+ this.clazz = clazz;
+ this.log = log;
+
+ if (debugReflection)
+ {
+ log.debug("=================================================================");
+ log.debug("== Class: {}", clazz);
+ }
+
+ methodCache = createMethodCache(conversionHandler);
+
+ if (debugReflection)
+ {
+ log.debug("=================================================================");
+ }
+ }
+
+ /**
+ * Returns the class object whose methods are cached by this map.
+ *
+ * @return The class object whose methods are cached by this map.
+ */
+ public Class<?> getCachedClass()
+ {
+ return clazz;
+ }
+
+ /**
+ * Find a Method using the method name and parameter objects.
+ *
+ * @param name The method name to look up.
+ * @param params An array of parameters for the method.
+ * @return A Method object representing the method to invoke or null.
+ * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
+ */
+ public Method findMethod(final String name, final Object[] params)
+ throws MethodMap.AmbiguousException
+ {
+ return methodCache.get(name, params);
+ }
+
+ /**
+ * Populate the Map of direct hits. These
+ * are taken from all the public methods
+ * that our class, its parents and their implemented interfaces provide.
+ */
+ private MethodCache createMethodCache(TypeConversionHandler conversionHandler)
+ {
+ MethodCache methodCache = new MethodCache(log, conversionHandler);
+ //
+ // Looks through all elements in the class hierarchy. This one is bottom-first (i.e. we start
+ // with the actual declaring class and its interfaces and then move up (superclass etc.) until we
+ // hit java.lang.Object. That is important because it will give us the methods of the declaring class
+ // which might in turn be abstract further up the tree.
+ //
+ // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently
+ // hit with Tomcat 5.5).
+ //
+ // We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up
+ // until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will
+ // hit the public elements sooner or later because we reflect all the public elements anyway.
+ //
+ // Ah, the miracles of Java for(;;) ...
+ for (Class<?> classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
+ {
+ if (Modifier.isPublic(classToReflect.getModifiers()))
+ {
+ populateMethodCacheWith(methodCache, classToReflect);
+ }
+ Class<?> [] interfaces = classToReflect.getInterfaces();
+ for (Class<?> anInterface : interfaces)
+ {
+ populateMethodCacheWithInterface(methodCache, anInterface);
+ }
+ }
+ // return the already initialized cache
+ return methodCache;
+ }
+
+ /* recurses up interface heirarchy to get all super interfaces (VELOCITY-689) */
+ private void populateMethodCacheWithInterface(MethodCache methodCache, Class<?> iface)
+ {
+ if (Modifier.isPublic(iface.getModifiers()))
+ {
+ populateMethodCacheWith(methodCache, iface);
+ }
+ Class<?>[] supers = iface.getInterfaces();
+ for (Class<?> aSuper : supers)
+ {
+ populateMethodCacheWithInterface(methodCache, aSuper);
+ }
+ }
+
+ private void populateMethodCacheWith(MethodCache methodCache, Class<?> classToReflect)
+ {
+ if (debugReflection)
+ {
+ log.debug("Reflecting {}", classToReflect);
+ }
+
+ try
+ {
+ Method[] methods = classToReflect.getDeclaredMethods();
+ for (Method method : methods)
+ {
+ int modifiers = method.getModifiers();
+ if (Modifier.isPublic(modifiers))
+ {
+ methodCache.put(method);
+ }
+ }
+ }
+ catch (SecurityException se) // Everybody feels better with...
+ {
+ log.debug("While accessing methods of {}:", classToReflect, se);
+ }
+ }
+
+ /**
+ * This is the cache to store and look up the method information.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+ private static final class MethodCache
+ {
+ private static final Object CACHE_MISS = new Object();
+
+ private static final String NULL_ARG = Object.class.getName();
+
+ private static final Map<Class<?>, String> convertPrimitives = new HashMap();
+
+ static
+ {
+ convertPrimitives.put(Boolean.TYPE, Boolean.class.getName());
+ convertPrimitives.put(Byte.TYPE, Byte.class.getName());
+ convertPrimitives.put(Character.TYPE, Character.class.getName());
+ convertPrimitives.put(Double.TYPE, Double.class.getName());
+ convertPrimitives.put(Float.TYPE, Float.class.getName());
+ convertPrimitives.put(Integer.TYPE, Integer.class.getName());
+ convertPrimitives.put(Long.TYPE, Long.class.getName());
+ convertPrimitives.put(Short.TYPE, Short.class.getName());
+ }
+
+ /** Class logger */
+ private final Logger log;
+
+ /**
+ * Cache of Methods, or CACHE_MISS, keyed by method
+ * name and actual arguments used to find it.
+ */
+ private final Map<Object, Object> cache = new ConcurrentHashMap<>();
+
+ /** Map of methods that are searchable according to method parameters to find a match */
+ private final MethodMap methodMap;
+
+ private MethodCache(Logger log, TypeConversionHandler conversionHandler)
+ {
+ this.log = log;
+ methodMap = new MethodMap(conversionHandler);
+ }
+
+ /**
+ * Find a Method using the method name and parameter objects.
+ *
+ * Look in the methodMap for an entry. If found,
+ * it'll either be a CACHE_MISS, in which case we
+ * simply give up, or it'll be a Method, in which
+ * case, we return it.
+ *
+ * If nothing is found, then we must actually go
+ * and introspect the method from the MethodMap.
+ *
+ * @param name The method name to look up.
+ * @param params An array of parameters for the method.
+ * @return A Method object representing the method to invoke or null.
+ * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters.
+ */
+ public Method get(final String name, final Object [] params)
+ throws MethodMap.AmbiguousException
+ {
+ String methodKey = makeMethodKey(name, params);
+
+ Object cacheEntry = cache.get(methodKey);
+ if (cacheEntry == CACHE_MISS)
+ {
+ // We looked this up before and failed.
+ return null;
+ }
+
+ if (cacheEntry == null)
+ {
+ try
+ {
+ // That one is expensive...
+ cacheEntry = methodMap.find(name, params);
+ }
+ catch(MethodMap.AmbiguousException ae)
+ {
+ /*
+ * that's a miss :-)
+ */
+ cache.put(methodKey, CACHE_MISS);
+ throw ae;
+ }
+
+ cache.put(methodKey,
+ (cacheEntry != null) ? cacheEntry : CACHE_MISS);
+ }
+
+ // Yes, this might just be null.
+ return (Method) cacheEntry;
+ }
+
+ private void put(Method method)
+ {
+ String methodKey = makeMethodKey(method);
+
+ // We don't overwrite methods because we fill the
+ // cache from defined class towards java.lang.Object
+ // and that would cause overridden methods to appear
+ // as if they were not overridden.
+ if (cache.get(methodKey) == null)
+ {
+ cache.put(methodKey, method);
+ methodMap.add(method);
+ if (debugReflection)
+ {
+ log.debug("Adding {}", method);
+ }
+ }
+ }
+
+ /**
+ * Make a methodKey for the given method using
+ * the concatenation of the name and the
+ * types of the method parameters.
+ *
+ * @param method to be stored as key
+ * @return key for ClassMap
+ */
+ private String makeMethodKey(final Method method)
+ {
+ Class<?>[] parameterTypes = method.getParameterTypes();
+ int args = parameterTypes.length;
+ if (args == 0)
+ {
+ return method.getName();
+ }
+
+ StringBuilder methodKey = new StringBuilder((args+1)*16).append(method.getName());
+
+ for (Class<?> parameterType : parameterTypes)
+ {
+ /*
+ * If the argument type is primitive then we want
+ * to convert our primitive type signature to the
+ * corresponding Object type so introspection for
+ * methods with primitive types will work correctly.
+ *
+ * The lookup map (convertPrimitives) contains all eight
+ * primitives (boolean, byte, char, double, float, int, long, short)
+ * known to Java. So it should never return null for the key passed in.
+ */
+ if (parameterType.isPrimitive())
+ {
+ methodKey.append((String) convertPrimitives.get(parameterType));
+ } else
+ {
+ methodKey.append(parameterType.getName());
+ }
+ }
+
+ return methodKey.toString();
+ }
+
+ private String makeMethodKey(String method, Object[] params)
+ {
+ int args = params.length;
+ if (args == 0)
+ {
+ return method;
+ }
+
+ StringBuilder methodKey = new StringBuilder((args+1)*16).append(method);
+
+ for (Object arg : params)
+ {
+ if (arg == null)
+ {
+ methodKey.append(NULL_ARG);
+ }
+ else
+ {
+ methodKey.append(arg.getClass().getName());
+ }
+ }
+
+ return methodKey.toString();
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
new file mode 100644
index 00000000..77222639
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/ConversionHandler.java
@@ -0,0 +1,70 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 conversion handler adds admissible conversions between Java types whenever Velocity introspection has to map
+ * VTL methods and property accessors to Java methods.
+ * Both methods must be consistent: <code>getNeededConverter</code> must not return <code>null</code> whenever
+ * <code>isExplicitlyConvertible</code> returned true with the same arguments.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: ConversionHandler.java $
+ * @deprecated use {@link TypeConversionHandler}
+ * @see TypeConversionHandler
+ * @since 2.0
+ */
+
+@Deprecated
+public interface ConversionHandler
+{
+ /**
+ * Check to see if the conversion can be done using an explicit conversion
+ *
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @param possibleVarArg whether var arg is possible here
+ * @return null if no conversion is needed, or the appropriate Converter object
+ * @since 2.0
+ */
+ boolean isExplicitlyConvertible(Class<?> formal, Class<?> actual, boolean possibleVarArg);
+
+ /**
+ * Returns the appropriate Converter object needed for an explicit conversion
+ * Returns null if no conversion is needed.
+ *
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @return null if no conversion is needed, or the appropriate Converter object
+ * @since 2.0
+ */
+ Converter getNeededConverter(final Class<?> formal, final Class<?> actual);
+
+ /**
+ * Add the given converter to the handler. Implementation should be thread-safe.
+ *
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @param converter converter
+ * @since 2.0
+ */
+ void addConverter(Class<?> formal, Class<?> actual, Converter converter);
+}
+
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java
new file mode 100644
index 00000000..276d0bd2
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Converter.java
@@ -0,0 +1,39 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.
+ */
+
+/**
+ * Converts a value to type T
+ *
+ * @since 2.0
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id$
+ */
+
+@FunctionalInterface
+public interface Converter<T>
+{
+ /**
+ * convert object to type T
+ * @param o input object
+ * @return converted object
+ */
+ T convert(Object o);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/DeprecatedCheckUberspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/DeprecatedCheckUberspector.java
new file mode 100644
index 00000000..7a1d4349
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/DeprecatedCheckUberspector.java
@@ -0,0 +1,111 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.lang.reflect.Method;
+
+/**
+ * Chainable Uberspector that checks for deprecated method calls. It does that by checking if the returned
+ * method has a Deprecated annotation. Because this is a chainable uberspector, it has to re-get the method using a
+ * default introspector, which is not safe; future uberspectors might not be able to return a precise method name, or a
+ * method of the original target object.
+ *
+ * Borrowed from the XWiki project.
+ *
+ * @since 2.0
+ * @version $Id:$
+ * @see ChainableUberspector
+ */
+public class DeprecatedCheckUberspector extends AbstractChainableUberspector implements Uberspect
+{
+ @Override
+ public void init()
+ {
+ super.init();
+ this.introspector = new Introspector(this.log);
+ }
+
+ @Override
+ public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
+ {
+ VelMethod method = super.getMethod(obj, methodName, args, i);
+ if (method != null) {
+ Method m = this.introspector.getMethod(obj.getClass(), method.getMethodName(), args);
+ if (m != null
+ && (m.isAnnotationPresent(Deprecated.class)
+ || m.getDeclaringClass().isAnnotationPresent(Deprecated.class)
+ || obj.getClass().isAnnotationPresent(Deprecated.class))) {
+ logWarning("method", obj, method.getMethodName(), i);
+ }
+ }
+
+ return method;
+ }
+
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
+ {
+ VelPropertyGet method = super.getPropertyGet(obj, identifier, i);
+ if (method != null) {
+ Method m = this.introspector.getMethod(obj.getClass(), method.getMethodName(), new Object[] {});
+ if (m != null
+ && (m.isAnnotationPresent(Deprecated.class)
+ || m.getDeclaringClass().isAnnotationPresent(Deprecated.class)
+ || obj.getClass().isAnnotationPresent(Deprecated.class))) {
+ logWarning("getter", obj, method.getMethodName(), i);
+ }
+ }
+
+ return method;
+ }
+
+ @Override
+ public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i)
+ {
+ // TODO Auto-generated method stub
+ VelPropertySet method = super.getPropertySet(obj, identifier, arg, i);
+ if (method != null) {
+ Method m = this.introspector.getMethod(obj.getClass(), method.getMethodName(), new Object[] { arg });
+ if (m != null
+ && (m.isAnnotationPresent(Deprecated.class)
+ || m.getDeclaringClass().isAnnotationPresent(Deprecated.class)
+ || obj.getClass().isAnnotationPresent(Deprecated.class))) {
+ logWarning("setter", obj, method.getMethodName(), i);
+ }
+ }
+
+ return method;
+ }
+
+ /**
+ * Helper method to log a warning when a deprecation has been found.
+ *
+ * @param deprecationType the type of deprecation (eg "getter", "setter", "method")
+ * @param object the object that has a deprecation
+ * @param methodName the deprecated method's name
+ * @param info a Velocity {@link org.apache.velocity.util.introspection.Info} object containing information about
+ * where the deprecation was located in the Velocity template file
+ */
+ private void logWarning(String deprecationType, Object object, String methodName, Info info)
+ {
+ this.log.warn("Deprecated usage of {} [{}] in {}@{},{}", deprecationType, object.getClass()
+ .getCanonicalName() + "." + methodName, info.getTemplateName(), info.getLine(), info.getColumn());
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Info.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Info.java
new file mode 100644
index 00000000..fbf02f4b
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Info.java
@@ -0,0 +1,97 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.Node;
+import org.apache.velocity.util.StringUtils;
+
+/**
+ * Little class to carry in info such as template name, line and column
+ * for information error reporting from the uberspector implementations
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class Info
+{
+ private int line;
+ private int column;
+ private String templateName;
+
+ /**
+ * @param source Usually a template name.
+ * @param line The line number from <code>source</code>.
+ * @param column The column number from <code>source</code>.
+ */
+ public Info(String source, int line, int column)
+ {
+ this.templateName = source;
+ this.line = line;
+ this.column = column;
+ }
+
+ public Info(Node node)
+ {
+ this(node.getTemplateName(), node.getLine(), node.getColumn());
+ }
+
+ /**
+ * Force callers to set the location information.
+ */
+ private Info()
+ {
+ }
+
+ /**
+ * @return The template name.
+ */
+ public String getTemplateName()
+ {
+ return templateName;
+ }
+
+ /**
+ * @return The line number.
+ */
+ public int getLine()
+ {
+ return line;
+ }
+
+ /**
+ * @return The column number.
+ */
+ public int getColumn()
+ {
+ return column;
+ }
+
+ /**
+ * Formats a textual representation of this object as <code>SOURCE
+ * [line X, column Y]</code>.
+ *
+ * @return String representing this object.
+ * @since 1.5
+ */
+ public String toString()
+ {
+ return StringUtils.formatFileString(getTemplateName(), getLine(), getColumn());
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionCacheData.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionCacheData.java
new file mode 100644
index 00000000..271ad47a
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionCacheData.java
@@ -0,0 +1,43 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.
+ */
+
+/**
+ * Holds information for node-local context data introspection
+ * information.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class IntrospectionCacheData
+{
+ /**
+ * Object to pair with class - currently either a Method or
+ * AbstractExecutor. It can be used in any way the using node
+ * wishes.
+ */
+ public Object thingy;
+
+ /**
+ * Class of context data object associated with the introspection
+ * information
+ */
+ public Class<?> contextData;
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
new file mode 100644
index 00000000..c3a2979b
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectionUtils.java
@@ -0,0 +1,336 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.reflect.TypeUtils;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author Nathan Bubna
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: IntrospectionUtils.java 476785 2006-11-19 10:06:21Z henning $
+ * @since 1.6
+ */
+public class IntrospectionUtils
+{
+ /**
+ * boxing helper maps for standard types
+ */
+ static Map<Class<?>, Class<?>> boxingMap, unboxingMap;
+
+ static
+ {
+ boxingMap = new HashMap<>();
+ boxingMap.put(Boolean.TYPE, Boolean.class);
+ boxingMap.put(Character.TYPE, Character.class);
+ boxingMap.put(Byte.TYPE, Byte.class);
+ boxingMap.put(Short.TYPE, Short.class);
+ boxingMap.put(Integer.TYPE, Integer.class);
+ boxingMap.put(Long.TYPE, Long.class);
+ boxingMap.put(Float.TYPE, Float.class);
+ boxingMap.put(Double.TYPE, Double.class);
+
+ unboxingMap = new HashMap<>();
+ for (Map.Entry<Class<?>, Class<?>> entry : boxingMap.entrySet())
+ {
+ unboxingMap.put(entry.getValue(), entry.getKey());
+ }
+ }
+
+ /**
+ * returns boxed type (or input type if not a primitive type)
+ * @param clazz input class
+ * @return boxed class
+ */
+ public static Class<?> getBoxedClass(Class clazz)
+ {
+ Class<?> boxed = boxingMap.get(clazz);
+ return boxed == null ? clazz : boxed;
+ }
+
+ /**
+ * returns unboxed type (or input type if not successful)
+ * @param clazz input class
+ * @return unboxed class
+ */
+ public static Class<?> getUnboxedClass(Class clazz)
+ {
+ Class<?> unboxed = unboxingMap.get(clazz);
+ return unboxed == null ? clazz : unboxed;
+ }
+
+ /**
+ * returns the Class corresponding to a Type, if possible
+ * @param type the input Type
+ * @return found Class, if any
+ */
+ public static Class<?> getTypeClass(Type type)
+ {
+ if (type == null)
+ {
+ return null;
+ }
+ if (type instanceof Class<?>)
+ {
+ return (Class<?>)type;
+ }
+ else if (type instanceof ParameterizedType)
+ {
+ return (Class<?>)((ParameterizedType)type).getRawType();
+ }
+ else if (type instanceof GenericArrayType)
+ {
+ Type componentType = ((GenericArrayType)type).getGenericComponentType();
+ Class<?> componentClass = getTypeClass(componentType);
+ if (componentClass != null)
+ {
+ return Array.newInstance(componentClass, 0).getClass();
+ }
+ }
+ else if (type instanceof TypeVariable)
+ {
+ Type[] bounds = TypeUtils.getImplicitBounds((TypeVariable)type);
+ if (bounds.length == 1) return getTypeClass(bounds[0]);
+ }
+ else if (type instanceof WildcardType)
+ {
+ Type[] bounds = TypeUtils.getImplicitUpperBounds((WildcardType)type);
+ if (bounds.length == 1) return getTypeClass(bounds[0]);
+ }
+ return null;
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, treating object types of primitive
+ * types as if they were primitive types (that is, a Boolean actual
+ * parameter type matches boolean primitive formal type). This behavior
+ * is because this method is used to determine applicable methods for
+ * an actual parameter list, and primitive types are represented by
+ * their object duals in reflective method calls.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return true if either formal type is assignable from actual type,
+ * or formal is a primitive type and actual is its corresponding object
+ * type or an object type of a primitive type that can be converted to
+ * the formal type.
+ */
+ public static boolean isMethodInvocationConvertible(Type formal,
+ Class<?> actual,
+ boolean possibleVarArg)
+ {
+ Class<?> formalClass = getTypeClass(formal);
+ if (formalClass != null)
+ {
+ /* if it's a null, it means the arg was null */
+ if (actual == null)
+ {
+ return !formalClass.isPrimitive();
+ }
+
+ /* Check for identity or widening reference conversion */
+ if (formalClass.isAssignableFrom(actual))
+ {
+ return true;
+ }
+
+ /* 2.0: Since MethodMap's comparison functions now use this method with potentially reversed arguments order,
+ * actual can be a primitive type. */
+
+ /* Check for boxing */
+ if (!formalClass.isPrimitive() && actual.isPrimitive())
+ {
+ Class<?> boxed = boxingMap.get(actual);
+ if (boxed != null && (boxed == formalClass || formalClass.isAssignableFrom(boxed))) return true;
+ }
+
+ if (formalClass.isPrimitive())
+ {
+ if (actual.isPrimitive())
+ {
+ /* check for widening primitive conversion */
+ if (formalClass == Short.TYPE && actual == Byte.TYPE)
+ return true;
+ if (formalClass == Integer.TYPE && (
+ actual == Byte.TYPE || actual == Short.TYPE))
+ return true;
+ if (formalClass == Long.TYPE && (
+ actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE))
+ return true;
+ if (formalClass == Float.TYPE && (
+ actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
+ actual == Long.TYPE))
+ return true;
+ if (formalClass == Double.TYPE && (
+ actual == Byte.TYPE || actual == Short.TYPE || actual == Integer.TYPE ||
+ actual == Long.TYPE || actual == Float.TYPE))
+ return true;
+ } else
+ {
+ /* Check for unboxing with widening primitive conversion. */
+ if (formalClass == Boolean.TYPE && actual == Boolean.class)
+ return true;
+ if (formalClass == Character.TYPE && actual == Character.class)
+ return true;
+ if (formalClass == Byte.TYPE && actual == Byte.class)
+ return true;
+ if (formalClass == Short.TYPE && (actual == Short.class || actual == Byte.class))
+ return true;
+ if (formalClass == Integer.TYPE && (actual == Integer.class || actual == Short.class ||
+ actual == Byte.class))
+ return true;
+ if (formalClass == Long.TYPE && (actual == Long.class || actual == Integer.class ||
+ actual == Short.class || actual == Byte.class))
+ return true;
+ if (formalClass == Float.TYPE && (actual == Float.class || actual == Long.class ||
+ actual == Integer.class || actual == Short.class || actual == Byte.class))
+ return true;
+ if (formalClass == Double.TYPE && (actual == Double.class || actual == Float.class ||
+ actual == Long.class || actual == Integer.class || actual == Short.class ||
+ actual == Byte.class))
+ return true;
+ }
+ }
+
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formalClass.isArray())
+ {
+ if (actual.isArray())
+ {
+ actual = actual.getComponentType();
+ }
+ return isMethodInvocationConvertible(formalClass.getComponentType(),
+ actual, false);
+ }
+ return false;
+ }
+ else
+ {
+ // no distinction between strict and implicit, not a big deal in this case
+ if (TypeUtils.isAssignable(actual, formal))
+ {
+ return true;
+ }
+ return possibleVarArg && TypeUtils.isArrayType(formal) &&
+ TypeUtils.isAssignable(actual, TypeUtils.getArrayComponentType(formal));
+ }
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, without matching object and primitive
+ * types. This method is used to determine the more specific type when
+ * comparing signatures of methods.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @param possibleVarArg whether or not we're dealing with the last parameter
+ * in the method declaration
+ * @return true if either formal type is assignable from actual type,
+ * or formal and actual are both primitive types and actual can be
+ * subject to widening conversion to formal.
+ */
+ public static boolean isStrictMethodInvocationConvertible(Type formal,
+ Class<?> actual,
+ boolean possibleVarArg)
+ {
+ Class<?> formalClass = getTypeClass(formal);
+ if (formalClass != null)
+ {
+ /* Check for nullity */
+ if (actual == null)
+ {
+ return !formalClass.isPrimitive();
+ }
+
+ /* Check for identity or widening reference conversion */
+ if (formalClass.isAssignableFrom(actual))
+ {
+ return true;
+ }
+
+ /* Check for widening primitive conversion. */
+ if (formalClass.isPrimitive())
+ {
+ if (formal == Short.TYPE && (actual == Byte.TYPE))
+ return true;
+ if (formal == Integer.TYPE &&
+ (actual == Short.TYPE || actual == Byte.TYPE))
+ return true;
+ if (formal == Long.TYPE &&
+ (actual == Integer.TYPE || actual == Short.TYPE ||
+ actual == Byte.TYPE))
+ return true;
+ if (formal == Float.TYPE &&
+ (actual == Long.TYPE || actual == Integer.TYPE ||
+ actual == Short.TYPE || actual == Byte.TYPE))
+ return true;
+ if (formal == Double.TYPE &&
+ (actual == Float.TYPE || actual == Long.TYPE ||
+ actual == Integer.TYPE || actual == Short.TYPE ||
+ actual == Byte.TYPE))
+ return true;
+ }
+
+ /* Check for vararg conversion. */
+ if (possibleVarArg && formalClass.isArray())
+ {
+ if (actual.isArray())
+ {
+ actual = actual.getComponentType();
+ }
+ return isStrictMethodInvocationConvertible(formalClass.getComponentType(),
+ actual, false);
+ }
+ return false;
+ }
+ else
+ {
+ // no distinction between strict and implicit, not a big deal in this case
+ if (TypeUtils.isAssignable(actual, formal))
+ {
+ return true;
+ }
+ return possibleVarArg && TypeUtils.isArrayType(formal) &&
+ TypeUtils.isAssignable(actual, TypeUtils.getArrayComponentType(formal));
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
new file mode 100644
index 00000000..29756fbf
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Introspector.java
@@ -0,0 +1,132 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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;
+
+/**
+ * This basic function of this class is to return a Method
+ * object for a particular class given the name of a method
+ * and the parameters to the method in the form of an Object[]
+ *
+ * The first time the Introspector sees a
+ * class it creates a class method map for the
+ * class in question. Basically the class method map
+ * is a Hastable where Method objects are keyed by a
+ * concatenation of the method name and the names of
+ * classes that make up the parameters.
+ *
+ * For example, a method with the following signature:
+ *
+ * public void method(String a, StringBuffer b)
+ *
+ * would be mapped by the key:
+ *
+ * "method" + "java.lang.String" + "java.lang.StringBuffer"
+ *
+ * This mapping is performed for all the methods in a class
+ * and stored for
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+public class Introspector extends IntrospectorBase
+{
+ /**
+ * @param log A Logger object to use for the introspector.
+ * @since 1.5
+ */
+ public Introspector(final Logger log)
+ {
+ this(log, null);
+ }
+
+ /**
+ * @param log A Logger object to use for the introspector.
+ * @param conversionHandler conversion handler
+ * @since 2.0
+ */
+ public Introspector(final Logger log, TypeConversionHandler conversionHandler)
+ {
+ super(log, conversionHandler);
+ }
+
+ /**
+ * Gets the method defined by <code>name</code> and
+ * <code>params</code> for the Class <code>c</code>.
+ *
+ * @param c Class in which the method search is taking place
+ * @param name Name of the method being searched for
+ * @param params An array of Objects (not Classes) that describe the
+ * the parameters
+ *
+ * @return The desired Method object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ */
+ @Override
+ public Method getMethod(final Class<?> c, final String name, final Object[] params)
+ throws IllegalArgumentException
+ {
+ try
+ {
+ return super.getMethod(c, name, params);
+ }
+ catch(MethodMap.AmbiguousException ae)
+ {
+ /*
+ * whoops. Ambiguous. Make a nice log message and return null...
+ */
+
+ StringBuilder msg = new StringBuilder("Introspection Error: Ambiguous method invocation ")
+ .append(name)
+ .append("(");
+
+ for (int i = 0; i < params.length; i++)
+ {
+ if (i > 0)
+ {
+ msg.append(", ");
+ }
+
+ if (params[i] == null)
+ {
+ msg.append("null");
+ }
+ else
+ {
+ msg.append(params[i].getClass().getName());
+ }
+ }
+
+ msg.append(") for class ")
+ .append(c);
+
+ log.debug(msg.toString());
+ }
+
+ return null;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
new file mode 100644
index 00000000..bce0d4c9
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorBase.java
@@ -0,0 +1,138 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.Validate;
+import org.slf4j.Logger;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Lookup a a Method object for a particular class given the name of a method
+ * and its parameters.
+ *
+ * The first time the Introspector sees a
+ * class it creates a class method map for the
+ * class in question. Basically the class method map
+ * is a Hashtable where Method objects are keyed by a
+ * concatenation of the method name and the names of
+ * classes that make up the parameters.
+ *
+ * For example, a method with the following signature:
+ *
+ * public void method(String a, StringBuffer b)
+ *
+ * would be mapped by the key:
+ *
+ * "method" + "java.lang.String" + "java.lang.StringBuffer"
+ *
+ * This mapping is performed for all the methods in a class
+ * and stored for.
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:paulo.gaspar@krankikom.de">Paulo Gaspar</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
+ * @version $Id$
+ */
+public abstract class IntrospectorBase
+{
+ /** Class logger */
+ protected final Logger log;
+
+ /** The Introspector Cache */
+ private final IntrospectorCache introspectorCache;
+
+ /**
+ * C'tor.
+ * @param log logger
+ * @param conversionHandler conversion handler
+ */
+ protected IntrospectorBase(final Logger log, final TypeConversionHandler conversionHandler)
+ {
+ this.log = log;
+ introspectorCache = new IntrospectorCache(log, conversionHandler);
+ }
+
+ /**
+ * Gets the method defined by <code>name</code> and
+ * <code>params</code> for the Class <code>c</code>.
+ *
+ * @param c Class in which the method search is taking place
+ * @param name Name of the method being searched for
+ * @param params An array of Objects (not Classes) that describe the
+ * the parameters
+ *
+ * @return The desired Method object.
+ * @throws NullPointerException When the parameters passed in can not be used for introspection because null.
+ * @throws MethodMap.AmbiguousException When the method map contains more than one match for the requested signature.
+ */
+ public Method getMethod(final Class<?> c, final String name, final Object[] params)
+ throws MethodMap.AmbiguousException
+ {
+ IntrospectorCache ic = getIntrospectorCache();
+
+ ClassMap classMap = ic.get(Validate.notNull(c, "class object is null!"));
+ if (classMap == null)
+ {
+ classMap = ic.put(c);
+ }
+
+ return classMap.findMethod(name, Validate.notNull(params, "params object is null!"));
+ }
+
+ /**
+ * Gets the field defined by <code>name</code>.
+ *
+ * @param c Class in which the method search is taking place
+ * @param name Name of the field being searched for
+ *
+ * @return The desired Field object.
+ * @throws IllegalArgumentException When the parameters passed in can not be used for introspection.
+ */
+ public Field getField(final Class<?> c, final String name)
+ throws IllegalArgumentException
+ {
+ IntrospectorCache ic = getIntrospectorCache();
+
+ ClassFieldMap classFieldMap = ic.getFieldMap(Validate.notNull(c, "class object is null!"));
+ if (classFieldMap == null)
+ {
+ ic.put(c);
+ classFieldMap = ic.getFieldMap(c);
+ }
+
+ return classFieldMap.findField(name);
+ }
+
+ /**
+ * Return the internal IntrospectorCache object.
+ *
+ * @return The internal IntrospectorCache object.
+ * @since 1.5
+ */
+ protected IntrospectorCache getIntrospectorCache()
+ {
+ return introspectorCache;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
new file mode 100644
index 00000000..daa463ef
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/IntrospectorCache.java
@@ -0,0 +1,179 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.Validate;
+
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * This is the internal introspector cache implementation.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public final class IntrospectorCache
+{
+ /**
+ * define a public string so that it can be looked for if interested
+ */
+ public final static String CACHEDUMP_MSG =
+ "IntrospectorCache detected classloader change. Dumping cache.";
+
+ /** Class logger */
+ private final Logger log;
+
+ /**
+ * Holds the method maps for the classes we know about. Map: Class --&gt; ClassMap object.
+ */
+ private final Map<Class<?>, ClassMap> classMapCache = new HashMap<>();
+
+ /**
+ * Holds the field maps for the classes we know about. Map: Class --&gt; ClassFieldMap object.
+ */
+ private final Map<Class<?>, ClassFieldMap> classFieldMapCache = new HashMap<>();
+
+ /**
+ * Keep the names of the classes in another map. This is needed for a multi-classloader environment where it is possible
+ * to have Class 'Foo' loaded by a classloader and then get asked to introspect on 'Foo' from another class loader. While these
+ * two Class objects have the same name, a <code>classMethodMaps.get(Foo.class)</code> will return null. For that case, we
+ * keep a set of class names to recognize this case.
+ */
+ private final Set<String> classNameCache = new HashSet<>();
+
+ /**
+ * Conversion handler
+ */
+ private final TypeConversionHandler conversionHandler;
+
+ /**
+ * C'tor
+ * @param log logger.
+ * @param conversionHandler conversion handler
+ */
+ public IntrospectorCache(final Logger log, final TypeConversionHandler conversionHandler)
+ {
+ this.log = log;
+ this.conversionHandler = conversionHandler;
+ }
+
+ /**
+ * Clears the internal cache.
+ */
+ public void clear()
+ {
+ synchronized (classMapCache)
+ {
+ classMapCache.clear();
+ classFieldMapCache.clear();
+ classNameCache.clear();
+ log.debug(CACHEDUMP_MSG);
+ }
+ }
+
+ /**
+ * Lookup a given Class object in the cache. If it does not exist,
+ * check whether this is due to a class change and purge the caches
+ * eventually.
+ *
+ * @param c The class to look up.
+ * @return A ClassMap object or null if it does not exist in the cache.
+ */
+ public ClassMap get(final Class<?> c)
+ {
+ ClassMap classMap = classMapCache.get(Validate.notNull(c));
+ if (classMap == null)
+ {
+ /*
+ * check to see if we have it by name.
+ * if so, then we have an object with the same
+ * name but loaded through a different class loader.
+ * In that case, we will just dump the cache to be sure.
+ */
+ synchronized (classMapCache)
+ {
+ if (classNameCache.contains(c.getName()))
+ {
+ clear();
+ }
+ }
+ }
+ return classMap;
+ }
+
+ /**
+ * Lookup a given Class object in the cache. If it does not exist,
+ * check whether this is due to a class change and purge the caches
+ * eventually.
+ *
+ * @param c The class to look up.
+ * @return A ClassFieldMap object or null if it does not exist in the cache.
+ */
+ public ClassFieldMap getFieldMap(final Class<?> c)
+ {
+ ClassFieldMap classFieldMap = classFieldMapCache.get(Validate.notNull(c));
+ if (classFieldMap == null)
+ {
+ /*
+ * check to see if we have it by name.
+ * if so, then we have an object with the same
+ * name but loaded through a different class loader.
+ * In that case, we will just dump the cache to be sure.
+ */
+ synchronized (classMapCache)
+ {
+ if (classNameCache.contains(c.getName()))
+ {
+ clear();
+ }
+ }
+ }
+ return classFieldMap;
+ }
+
+ /**
+ * Creates a class map for specific class and registers it in the
+ * cache. Also adds the qualified name to the name-&gt;class map
+ * for later Classloader change detection.
+ *
+ * @param c The class for which the class map gets generated.
+ * @return A ClassMap object.
+ */
+ public ClassMap put(final Class<?> c)
+ {
+ final ClassMap classMap = new ClassMap(c, log, conversionHandler);
+ final ClassFieldMap classFieldMap = new ClassFieldMap(c, log);
+ synchronized (classMapCache)
+ {
+ classMapCache.put(c, classMap);
+ classFieldMapCache.put(c, classFieldMap);
+ classNameCache.add(c.getName());
+ }
+ return classMap;
+ }
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/LinkingUberspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/LinkingUberspector.java
new file mode 100644
index 00000000..8b219275
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/LinkingUberspector.java
@@ -0,0 +1,124 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.util.Iterator;
+
+/**
+ * <p>
+ * When the introspector.uberspect.class configuration property contains several
+ * uberspector class names, it means those uberspectors will be chained. When an
+ * uberspector in the list other than the leftmost does not implement ChainableUberspector,
+ * then this utility class is used to provide a basic default chaining where the
+ * first non-null result is kept for each introspection call.
+ * </p>
+ *
+ * @since 1.6
+ * @see ChainableUberspector
+ * @version $Id: LinkingUberspector.java 10959 2008-07-01 00:12:29Z sdumitriu $
+ */
+public class LinkingUberspector extends AbstractChainableUberspector
+{
+ private Uberspect leftUberspect;
+ private Uberspect rightUberspect;
+
+ /**
+ * Constructor that takes the two uberspectors to link
+ * @param left left uberspector
+ * @param right right uberspector
+ */
+ public LinkingUberspector(Uberspect left,Uberspect right) {
+ leftUberspect = left;
+ rightUberspect = right;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Init both wrapped uberspectors
+ * </p>
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#init()
+ */
+ //@Override
+ @Override
+ public void init()
+ {
+ leftUberspect.init();
+ rightUberspect.init();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getIterator(java.lang.Object,
+ * org.apache.velocity.util.introspection.Info)
+ */
+ //@SuppressWarnings("unchecked")
+ //@Override
+ @Override
+ public Iterator getIterator(Object obj, Info i)
+ {
+ Iterator it = leftUberspect.getIterator(obj,i);
+ return it != null ? it : rightUberspect.getIterator(obj,i);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getMethod(java.lang.Object, java.lang.String,
+ * java.lang.Object[], org.apache.velocity.util.introspection.Info)
+ */
+ //@Override
+ @Override
+ public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
+ {
+ VelMethod method = leftUberspect.getMethod(obj,methodName,args,i);
+ return method != null ? method : rightUberspect.getMethod(obj,methodName,args,i);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getPropertyGet(java.lang.Object, java.lang.String,
+ * org.apache.velocity.util.introspection.Info)
+ */
+ //@Override
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
+ {
+ VelPropertyGet getter = leftUberspect.getPropertyGet(obj,identifier,i);
+ return getter != null ? getter : rightUberspect.getPropertyGet(obj,identifier,i);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.apache.velocity.util.introspection.Uberspect#getPropertySet(java.lang.Object, java.lang.String,
+ * java.lang.Object, org.apache.velocity.util.introspection.Info)
+ */
+ //@Override
+ @Override
+ public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i)
+ {
+ VelPropertySet setter = leftUberspect.getPropertySet(obj,identifier,arg,i);
+ return setter != null ? setter : rightUberspect.getPropertySet(obj,identifier,arg,i);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
new file mode 100644
index 00000000..1d0408bb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/MethodMap.java
@@ -0,0 +1,691 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.reflect.TypeUtils;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id$
+ */
+public class MethodMap
+{
+ /* Constants for specificity */
+ private static final int INCOMPARABLE = 0;
+ private static final int MORE_SPECIFIC = 1;
+ private static final int EQUIVALENT = 2;
+ private static final int LESS_SPECIFIC = 3;
+
+ /* Constants for applicability */
+ private static final int NOT_CONVERTIBLE = 0;
+ private static final int EXPLICITLY_CONVERTIBLE = 1;
+ private static final int IMPLCITLY_CONVERTIBLE = 2;
+ private static final int STRICTLY_CONVERTIBLE = 3;
+
+ TypeConversionHandler conversionHandler;
+
+ /**
+ * Default constructor
+ */
+ public MethodMap()
+ {
+ this(null);
+ }
+
+ /**
+ * Constructor with provided conversion handler
+ * @param conversionHandler conversion handler
+ * @since 2.0
+ */
+ public MethodMap(TypeConversionHandler conversionHandler)
+ {
+ this.conversionHandler = conversionHandler;
+ }
+
+ /**
+ * Keep track of all methods with the same name.
+ */
+ Map<String, List<Method>> methodByNameMap = new ConcurrentHashMap<>();
+
+ /**
+ * Add a method to a list of methods by name.
+ * For a particular class we are keeping track
+ * of all the methods with the same name.
+ * @param method
+ */
+ public void add(Method method)
+ {
+ String methodName = method.getName();
+
+ List<Method> l = get( methodName );
+
+ if ( l == null)
+ {
+ l = new ArrayList<>();
+ methodByNameMap.put(methodName, l);
+ }
+
+ l.add(method);
+ }
+
+ /**
+ * Return a list of methods with the same name.
+ *
+ * @param key
+ * @return List list of methods
+ */
+ public List<Method> get(String key)
+ {
+ return methodByNameMap.get(key);
+ }
+
+ /**
+ * <p>
+ * Find a method. Attempts to find the
+ * most specific applicable method using the
+ * algorithm described in the JLS section
+ * 15.12.2 (with the exception that it can't
+ * distinguish a primitive type argument from
+ * an object type argument, since in reflection
+ * primitive type arguments are represented by
+ * their object counterparts, so for an argument of
+ * type (say) java.lang.Integer, it will not be able
+ * to decide between a method that takes int and a
+ * method that takes java.lang.Integer as a parameter.
+ * </p>
+ *
+ * <p>
+ * This turns out to be a relatively rare case
+ * where this is needed - however, functionality
+ * like this is needed.
+ * </p>
+ *
+ * @param methodName name of method
+ * @param args the actual arguments with which the method is called
+ * @return the most specific applicable method, or null if no
+ * method is applicable.
+ * @throws AmbiguousException if there is more than one maximally
+ * specific applicable method
+ */
+ public Method find(String methodName, Object[] args)
+ throws AmbiguousException
+ {
+ List<Method> methodList = get(methodName);
+
+ if (methodList == null)
+ {
+ return null;
+ }
+
+ int l = args.length;
+ Class<?>[] classes = new Class[l];
+
+ for(int i = 0; i < l; ++i)
+ {
+ Object arg = args[i];
+
+ /*
+ * if we are careful down below, a null argument goes in there
+ * so we can know that the null was passed to the method
+ */
+ classes[i] =
+ arg == null ? null : arg.getClass();
+ }
+
+ return getBestMatch(methodList, classes);
+ }
+
+ private class Match
+ {
+ /* target method */
+ Method method;
+
+ /* cache arguments classes array */
+ Type[] methodTypes;
+
+ /* specificity: how does the best match compare to provided arguments
+ * one one LESS_SPECIFIC, MORE_SPECIFIC or INCOMPARABLE */
+ int specificity;
+
+ /* applicability which conversion level is needed against provided arguments
+ * one of STRICTLY_CONVERTIBLE, IMPLICITLY_CONVERTIBLE and EXPLICITLY_CONVERTIBLE_ */
+ int applicability;
+
+ /* whether the method has varrags */
+ boolean varargs;
+
+ Match(Method method, int applicability, Class<?>[] unboxedArgs)
+ {
+ this.method = method;
+ this.applicability = applicability;
+ this.methodTypes = method.getGenericParameterTypes();
+ this.specificity = compare(methodTypes, unboxedArgs);
+ this.varargs = methodTypes.length > 0 && TypeUtils.isArrayType(methodTypes[methodTypes.length - 1]);
+ }
+ }
+
+ private static boolean onlyNullOrObjects(Class<?>[] args)
+ {
+ for (Class<?> cls : args)
+ {
+ if (cls != null && cls != Object.class) return false;
+ }
+ return args.length > 0;
+ }
+
+ private Method getBestMatch(List<Method> methods, Class<?>[] args)
+ {
+ List<Match> bestMatches = new LinkedList<>();
+ Class<?>[] unboxedArgs = new Class<?>[args.length];
+ for (int i = 0; i < args.length; ++i)
+ {
+ unboxedArgs[i] = IntrospectionUtils.getUnboxedClass(args[i]);
+ }
+ for (Method method : methods)
+ {
+ int applicability = getApplicability(method, unboxedArgs);
+ if (applicability > NOT_CONVERTIBLE)
+ {
+ Match match = new Match(method, applicability, unboxedArgs);
+ if (bestMatches.size() == 0)
+ {
+ bestMatches.add(match);
+ }
+ else
+ {
+ /* filter existing matches */
+ boolean keepMethod = true;
+ for (ListIterator<Match> it = bestMatches.listIterator(); keepMethod && it.hasNext();)
+ {
+ Match best = it.next();
+ /* do not retain match if it's more specific than (or incomparable to) provided (unboxed) arguments
+ * while one of the best matches is less specific
+ */
+ if (best.specificity == LESS_SPECIFIC && match.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */
+ {
+ keepMethod = false;
+ }
+ /* drop considered best match if match is less specific than (unboxed) provided args while
+ * the considered best match is more specific or incomparable
+ */
+ else if (match.specificity == LESS_SPECIFIC && best.specificity < EQUIVALENT) /* != LESS_SPECIFIC && != EQUIVALENT */
+ {
+ it.remove();
+ }
+ /* compare applicability */
+ else if (best.applicability > match.applicability)
+ {
+ keepMethod = false;
+ }
+ else if (best.applicability < match.applicability)
+ {
+ it.remove();
+ }
+ /* compare methods between them */
+ else
+ {
+ /* but only if some provided args are non null and not Object */
+ if (onlyNullOrObjects(args))
+ {
+ /* in this case we only favor non-varrags methods */
+ if (match.varargs != best.varargs)
+ {
+ if (match.varargs)
+ {
+ keepMethod = false;
+ }
+ else if (best.varargs)
+ {
+ it.remove();
+ }
+ }
+ }
+ else
+ {
+ switch (compare(match.methodTypes, best.methodTypes))
+ {
+ case LESS_SPECIFIC:
+ keepMethod = false;
+ break;
+ case MORE_SPECIFIC:
+ it.remove();
+ break;
+ case INCOMPARABLE:
+ /* Java compiler favors non-vararg methods. Let's do the same. */
+ if (match.varargs != best.varargs)
+ {
+ if (match.varargs)
+ {
+ keepMethod = false;
+ }
+ else if (best.varargs)
+ {
+ it.remove();
+ }
+ }
+ /* otherwise it's an equivalent match */
+ break;
+ case EQUIVALENT:
+ break;
+ }
+ }
+ }
+ }
+ if (keepMethod)
+ {
+ bestMatches.add(match);
+ }
+ }
+ }
+ }
+
+ switch (bestMatches.size())
+ {
+ case 0: return null;
+ case 1: return bestMatches.get(0).method;
+ default: throw new AmbiguousException();
+ }
+ }
+
+ /**
+ * Simple distinguishable exception, used when
+ * we run across ambiguous overloading. Caught
+ * by the introspector.
+ */
+ public static class AmbiguousException extends RuntimeException
+ {
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = -2314636505414551663L;
+ }
+
+ /**
+ * Determines which method signature (represented by a class array) is more
+ * specific. This defines a partial ordering on the method signatures.
+ * @param t1 first signature to compare
+ * @param t2 second signature to compare
+ * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
+ * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
+ */
+ private int compare(Type[] t1, Type[] t2)
+ {
+ boolean t1IsVararag = false;
+ boolean t2IsVararag = false;
+ boolean fixedLengths = false;
+
+ // compare lengths to handle comparisons where the size of the arrays
+ // doesn't match, but the methods are both applicable due to the fact
+ // that one is a varargs method
+ if (t1.length > t2.length)
+ {
+ int l2 = t2.length;
+ if (l2 == 0)
+ {
+ return MORE_SPECIFIC;
+ }
+ t2 = Arrays.copyOf(t2, t1.length);
+ Type itemType = TypeUtils.getArrayComponentType(t2[l2 - 1]);
+ /* if item class is null, then it implies the vaarg is #1
+ * (and receives an empty array)
+ */
+ if (itemType == null)
+ {
+ /* by construct, we have c1.length = l2 + 1 */
+ t1IsVararag = true;
+ t2[t1.length - 1] = null;
+ }
+ else
+ {
+ t2IsVararag = true;
+ for (int i = l2 - 1; i < t1.length; ++i)
+ {
+ /* also overwrite the vaargs itself */
+ t2[i] = itemType;
+ }
+ }
+ fixedLengths = true;
+ }
+ else if (t2.length > t1.length)
+ {
+ int l1 = t1.length;
+ if (l1 == 0)
+ {
+ return LESS_SPECIFIC;
+ }
+ t1 = Arrays.copyOf(t1, t2.length);
+ Type itemType = TypeUtils.getArrayComponentType(t1[l1 - 1]);
+ /* if item class is null, then it implies the vaarg is #2
+ * (and receives an empty array)
+ */
+ if (itemType == null)
+ {
+ /* by construct, we have c2.length = l1 + 1 */
+ t2IsVararag = true;
+ t1[t2.length - 1] = null;
+ }
+ else
+ {
+ t1IsVararag = true;
+ for (int i = l1 - 1; i < t2.length; ++i)
+ {
+ /* also overwrite the vaargs itself */
+ t1[i] = itemType;
+ }
+ }
+ fixedLengths = true;
+ }
+
+ /* ok, move on and compare those of equal lengths */
+ int fromC1toC2 = STRICTLY_CONVERTIBLE;
+ int fromC2toC1 = STRICTLY_CONVERTIBLE;
+ for(int i = 0; i < t1.length; ++i)
+ {
+ Class<?> c1 = t1[i] == null ? null : IntrospectionUtils.getTypeClass(t1[i]);
+ Class<?> c2 = t2[i] == null ? null : IntrospectionUtils.getTypeClass(t2[i]);
+ boolean last = !fixedLengths && (i == t1.length - 1);
+ if (t1[i] == null && t2[i] != null || t1[i] != null && t2[i] == null || !t1[i].equals(t2[i]))
+ {
+ if (t1[i] == null)
+ {
+ fromC2toC1 = NOT_CONVERTIBLE;
+ if (c2 != null && c2.isPrimitive())
+ {
+ fromC1toC2 = NOT_CONVERTIBLE;
+ }
+ }
+ else if (t2[i] == null)
+ {
+ fromC1toC2 = NOT_CONVERTIBLE;
+ if (c1 != null && c1.isPrimitive())
+ {
+ fromC2toC1 = NOT_CONVERTIBLE;
+ }
+ }
+ else
+ {
+ if (c1 != null)
+ {
+ switch (fromC1toC2)
+ {
+ case STRICTLY_CONVERTIBLE:
+ if (isStrictConvertible(t2[i], c1, last)) break;
+ fromC1toC2 = IMPLCITLY_CONVERTIBLE;
+ case IMPLCITLY_CONVERTIBLE:
+ if (isConvertible(t2[i], c1, last)) break;
+ fromC1toC2 = EXPLICITLY_CONVERTIBLE;
+ case EXPLICITLY_CONVERTIBLE:
+ if (isExplicitlyConvertible(t2[i], c1, last)) break;
+ fromC1toC2 = NOT_CONVERTIBLE;
+ }
+ }
+ else if (fromC1toC2 > NOT_CONVERTIBLE)
+ {
+ fromC1toC2 = TypeUtils.isAssignable(t1[i], t2[i]) ?
+ Math.min(fromC1toC2, IMPLCITLY_CONVERTIBLE) :
+ NOT_CONVERTIBLE;
+ }
+ if (c2 != null)
+ {
+ switch (fromC2toC1)
+ {
+ case STRICTLY_CONVERTIBLE:
+ if (isStrictConvertible(t1[i], c2, last)) break;
+ fromC2toC1 = IMPLCITLY_CONVERTIBLE;
+ case IMPLCITLY_CONVERTIBLE:
+ if (isConvertible(t1[i], c2, last)) break;
+ fromC2toC1 = EXPLICITLY_CONVERTIBLE;
+ case EXPLICITLY_CONVERTIBLE:
+ if (isExplicitlyConvertible(t1[i], c2, last)) break;
+ fromC2toC1 = NOT_CONVERTIBLE;
+ }
+ }
+ else if (fromC2toC1 > NOT_CONVERTIBLE)
+ {
+ fromC2toC1 = TypeUtils.isAssignable(t2[i], t1[i]) ?
+ Math.min(fromC2toC1, IMPLCITLY_CONVERTIBLE) :
+ NOT_CONVERTIBLE;
+ }
+ }
+ }
+ }
+
+ if (fromC1toC2 == NOT_CONVERTIBLE && fromC2toC1 == NOT_CONVERTIBLE)
+ {
+ /*
+ * Incomparable due to cross-assignable arguments (i.e.
+ * foo(String, Foo) vs. foo(Foo, String))
+ */
+ return INCOMPARABLE;
+ }
+
+ if (fromC1toC2 > fromC2toC1)
+ {
+ return MORE_SPECIFIC;
+ }
+ else if (fromC2toC1 > fromC1toC2)
+ {
+ return LESS_SPECIFIC;
+ }
+ else
+ {
+ /*
+ * If one method accepts varargs and the other does not,
+ * call the non-vararg one more specific.
+ */
+ boolean last1Array = t1IsVararag || !fixedLengths && TypeUtils.isArrayType (t1[t1.length - 1]);
+ boolean last2Array = t2IsVararag || !fixedLengths && TypeUtils.isArrayType(t2[t2.length - 1]);
+ if (last1Array && !last2Array)
+ {
+ return LESS_SPECIFIC;
+ }
+ if (!last1Array && last2Array)
+ {
+ return MORE_SPECIFIC;
+ }
+ }
+ return EQUIVALENT;
+ }
+
+ /**
+ * Returns the applicability of the supplied method against actual argument types.
+ *
+ * @param method method that will be called
+ * @param classes arguments to method
+ * @return the level of applicability:
+ * 0 = not applicable
+ * 1 = explicitly applicable (i.e. using stock or custom conversion handlers)
+ * 2 = implicitly applicable (i.e. using JAva implicit boxing/unboxing and primitive types widening)
+ * 3 = strictly applicable
+ */
+ private int getApplicability(Method method, Class<?>[] classes)
+ {
+ Type[] methodArgs = method.getGenericParameterTypes();
+ int ret = STRICTLY_CONVERTIBLE;
+ if (methodArgs.length > classes.length)
+ {
+ // if there's just one more methodArg than class arg
+ // and the last methodArg is an array, then treat it as a vararg
+ if (methodArgs.length == classes.length + 1 && TypeUtils.isArrayType(methodArgs[methodArgs.length - 1]))
+ {
+ // all the args preceding the vararg must match
+ for (int i = 0; i < classes.length; i++)
+ {
+ if (!isStrictConvertible(methodArgs[i], classes[i], false))
+ {
+ if (isConvertible(methodArgs[i], classes[i], false))
+ {
+ ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+ }
+ else if (isExplicitlyConvertible(methodArgs[i], classes[i], false))
+ {
+ ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+ }
+ else
+ {
+ return NOT_CONVERTIBLE;
+ }
+ }
+ }
+ return ret;
+ }
+ else
+ {
+ return NOT_CONVERTIBLE;
+ }
+ }
+ else if (methodArgs.length == classes.length)
+ {
+ // this will properly match when the last methodArg
+ // is an array/varargs and the last class is the type of array
+ // (e.g. String when the method is expecting String...)
+ for(int i = 0; i < classes.length; ++i)
+ {
+ boolean possibleVararg = i == classes.length - 1 && TypeUtils.isArrayType(methodArgs[i]);
+ if (!isStrictConvertible(methodArgs[i], classes[i], possibleVararg))
+ {
+ if (isConvertible(methodArgs[i], classes[i], possibleVararg))
+ {
+ ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+ }
+ else if (isExplicitlyConvertible(methodArgs[i], classes[i], possibleVararg))
+ {
+ ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+ }
+ else
+ {
+ return NOT_CONVERTIBLE;
+ }
+ }
+ }
+ return ret;
+ }
+ else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
+ {
+ // check that the last methodArg is an array
+ Type lastarg = methodArgs[methodArgs.length - 1];
+ if (!TypeUtils.isArrayType(lastarg))
+ {
+ return NOT_CONVERTIBLE;
+ }
+
+ // check that they all match up to the last method arg component type
+ for (int i = 0; i < methodArgs.length - 1; ++i)
+ {
+ if (!isStrictConvertible(methodArgs[i], classes[i], false))
+ {
+ if (isConvertible(methodArgs[i], classes[i], false))
+ {
+ ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+ }
+ else if (isExplicitlyConvertible(methodArgs[i], classes[i], false))
+ {
+ ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+ }
+ else
+ {
+ return NOT_CONVERTIBLE;
+ }
+ }
+ }
+
+ // check that all remaining arguments are convertible to the vararg type
+ Type vararg = TypeUtils.getArrayComponentType(lastarg);
+ for (int i = methodArgs.length - 1; i < classes.length; ++i)
+ {
+ if (!isStrictConvertible(vararg, classes[i], false))
+ {
+ if (isConvertible(vararg, classes[i], false))
+ {
+ ret = Math.min(ret, IMPLCITLY_CONVERTIBLE);
+ }
+ else if (isExplicitlyConvertible(vararg, classes[i], false))
+ {
+ ret = Math.min(ret, EXPLICITLY_CONVERTIBLE);
+ }
+ else
+ {
+ return NOT_CONVERTIBLE;
+ }
+ }
+ }
+ return ret;
+ }
+ return NOT_CONVERTIBLE;
+ }
+
+ /**
+ * Returns true if <code>actual</code> is convertible to <code>formal</code> by implicit Java method call conversions
+ *
+ * @param formal
+ * @param actual
+ * @param possibleVarArg
+ * @return convertible
+ */
+ private boolean isConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
+ {
+ return IntrospectionUtils.
+ isMethodInvocationConvertible(formal, actual, possibleVarArg);
+ }
+
+ /**
+ * Returns true if <code>actual</code> is strictly convertible to <code>formal</code> (aka without implicit
+ * boxing/unboxing)
+ *
+ * @param formal
+ * @param actual
+ * @param possibleVarArg
+ * @return convertible
+ */
+ private static boolean isStrictConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
+ {
+ return IntrospectionUtils.
+ isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
+ }
+
+ /**
+ * Returns true if <code>actual</code> is convertible to <code>formal</code> using an explicit converter
+ *
+ * @param formal
+ * @param actual
+ * @param possibleVarArg
+ * @return
+ */
+ private boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
+ {
+ return conversionHandler != null && conversionHandler.isExplicitlyConvertible(formal, actual, possibleVarArg);
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorControl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorControl.java
new file mode 100644
index 00000000..6355dfa0
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorControl.java
@@ -0,0 +1,42 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 to determine which methods are allowed to be executed.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public interface SecureIntrospectorControl
+{
+
+ /**
+ * Determine which methods and classes to prevent from executing.
+ *
+ * @param clazz Class for which method is being called
+ * @param method method being called. This may be null in the case of a call to iterator, get, or set method
+ *
+ * @return true if method may be called on object
+ */
+ boolean checkObjectExecutePermission(Class<?> clazz, String method);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorImpl.java
new file mode 100644
index 00000000..c07534eb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureIntrospectorImpl.java
@@ -0,0 +1,166 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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;
+
+/**
+ * <p>Prevent "dangerous" classloader/reflection related calls. Use this
+ * introspector for situations in which template writers are numerous
+ * or untrusted. Specifically, this introspector prevents creation of
+ * arbitrary objects and prevents reflection on objects.
+ *
+ * <p>See documentation of checkObjectExecutePermission() for
+ * more information on specific classes and methods blocked.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class SecureIntrospectorImpl extends Introspector implements SecureIntrospectorControl
+{
+ private String[] badClasses;
+ private String[] badPackages;
+
+ public SecureIntrospectorImpl(String[] badClasses, String[] badPackages, Logger log)
+ {
+ super(log);
+ this.badClasses = badClasses;
+ this.badPackages = badPackages;
+ }
+
+ /**
+ * Get the Method object corresponding to the given class, name and parameters.
+ * Will check for appropriate execute permissions and return null if the method
+ * is not allowed to be executed.
+ *
+ * @param clazz Class on which method will be called
+ * @param methodName Name of method to be called
+ * @param params array of parameters to method
+ * @return Method object retrieved by Introspector
+ * @throws IllegalArgumentException The parameter passed in were incorrect.
+ */
+ @Override
+ public Method getMethod(Class<?> clazz, String methodName, Object[] params)
+ throws IllegalArgumentException
+ {
+ if (!checkObjectExecutePermission(clazz, methodName))
+ {
+ log.warn("Cannot retrieve method {} from object of class {} due to security restrictions."
+ , methodName, clazz.getName());
+ return null;
+ }
+ else
+ {
+ return super.getMethod(clazz, methodName, params);
+ }
+ }
+
+ /**
+ * Determine which methods and classes to prevent from executing. Always blocks
+ * methods wait() and notify(). Always allows methods on Number, Boolean, and String.
+ * Prohibits method calls on classes related to reflection and system operations.
+ * For the complete list, see the properties <code>introspector.restrict.classes</code>
+ * and <code>introspector.restrict.packages</code>.
+ *
+ * @param clazz Class on which method will be called
+ * @param methodName Name of method to be called
+ * @see org.apache.velocity.util.introspection.SecureIntrospectorControl#checkObjectExecutePermission(java.lang.Class, java.lang.String)
+ */
+ @Override
+ public boolean checkObjectExecutePermission(Class<?> clazz, String methodName)
+ {
+ /*
+ * check for wait and notify
+ */
+ if (methodName != null &&
+ (methodName.equals("wait") || methodName.equals("notify")) )
+ {
+ return false;
+ }
+
+ /*
+ * Always allow the most common classes - Number, Boolean and String
+ */
+ else if (Number.class.isAssignableFrom(clazz))
+ {
+ return true;
+ }
+ else if (Boolean.class.isAssignableFrom(clazz))
+ {
+ return true;
+ }
+ else if (String.class.isAssignableFrom(clazz))
+ {
+ return true;
+ }
+
+ /*
+ * Always allow Class.getName()
+ */
+ else if (Class.class.isAssignableFrom(clazz) &&
+ (methodName != null) && methodName.equals("getName"))
+ {
+ return true;
+ }
+
+ /*
+ * Always disallow ClassLoader, Thread and subclasses
+ */
+ if (ClassLoader.class.isAssignableFrom(clazz) ||
+ Thread.class.isAssignableFrom(clazz))
+ {
+ return false;
+ }
+
+ /*
+ * check the classname (minus any array info)
+ * whether it matches disallowed classes or packages
+ */
+ String className = clazz.getName();
+ if (className.startsWith("[L") && className.endsWith(";"))
+ {
+ className = className.substring(2, className.length() - 1);
+ }
+
+ int dotPos = className.lastIndexOf('.');
+ String packageName = (dotPos == -1) ? "" : className.substring(0, dotPos);
+
+ for (String badPackage : badPackages)
+ {
+ if (packageName.equals(badPackage))
+ {
+ return false;
+ }
+ }
+
+ for (String badClass : badClasses)
+ {
+ if (className.equals(badClass))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureUberspector.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureUberspector.java
new file mode 100644
index 00000000..bce6d084
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/SecureUberspector.java
@@ -0,0 +1,85 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 java.util.Iterator;
+
+/**
+ * Use a custom introspector that prevents classloader related method
+ * calls. Use this introspector for situations in which template
+ * writers are numerous or untrusted. Specifically, this introspector
+ * prevents creation of arbitrary objects or reflection on objects.
+ *
+ * <p>To use this introspector, set the following property:
+ * <pre>
+ * introspector.uberspect.class = org.apache.velocity.util.introspection.SecureUberspector
+ * </pre>
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ * @since 1.5
+ */
+public class SecureUberspector extends UberspectImpl
+{
+ /**
+ * init - generates the Introspector. As the setup code
+ * makes sure that the log gets set before this is called,
+ * we can initialize the Introspector using the log object.
+ */
+ @Override
+ public void init()
+ {
+ String [] badPackages = rsvc.getConfiguration()
+ .getStringArray(RuntimeConstants.INTROSPECTOR_RESTRICT_PACKAGES);
+
+ String [] badClasses = rsvc.getConfiguration()
+ .getStringArray(RuntimeConstants.INTROSPECTOR_RESTRICT_CLASSES);
+
+ introspector = new SecureIntrospectorImpl(badClasses, badPackages, log);
+ }
+
+ /**
+ * Get an iterator from the given object. Since the superclass method
+ * this secure version checks for execute permission.
+ *
+ * @param obj object to iterate over
+ * @param i line, column, template info
+ * @return Iterator for object
+ */
+ @Override
+ public Iterator getIterator(Object obj, Info i)
+ {
+ if (obj != null)
+ {
+ SecureIntrospectorControl sic = (SecureIntrospectorControl)introspector;
+ if (sic.checkObjectExecutePermission(obj.getClass(), null))
+ {
+ return super.getIterator(obj, i);
+ }
+ else
+ {
+ log.warn("Cannot retrieve iterator from {} due to security restrictions.", obj.getClass().getName());
+ }
+ }
+ return null;
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java
new file mode 100644
index 00000000..6408019e
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandler.java
@@ -0,0 +1,68 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.lang.reflect.Type;
+
+/**
+ * A conversion handler adds admissible conversions between Java types whenever Velocity introspection has to map
+ * VTL methods and property accessors to Java methods.
+ * Both methods must be consistent: <code>getNeededConverter</code> must not return <code>null</code> whenever
+ * <code>isExplicitlyConvertible</code> returned true with the same arguments.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: ConversionHandler.java $
+ * @since 2.1
+ */
+
+public interface TypeConversionHandler
+{
+ /**
+ * Check to see if the conversion can be done using an explicit conversion
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @param possibleVarArg whether var arg is possible
+ * @return null if no conversion is needed, or the appropriate Converter object
+ * @since 2.1
+ */
+ boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg);
+
+ /**
+ * Returns the appropriate Converter object needed for an explicit conversion
+ * Returns null if no conversion is needed.
+ *
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @return null if no conversion is needed, or the appropriate Converter object
+ * @since 2.1
+ */
+ Converter<?> getNeededConverter(Type formal, Class<?> actual);
+
+ /**
+ * Add the given converter to the handler. Implementation should be thread-safe.
+ *
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @param converter converter
+ * @since 2.1
+ */
+ void addConverter(Type formal, Class<?> actual, Converter<?> converter);
+
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
new file mode 100644
index 00000000..ced7ca81
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/TypeConversionHandlerImpl.java
@@ -0,0 +1,725 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.LocaleUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * A conversion handler adds admissible conversions between Java types whenever Velocity introspection has to map
+ * VTL methods and property accessors to Java methods. This implementation is the default Conversion Handler
+ * for Velocity.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: TypeConversionHandlerImpl.java $
+ * @since 2.0
+ */
+
+public class TypeConversionHandlerImpl implements TypeConversionHandler
+{
+ /**
+ * standard narrowing and string parsing conversions.
+ */
+ static Map<Pair<String, String>, Converter<?>> standardConverterMap;
+
+ /**
+ * basic toString converter
+ */
+ static Converter<?> toString;
+
+ /**
+ * cache miss converter
+ */
+ static Converter<?> cacheMiss;
+
+ /**
+ * a converters cache map, initialized with the standard narrowing and string parsing conversions.
+ */
+ Map<Pair<String, String>, Converter<?>> converterCacheMap;
+
+ static final String BOOLEAN_TYPE = "boolean";
+ static final String BYTE_TYPE = "byte";
+ static final String SHORT_TYPE = "short";
+ static final String INTEGER_TYPE = "int";
+ static final String LONG_TYPE = "long";
+ static final String FLOAT_TYPE = "float";
+ static final String DOUBLE_TYPE = "double";
+ static final String CHARACTER_TYPE = "char";
+ static final String BOOLEAN_CLASS = "java.lang.Boolean";
+ static final String BYTE_CLASS = "java.lang.Byte";
+ static final String SHORT_CLASS = "java.lang.Short";
+ static final String INTEGER_CLASS = "java.lang.Integer";
+ static final String LONG_CLASS = "java.lang.Long";
+ static final String BIG_INTEGER_CLASS = "java.math.BigInteger";
+ static final String FLOAT_CLASS = "java.lang.Float";
+ static final String DOUBLE_CLASS = "java.lang.Double";
+ static final String BIG_DECIMAL_CLASS = "java.math.BigDecimal";
+ static final String NUMBER_CLASS = "java.lang.Number";
+ static final String CHARACTER_CLASS = "java.lang.Character";
+ static final String STRING_CLASS = "java.lang.String";
+ static final String LOCALE_CLASS = "java.util.Locale";
+
+ /*
+ * Bounds checking helper
+ */
+
+ static boolean checkBounds(Number n, double min, double max)
+ {
+ double d = n.doubleValue();
+ if (d < min || d > max)
+ {
+ throw new NumberFormatException("value out of range: " + n);
+ }
+ return true;
+ }
+
+ static
+ {
+ standardConverterMap = new HashMap<>();
+
+ cacheMiss = o -> o;
+
+ /*
+ * Conversions towards boolean
+ */
+
+ /* number -> boolean */
+
+ Converter<Boolean> numberToBool = o -> Optional.ofNullable((Number)o).map(n -> Double.compare(n.doubleValue(), 0.0) != 0).orElse(null);
+ Converter<Boolean> bigIntegerToBool = o -> Optional.ofNullable((BigInteger)o).map(bi -> bi.signum() != 0).orElse(null);
+ Converter<Boolean> bigDecimalToBool = o -> Optional.ofNullable((BigDecimal)o).map(bi -> bi.signum() != 0).orElse(null);
+
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, BYTE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, SHORT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, INTEGER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, LONG_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, BIG_INTEGER_CLASS), bigIntegerToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, FLOAT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, DOUBLE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, BIG_DECIMAL_CLASS), bigDecimalToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, NUMBER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, BYTE_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, SHORT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, INTEGER_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, LONG_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, FLOAT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, DOUBLE_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, BYTE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, SHORT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, INTEGER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, BIG_INTEGER_CLASS), bigIntegerToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, LONG_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, FLOAT_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, DOUBLE_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, BIG_DECIMAL_CLASS), bigDecimalToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, NUMBER_CLASS), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, BYTE_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, SHORT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, INTEGER_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, LONG_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, FLOAT_TYPE), numberToBool);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, DOUBLE_TYPE), numberToBool);
+
+ /* character -> boolean */
+
+ Converter<Boolean> charToBoolean = o -> Optional.ofNullable((Character)o).map(c -> c != 0).orElse(null);
+
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, CHARACTER_CLASS), charToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, CHARACTER_TYPE), charToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, CHARACTER_CLASS), charToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, CHARACTER_TYPE), charToBoolean);
+
+ /* string -> boolean */
+
+ Converter<Boolean> stringToBoolean = o -> Boolean.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(BOOLEAN_CLASS, STRING_CLASS), stringToBoolean);
+ standardConverterMap.put(Pair.of(BOOLEAN_TYPE, STRING_CLASS), stringToBoolean);
+
+ /*
+ * Conversions towards byte
+ */
+
+ /* narrowing towards byte */
+
+ Converter<Byte> narrowingToByte = o -> Optional.ofNullable((Number)o)
+ .filter(n -> checkBounds(n, Byte.MIN_VALUE, Byte.MAX_VALUE))
+ .map(Number::byteValue)
+ .orElse(null);
+
+ Converter<Byte> narrowingBigIntegerToByte = o -> Optional.ofNullable((BigInteger)o)
+ .map(BigInteger::byteValueExact)
+ .orElse(null);
+
+ Converter<Byte> narrowingBigDecimalToByte = o -> Optional.ofNullable((BigDecimal)o)
+ .map(BigDecimal::byteValueExact)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BYTE_CLASS, SHORT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, INTEGER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, LONG_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, BIG_INTEGER_CLASS), narrowingBigIntegerToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, FLOAT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, DOUBLE_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, BIG_DECIMAL_CLASS), narrowingBigDecimalToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, NUMBER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, SHORT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, INTEGER_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, LONG_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, FLOAT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, DOUBLE_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, SHORT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, INTEGER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, LONG_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, BIG_INTEGER_CLASS), narrowingBigIntegerToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, FLOAT_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, DOUBLE_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, BIG_DECIMAL_CLASS), narrowingBigDecimalToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, NUMBER_CLASS), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, SHORT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, INTEGER_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, LONG_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, FLOAT_TYPE), narrowingToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, DOUBLE_TYPE), narrowingToByte);
+
+ /* string to byte */
+
+ Converter<Byte> stringToByte = o -> Byte.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(BYTE_CLASS, STRING_CLASS), stringToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, STRING_CLASS), stringToByte);
+
+ /*
+ * Conversions towards short
+ */
+
+ /* narrowing towards short */
+
+ Converter<Short> narrowingToShort = o -> Optional.ofNullable((Number)o)
+ .filter(n -> checkBounds(n, Short.MIN_VALUE, Short.MAX_VALUE))
+ .map(Number::shortValue)
+ .orElse(null);
+
+ Converter<Short> narrowingBigIntegerToShort = o -> Optional.ofNullable((BigInteger)o)
+ .map(BigInteger::shortValueExact)
+ .orElse(null);
+
+ Converter<Short> narrowingBigDecimalToShort = o -> Optional.ofNullable((BigDecimal)o)
+ .map(BigDecimal::shortValueExact)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(SHORT_CLASS, INTEGER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, LONG_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BIG_INTEGER_CLASS), narrowingBigIntegerToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, FLOAT_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, DOUBLE_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BIG_DECIMAL_CLASS), narrowingBigDecimalToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, NUMBER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, INTEGER_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, LONG_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, FLOAT_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, DOUBLE_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, INTEGER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, LONG_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, BIG_INTEGER_CLASS), narrowingBigIntegerToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, FLOAT_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, DOUBLE_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, BIG_DECIMAL_CLASS), narrowingBigDecimalToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, NUMBER_CLASS), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, INTEGER_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, LONG_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, FLOAT_TYPE), narrowingToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, DOUBLE_TYPE), narrowingToShort);
+
+ /* widening towards short */
+
+ Converter<Short> wideningToShort = o -> Optional.ofNullable((Number)o)
+ .map(Number::shortValue)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BYTE_CLASS), wideningToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BYTE_TYPE), wideningToShort);
+
+ /* string to short */
+
+ Converter<Short> stringToShort = o -> Short.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(SHORT_CLASS, STRING_CLASS), stringToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, STRING_CLASS), stringToShort);
+
+ /*
+ * Conversions towards int
+ */
+
+ /* narrowing towards int */
+
+ Converter<Integer> narrowingToInteger = o -> Optional.ofNullable((Number)o)
+ .filter(n -> checkBounds(n, Integer.MIN_VALUE, Integer.MAX_VALUE))
+ .map(Number::intValue)
+ .orElse(null);
+
+ Converter<Integer> narrowingBigIntegerToInteger = o -> Optional.ofNullable((BigInteger)o)
+ .map(BigInteger::intValueExact)
+ .orElse(null);
+
+ Converter<Integer> narrowingBigDecimalToInteger = o -> Optional.ofNullable((BigDecimal)o)
+ .map(BigDecimal::intValueExact)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, LONG_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BIG_INTEGER_CLASS), narrowingBigIntegerToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, FLOAT_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, DOUBLE_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BIG_DECIMAL_CLASS), narrowingBigDecimalToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, NUMBER_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, LONG_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, FLOAT_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, DOUBLE_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, LONG_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, BIG_INTEGER_CLASS), narrowingBigIntegerToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, FLOAT_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, DOUBLE_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, BIG_DECIMAL_CLASS), narrowingBigDecimalToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, NUMBER_CLASS), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, LONG_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, FLOAT_TYPE), narrowingToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, DOUBLE_TYPE), narrowingToInteger);
+
+ /* widening towards int */
+
+ Converter<Integer> wideningToInteger = o -> Optional.ofNullable((Number)o)
+ .map(Number::intValue)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BYTE_CLASS), wideningToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, SHORT_CLASS), wideningToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BYTE_TYPE), wideningToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, SHORT_TYPE), wideningToInteger);
+
+ /* string to int */
+
+ Converter<Integer> stringToInteger = o -> Integer.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, STRING_CLASS), stringToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, STRING_CLASS), stringToInteger);
+
+ /*
+ * Conversions towards long
+ */
+
+ /* narrowing towards long */
+
+ Converter<Long> narrowingToLong = o -> Optional.ofNullable((Number)o)
+ .filter(n -> checkBounds(n, Long.MIN_VALUE, Long.MAX_VALUE))
+ .map(Number::longValue)
+ .orElse(null);
+
+ Converter<Long> narrowingBigIntegerToLong = o -> Optional.ofNullable((BigInteger)o)
+ .map(BigInteger::longValueExact)
+ .orElse(null);
+
+ Converter<Long> narrowingBigDecimalToLong = o -> Optional.ofNullable((BigDecimal)o)
+ .map(BigDecimal::longValueExact)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(LONG_CLASS, BIG_INTEGER_CLASS), narrowingBigIntegerToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, FLOAT_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, DOUBLE_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, BIG_DECIMAL_CLASS), narrowingBigDecimalToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, NUMBER_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, FLOAT_TYPE), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, DOUBLE_TYPE), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, BIG_INTEGER_CLASS), narrowingBigIntegerToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, FLOAT_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, DOUBLE_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, BIG_DECIMAL_CLASS), narrowingBigDecimalToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, NUMBER_CLASS), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, FLOAT_TYPE), narrowingToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, DOUBLE_TYPE), narrowingToLong);
+
+ /* widening towards long */
+
+ Converter<Long> wideningToLong = o -> Optional.ofNullable((Number)o)
+ .map(Number::longValue)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(LONG_CLASS, BYTE_CLASS), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, SHORT_CLASS), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, INTEGER_CLASS), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, BYTE_TYPE), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, SHORT_TYPE), wideningToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, INTEGER_TYPE), wideningToLong);
+
+ /* string to long */
+
+ Converter<Long> stringToLong = o -> Long.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(LONG_CLASS, STRING_CLASS), stringToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, STRING_CLASS), stringToLong);
+
+ /*
+ * Conversions towards BigInteger
+ */
+
+ /* exact types towards BigInteger */
+
+ Converter<BigInteger> toBigInteger = o -> Optional.ofNullable((Number)o)
+ .map(n -> BigInteger.valueOf(n.longValue()))
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, BYTE_CLASS), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, SHORT_CLASS), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, INTEGER_CLASS), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, LONG_CLASS), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, BYTE_TYPE), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, SHORT_TYPE), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, INTEGER_TYPE), toBigInteger);
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, LONG_TYPE), toBigInteger);
+
+ /* approximate types towards BigInteger */
+
+ /* It makes no sense trying to convert from float or double towards BigInteger
+ if we do care about precision loss..
+ */
+
+ Converter<BigInteger> bigDecimalToBigInteger = o -> Optional.ofNullable((BigDecimal)o)
+ .map(BigDecimal::toBigIntegerExact)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, BIG_DECIMAL_CLASS), bigDecimalToBigInteger);
+
+ /* string to BigInteger */
+
+ Converter<BigInteger> stringToBigInteger = o -> Optional.ofNullable(o)
+ .map(s -> new BigInteger(String.valueOf(s)))
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BIG_INTEGER_CLASS, STRING_CLASS), stringToBigInteger);
+
+ /*
+ * Conversions towards float
+ */
+
+ Converter<Float> toFloat = o -> Optional.ofNullable((Number)o)
+ .map(Number::floatValue)
+ .orElse(null);
+
+ /* narrowing towards float */
+
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, BIG_INTEGER_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, DOUBLE_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, BIG_DECIMAL_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, NUMBER_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, DOUBLE_TYPE), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, BIG_INTEGER_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, DOUBLE_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, BIG_DECIMAL_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, NUMBER_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, DOUBLE_TYPE), toFloat);
+
+ /* exact types towards float */
+
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, BYTE_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, SHORT_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, INTEGER_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, LONG_CLASS), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, BYTE_TYPE), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, SHORT_TYPE), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, INTEGER_TYPE), toFloat);
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, LONG_TYPE), toFloat);
+
+ /* string to float */
+
+ Converter<Float> stringToFloat = o -> Float.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(FLOAT_CLASS, STRING_CLASS), stringToFloat);
+ standardConverterMap.put(Pair.of(FLOAT_TYPE, STRING_CLASS), stringToFloat);
+
+ /*
+ * Conversions towards double
+ */
+
+ Converter<Double> toDouble = o -> Optional.ofNullable((Number)o)
+ .map(Number::doubleValue)
+ .orElse(null);
+
+ /* narrowing towards double */
+
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, BIG_INTEGER_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, BIG_DECIMAL_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_TYPE, BIG_INTEGER_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_TYPE, BIG_DECIMAL_CLASS), toDouble);
+
+ /* exact types or widening towards double */
+
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, BYTE_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, SHORT_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, INTEGER_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, LONG_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, FLOAT_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, NUMBER_CLASS), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, BYTE_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, SHORT_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, INTEGER_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, LONG_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, FLOAT_TYPE), toDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_TYPE, NUMBER_CLASS), toDouble);
+
+ /* string to double */
+
+ Converter<Double> stringToDouble = o -> Double.valueOf(String.valueOf(o));
+
+ standardConverterMap.put(Pair.of(DOUBLE_CLASS, STRING_CLASS), stringToDouble);
+ standardConverterMap.put(Pair.of(DOUBLE_TYPE, STRING_CLASS), stringToDouble);
+
+ /*
+ * Conversions towards BigDecimal
+ */
+
+ /* exact types towards BigDecimal */
+
+ Converter<BigDecimal> exactToBigDecimal = o -> Optional.ofNullable((Number)o)
+ .map(n -> BigDecimal.valueOf(n.longValue()))
+ .orElse(null);
+
+ Converter<BigDecimal> bigIntegerToBigDecimal = o -> Optional.ofNullable((BigInteger)o)
+ .map(bi -> new BigDecimal(bi))
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, BYTE_CLASS), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, SHORT_CLASS), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, INTEGER_CLASS), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, LONG_CLASS), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, BIG_INTEGER_CLASS), bigIntegerToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, BYTE_TYPE), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, SHORT_TYPE), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, INTEGER_TYPE), exactToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, LONG_TYPE), exactToBigDecimal);
+
+ /* approximate types towards BigDecimal */
+
+ Converter<BigDecimal> approxToBigDecimal = o -> Optional.ofNullable((Number)o)
+ .map(n -> BigDecimal.valueOf(n.doubleValue()))
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, FLOAT_CLASS), approxToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, DOUBLE_CLASS), approxToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, FLOAT_TYPE), approxToBigDecimal);
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, DOUBLE_TYPE), approxToBigDecimal);
+
+ /* string to BigDecimal */
+
+ Converter<BigDecimal> stringToBigDecimal = o -> Optional.ofNullable(o)
+ .map(s -> new BigDecimal(String.valueOf(s)))
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BIG_DECIMAL_CLASS, STRING_CLASS), stringToBigDecimal);
+
+ /*
+ * Conversions from boolean to numeric type
+ */
+
+ /* boolean to byte */
+
+ Converter<Byte> booleanToByte = o -> Optional.ofNullable((Boolean)o)
+ .map(b -> b ? (byte)1 : (byte)0)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(BYTE_CLASS, BOOLEAN_CLASS), booleanToByte);
+ standardConverterMap.put(Pair.of(BYTE_CLASS, BOOLEAN_TYPE), booleanToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, BOOLEAN_CLASS), booleanToByte);
+ standardConverterMap.put(Pair.of(BYTE_TYPE, BOOLEAN_TYPE), booleanToByte);
+
+ /* boolean to short */
+
+ Converter<Short> booleanToShort = o -> Optional.ofNullable((Boolean)o)
+ .map(b -> b ? (short)1 : (short)0)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BOOLEAN_CLASS), booleanToShort);
+ standardConverterMap.put(Pair.of(SHORT_CLASS, BOOLEAN_TYPE), booleanToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, BOOLEAN_CLASS), booleanToShort);
+ standardConverterMap.put(Pair.of(SHORT_TYPE, BOOLEAN_TYPE), booleanToShort);
+
+ /* boolean to integer */
+
+ Converter<Integer> booleanToInteger = o -> Optional.ofNullable((Boolean)o)
+ .map(b -> b ? (int)1 : (int)0)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BOOLEAN_CLASS), booleanToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_CLASS, BOOLEAN_TYPE), booleanToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, BOOLEAN_CLASS), booleanToInteger);
+ standardConverterMap.put(Pair.of(INTEGER_TYPE, BOOLEAN_TYPE), booleanToInteger);
+
+ /* boolean to long */
+
+ Converter<Long> booleanToLong = o -> Optional.ofNullable((Boolean)o)
+ .map(b -> b ? 1l : 0l)
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(LONG_CLASS, BOOLEAN_CLASS), booleanToLong);
+ standardConverterMap.put(Pair.of(LONG_CLASS, BOOLEAN_TYPE), booleanToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, BOOLEAN_CLASS), booleanToLong);
+ standardConverterMap.put(Pair.of(LONG_TYPE, BOOLEAN_TYPE), booleanToLong);
+
+ /* to string */
+
+ toString = o -> String.valueOf(o);
+
+ /* string to locale */
+ Converter<Locale> stringToLocale = o -> Optional.ofNullable(o)
+ .map(l -> LocaleUtils.toLocale(String.valueOf(l)))
+ .orElse(null);
+
+ standardConverterMap.put(Pair.of(LOCALE_CLASS, STRING_CLASS), stringToLocale);
+ }
+
+ /**
+ * Constructor
+ */
+ public TypeConversionHandlerImpl()
+ {
+ converterCacheMap = new ConcurrentHashMap<>();
+ }
+
+ /**
+ * Check to see if the conversion can be done using an explicit conversion
+ * @param actual found argument type
+ * @param formal expected formal type
+ * @return true if actual class can be explicitely converted to expected formal type
+ * @since 2.1
+ */
+ @Override
+ public boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
+ {
+ /*
+ * for consistency, we also have to check standard implicit convertibility
+ * since it may not have been checked before by the calling code
+ */
+ Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null && formalClass == actual ||
+ IntrospectionUtils.isMethodInvocationConvertible(formal, actual, possibleVarArg) ||
+ getNeededConverter(formal, actual) != null)
+ {
+ return true;
+ }
+
+ /* Check var arg */
+ if (possibleVarArg && TypeUtils.isArrayType(formal))
+ {
+ if (actual.isArray())
+ {
+ actual = actual.getComponentType();
+ }
+ return isExplicitlyConvertible(TypeUtils.getArrayComponentType(formal), actual, false);
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns the appropriate Converter object needed for an explicit conversion
+ * Returns null if no conversion is needed.
+ *
+ * @param actual found argument type
+ * @param formal expected formal type
+ * @return null if no conversion is needed, or the appropriate Converter object
+ * @since 2.1
+ */
+ @Override
+ public Converter<?> getNeededConverter(Type formal, Class<?> actual)
+ {
+ if (actual == null)
+ {
+ return null;
+ }
+ Pair<String, String> key = Pair.of(formal.getTypeName(), actual.getTypeName());
+
+ /* first check for a standard conversion */
+ Converter<?> converter = standardConverterMap.get(key);
+ if (converter == null)
+ {
+ /* then the converters cache map */
+ converter = converterCacheMap.get(key);
+ if (converter == null)
+ {
+ Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
+ /* check for conversion towards string */
+ if (formal == String.class)
+ {
+ converter = toString;
+ }
+ /* check for String -> Enum constant conversion */
+ else if (formalClass != null && formalClass.isEnum() && actual == String.class)
+ {
+ final Class<Enum> enumClass = (Class<Enum>)formalClass;
+ converter = o -> Enum.valueOf(enumClass, (String)o);
+ }
+
+ converterCacheMap.put(key, converter == null ? cacheMiss : converter);
+ }
+ }
+ return converter == cacheMiss ? null : converter;
+ }
+
+ /**
+ * Add the given converter to the handler.
+ *
+ * @param formal expected formal type
+ * @param actual provided argument type
+ * @param converter converter
+ * @since 2.1
+ */
+ @Override
+ public void addConverter(Type formal, Class<?> actual, Converter<?> converter)
+ {
+ Pair<String, String> key = Pair.of(formal.getTypeName(), actual.getTypeName());
+ converterCacheMap.put(key, converter);
+ Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null)
+ {
+ if (formalClass.isPrimitive())
+ {
+ key = Pair.of(IntrospectionUtils.getBoxedClass(formalClass).getTypeName(), actual.getTypeName());
+ converterCacheMap.put(key, converter);
+ }
+ else
+ {
+ Class<?> unboxedFormal = IntrospectionUtils.getUnboxedClass(formalClass);
+ if (unboxedFormal != formalClass)
+ {
+ key = Pair.of(unboxedFormal.getTypeName(), actual.getTypeName());
+ converterCacheMap.put(key, converter);
+ }
+ }
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Uberspect.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Uberspect.java
new file mode 100644
index 00000000..a57b20ae
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/Uberspect.java
@@ -0,0 +1,74 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.util.Iterator;
+
+/**
+ * 'Federated' introspection/reflection interface to allow the introspection
+ * behavior in Velocity to be customized.
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magusson Jr.</a>
+ * @version $Id$
+ */
+public interface Uberspect
+{
+ /**
+ * Initializer - will be called before use
+ */
+ void init();
+
+ /**
+ * To support iteratives - #foreach()
+ * @param obj
+ * @param info
+ * @return An Iterator.
+ */
+ Iterator getIterator(Object obj, Info info);
+
+ /**
+ * Returns a general method, corresponding to $foo.bar( $woogie )
+ * @param obj
+ * @param method
+ * @param args
+ * @param info
+ * @return A Velocity Method.
+ */
+ VelMethod getMethod(Object obj, String method, Object[] args, Info info);
+
+ /**
+ * Property getter - returns VelPropertyGet appropos for #set($foo = $bar.woogie)
+ * @param obj
+ * @param identifier
+ * @param info
+ * @return A Velocity Getter.
+ */
+ VelPropertyGet getPropertyGet(Object obj, String identifier, Info info);
+
+ /**
+ * Property setter - returns VelPropertySet appropos for #set($foo.bar = "geir")
+ * @param obj
+ * @param identifier
+ * @param arg
+ * @param info
+ * @return A Velocity Setter.
+ */
+ VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info info);
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
new file mode 100644
index 00000000..577232f5
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectImpl.java
@@ -0,0 +1,796 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.parser.node.AbstractExecutor;
+import org.apache.velocity.runtime.parser.node.BooleanPropertyExecutor;
+import org.apache.velocity.runtime.parser.node.GetExecutor;
+import org.apache.velocity.runtime.parser.node.MapGetExecutor;
+import org.apache.velocity.runtime.parser.node.MapSetExecutor;
+import org.apache.velocity.runtime.parser.node.PropertyExecutor;
+import org.apache.velocity.runtime.parser.node.PutExecutor;
+import org.apache.velocity.runtime.parser.node.SetExecutor;
+import org.apache.velocity.runtime.parser.node.SetPropertyExecutor;
+import org.apache.velocity.util.ArrayIterator;
+import org.apache.velocity.util.ArrayListWrapper;
+import org.apache.velocity.util.ClassUtils;
+import org.apache.velocity.util.EnumerationIterator;
+import org.apache.velocity.util.RuntimeServicesAware;
+
+import org.slf4j.Logger;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Implementation of Uberspect to provide the default introspective
+ * functionality of Velocity
+ *
+ * @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 UberspectImpl implements Uberspect, RuntimeServicesAware
+{
+ /**
+ * Our runtime logger.
+ */
+ protected Logger log;
+
+ /**
+ * the default Velocity introspector
+ */
+ protected Introspector introspector;
+
+ /**
+ * the conversion handler
+ */
+ protected TypeConversionHandler conversionHandler;
+
+ /**
+ * runtime services
+ */
+ protected RuntimeServices rsvc;
+
+ /**
+ * init - generates the Introspector. As the setup code
+ * makes sure that the log gets set before this is called,
+ * we can initialize the Introspector using the log object.
+ */
+ @Override
+ public void init()
+ {
+ introspector = new Introspector(log, conversionHandler);
+ }
+
+ public TypeConversionHandler getConversionHandler()
+ {
+ return conversionHandler;
+ }
+
+ /**
+ * sets the runtime services
+ * @param rs runtime services
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public void setRuntimeServices(RuntimeServices rs)
+ {
+ rsvc = rs;
+ log = rsvc.getLog("introspection");
+
+ Object conversionHandlerInstance = rs.getProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE);
+ if (conversionHandlerInstance == null)
+ {
+ String conversionHandlerClass = rs.getString(RuntimeConstants.CONVERSION_HANDLER_CLASS);
+ if (conversionHandlerClass != null && !conversionHandlerClass.equals("none"))
+ {
+ try
+ {
+ conversionHandlerInstance = ClassUtils.getNewInstance(conversionHandlerClass);
+ }
+ catch (ClassNotFoundException cnfe )
+ {
+ String err = "The specified class for ConversionHandler (" + conversionHandlerClass
+ + ") 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 '" + conversionHandlerClass + "'", ie);
+ }
+ catch (IllegalAccessException ae)
+ {
+ throw new VelocityException("Cannot access class '" + conversionHandlerClass + "'", ae);
+ }
+ }
+ }
+
+ if (conversionHandlerInstance != null)
+ {
+ if (conversionHandlerInstance instanceof ConversionHandler)
+ {
+ log.warn("The ConversionHandler interface is deprecated - see the TypeConversionHandler interface");
+ final ConversionHandler ch = (ConversionHandler)conversionHandlerInstance;
+ conversionHandler = new TypeConversionHandler()
+ {
+ @Override
+ public boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
+ {
+ Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null) return ch.isExplicitlyConvertible(formalClass, actual, possibleVarArg);
+ else return false;
+ }
+
+ @Override
+ public Converter<?> getNeededConverter(Type formal, Class<?> actual)
+ {
+ Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null) return ch.getNeededConverter(formalClass, actual);
+ else return null;
+ }
+
+ @Override
+ public void addConverter(Type formal, Class<?> actual, Converter<?> converter)
+ {
+ Class<?> formalClass = IntrospectionUtils.getTypeClass(formal);
+ if (formalClass != null) ch.addConverter(formalClass, actual, converter);
+ else throw new UnsupportedOperationException("This conversion handler doesn't know how to handle Type: " + formal.getTypeName());
+ }
+ };
+ }
+ else if (!(conversionHandlerInstance instanceof TypeConversionHandler))
+ {
+ String err = "The specified class or provided instance for the conversion handler (" + conversionHandlerInstance.getClass().getName()
+ + ") does not implement " + TypeConversionHandler.class.getName()
+ + "; Velocity is not initialized correctly.";
+
+ log.error(err);
+ throw new VelocityException(err, null, rsvc.getLogContext().getStackTrace());
+ }
+ else
+ {
+ conversionHandler = (TypeConversionHandler)conversionHandlerInstance;
+ }
+ }
+ }
+
+ /**
+ * Sets the runtime logger - this must be called before anything
+ * else.
+ *
+ * @param log The logger instance to use.
+ * @since 1.5
+ * @deprecated logger is now set by default to the namespace logger "velocity.rendering".
+ */
+ public void setLog(Logger log)
+ {
+ this.log = log;
+ }
+
+ /**
+ * To support iterative objects used in a <code>#foreach()</code>
+ * loop.
+ *
+ * @param obj The iterative object.
+ * @param i Info about the object's location.
+ * @return An {@link Iterator} object.
+ */
+ @Override
+ public Iterator getIterator(Object obj, Info i)
+ {
+ if (obj.getClass().isArray())
+ {
+ return new ArrayIterator(obj);
+ }
+ else if (obj instanceof Iterable)
+ {
+ return ((Iterable) obj).iterator();
+ }
+ else if (obj instanceof Map)
+ {
+ return ((Map) obj).values().iterator();
+ }
+ else if (obj instanceof Iterator)
+ {
+ log.debug("The iterative object in the #foreach() loop at {}" +
+ " is of type java.util.Iterator. Because " +
+ "it is not resettable, if used in more than once it " +
+ "may lead to unexpected results.", i);
+ return ((Iterator) obj);
+ }
+ else if (obj instanceof Enumeration)
+ {
+ log.debug("The iterative object in the #foreach() loop at {}" +
+ " is of type java.util.Enumeration. Because " +
+ "it is not resettable, if used in more than once it " +
+ "may lead to unexpected results.", i);
+ return new EnumerationIterator((Enumeration) obj);
+ }
+ else
+ {
+ // look for an iterator() method to support the JDK5 Iterable
+ // interface or any user tools/DTOs that want to work in
+ // foreach without implementing the Collection interface
+ Class<?> type = obj.getClass();
+ try
+ {
+ Method iter = type.getMethod("iterator");
+ Class<?> returns = iter.getReturnType();
+ if (Iterator.class.isAssignableFrom(returns))
+ {
+ try
+ {
+ return (Iterator)iter.invoke(obj);
+ }
+ catch (IllegalAccessException e)
+ {
+ // Cannot invoke this method, just give up
+ }
+ catch (Exception e)
+ {
+ throw new VelocityException("Error invoking the method 'iterator' on class '"
+ + obj.getClass().getName() +"'", e, rsvc.getLogContext().getStackTrace());
+ }
+ }
+ else
+ {
+ log.debug("iterator() method of reference in #foreach loop at " +
+ "{} does not return a true Iterator.", i);
+ }
+ }
+ catch (NoSuchMethodException nsme)
+ {
+ // eat this one, but let all other exceptions thru
+ }
+ }
+
+ /* we have no clue what this is */
+ log.debug("Could not determine type of iterator in #foreach loop at {}", i);
+
+ return null;
+ }
+
+ /**
+ * Method
+ * @param obj
+ * @param methodName
+ * @param args
+ * @param i
+ * @return A Velocity Method.
+ */
+ @Override
+ public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
+ {
+ if (obj == null)
+ {
+ return null;
+ }
+
+ Method m = introspector.getMethod(obj.getClass(), methodName, args);
+ if (m != null)
+ {
+ return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args));
+ }
+
+ Class<?> cls = obj.getClass();
+ // if it's an array
+ if (cls.isArray())
+ {
+ // check for support via our array->list wrapper
+ m = introspector.getMethod(ArrayListWrapper.class, methodName, args);
+ if (m != null)
+ {
+ // and create a method that knows to wrap the value
+ // before invoking the method
+ return new VelMethodImpl(m, true, getNeededConverters(m.getGenericParameterTypes(), args));
+ }
+ }
+ // watch for classes, to allow calling their static methods (VELOCITY-102)
+ else if (cls == Class.class)
+ {
+ m = introspector.getMethod((Class<?>)obj, methodName, args);
+ if (m != null)
+ {
+ return new VelMethodImpl(m, false, getNeededConverters(m.getGenericParameterTypes(), args));
+ }
+ }
+ return null;
+ }
+
+ /**
+ * get the list of needed converters to adapt passed argument types to method types
+ * @return null if not conversion needed, otherwise an array containing needed converters
+ */
+ private Converter<?>[] getNeededConverters(Type[] expected, Object[] provided)
+ {
+ if (conversionHandler == null) return null;
+ // var args are not handled here - CB TODO
+ int n = Math.min(expected.length, provided.length);
+ Converter<?>[] converters = null;
+ for (int i = 0; i < n; ++i)
+ {
+ Object arg = provided[i];
+ if (arg == null) continue;
+ Converter<?> converter = conversionHandler.getNeededConverter(expected[i], arg.getClass());
+ if (converter != null)
+ {
+ if (converters == null)
+ {
+ converters = new Converter[expected.length];
+ }
+ converters[i] = converter;
+ }
+ }
+ return converters;
+ }
+
+ /**
+ * Property getter
+ * @param obj
+ * @param identifier
+ * @param i
+ * @return A Velocity Getter Method.
+ */
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
+ {
+ if (obj == null)
+ {
+ return null;
+ }
+
+ Class<?> claz = obj.getClass();
+
+ /*
+ * first try for a getFoo() type of property
+ * (also getfoo() )
+ */
+ AbstractExecutor executor = new PropertyExecutor(log, introspector, claz, identifier);
+
+ /*
+ * Let's see if we are a map...
+ */
+ if (!executor.isAlive())
+ {
+ executor = new MapGetExecutor(log, obj, identifier);
+ }
+
+ /*
+ * if that didn't work, look for get("foo")
+ */
+
+ if (!executor.isAlive())
+ {
+ executor = new GetExecutor(log, introspector, claz, identifier);
+ }
+
+ /*
+ * finally, look for boolean isFoo()
+ */
+
+ if (!executor.isAlive())
+ {
+ executor = new BooleanPropertyExecutor(log, introspector, claz,
+ identifier);
+ }
+
+ /*
+ * and idem on an array
+ */
+ if (!executor.isAlive() && obj.getClass().isArray())
+ {
+ executor = new BooleanPropertyExecutor(log, introspector, ArrayListWrapper.class,
+ identifier, true);
+ }
+
+ return (executor.isAlive()) ? new VelGetterImpl(executor) : null;
+ }
+
+ /**
+ * Property setter
+ * @param obj
+ * @param identifier
+ * @param arg
+ * @param i
+ * @return A Velocity Setter method.
+ */
+ @Override
+ public VelPropertySet getPropertySet(Object obj, String identifier,
+ Object arg, Info i)
+ {
+ if (obj == null)
+ {
+ return null;
+ }
+
+ Class<?> claz = obj.getClass();
+
+ /*
+ * first try for a setFoo() type of property
+ * (also setfoo() )
+ */
+ SetExecutor executor = new SetPropertyExecutor(log, introspector, claz, identifier, arg);
+
+ /*
+ * Let's see if we are a map...
+ */
+ if (!executor.isAlive()) {
+ executor = new MapSetExecutor(log, claz, identifier);
+ }
+
+ /*
+ * if that didn't work, look for put("foo", arg)
+ */
+
+ if (!executor.isAlive())
+ {
+ executor = new PutExecutor(log, introspector, claz, arg, identifier);
+ }
+
+ return (executor.isAlive()) ? new VelSetterImpl(executor) : null;
+ }
+
+ /**
+ * Implementation of VelMethod
+ */
+ public static class VelMethodImpl implements VelMethod
+ {
+ final Method method;
+ Boolean isVarArg;
+ boolean wrapArray;
+ Converter<?> converters[];
+
+ /**
+ * @param m
+ */
+ public VelMethodImpl(Method m)
+ {
+ this(m, false, null);
+ }
+
+ /**
+ * @param method
+ * @param wrapArray
+ * @since 1.6
+ */
+ public VelMethodImpl(Method method, boolean wrapArray)
+ {
+ this(method, wrapArray, null);
+ }
+
+ /**
+ * @param method
+ * @param wrapArray
+ * @param converters
+ * @since 2.0
+ */
+ public VelMethodImpl(Method method, boolean wrapArray, Converter<?>[] converters)
+ {
+ this.method = method;
+ this.wrapArray = wrapArray;
+ this.converters = converters;
+ }
+
+ private VelMethodImpl()
+ {
+ method = null;
+ }
+
+ /**
+ * @param o
+ * @param actual
+ * @return invocation result
+ * @see VelMethod#invoke(java.lang.Object, java.lang.Object[])
+ */
+ @Override
+ public Object invoke(Object o, Object[] actual)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ // if we're pretending an array is a list...
+ if (wrapArray)
+ {
+ o = new ArrayListWrapper(o);
+ }
+
+ if (isVarArg())
+ {
+ Class<?>[] formal = method.getParameterTypes();
+ int index = formal.length - 1;
+ if (actual.length >= index)
+ {
+ Class<?> type = formal[index].getComponentType();
+ actual = handleVarArg(type, index, actual);
+ }
+ }
+
+ if (converters != null)
+ {
+ // some converters may throw an ArithmeticException
+ // which we want to wrap into an IllegalArgumentException
+ try
+ {
+ for (int i = 0; i < actual.length; ++i)
+ {
+ if (converters[i] != null)
+ {
+ actual[i] = converters[i].convert(actual[i]);
+ }
+ }
+ }
+ catch (ArithmeticException ae)
+ {
+ throw new IllegalArgumentException(ae);
+ }
+ }
+
+ // call extension point invocation
+ return doInvoke(o, actual);
+ }
+
+ /**
+ * Offers an extension point for subclasses (in alternate Uberspects)
+ * to alter the invocation after any array wrapping or varargs handling
+ * has already been completed.
+ * @param o target object
+ * @param actual arguments
+ * @return invocation result
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ * @since 1.6
+ */
+ protected Object doInvoke(Object o, Object[] actual)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ return method.invoke(o, actual);
+ }
+
+ /**
+ * @return true if this method can accept a variable number of arguments
+ * @since 1.6
+ */
+ public boolean isVarArg()
+ {
+ if (isVarArg == null)
+ {
+ Class<?>[] formal = method.getParameterTypes();
+ if (formal.length == 0)
+ {
+ this.isVarArg = Boolean.FALSE;
+ }
+ else
+ {
+ Class<?> last = formal[formal.length - 1];
+ // if the last arg is an array, then
+ // we consider this a varargs method
+ this.isVarArg = last.isArray();
+ }
+ }
+ return isVarArg;
+ }
+
+ /**
+ * @param type The vararg class type (aka component type
+ * of the expected array arg)
+ * @param index The index of the vararg in the method declaration
+ * (This will always be one less than the number of
+ * expected arguments.)
+ * @param actual The actual parameters being passed to this method
+ * @return The actual parameters adjusted for the varargs in order
+ * to fit the method declaration.
+ */
+ private Object[] handleVarArg(final Class<?> type,
+ final int index,
+ Object[] actual)
+ {
+ // if no values are being passed into the vararg
+ if (actual.length == index)
+ {
+ // copy existing args to new array
+ Object[] newActual = new Object[actual.length + 1];
+ System.arraycopy(actual, 0, newActual, 0, actual.length);
+ // create an empty array of the expected type
+ newActual[index] = Array.newInstance(type, 0);
+ actual = newActual;
+ }
+ // if one value is being passed into the vararg
+ else if (actual.length == index + 1 && actual[index] != null)
+ {
+ // make sure the last arg is an array of the expected type
+ Class<?> argClass = actual[index].getClass();
+ if (!argClass.isArray() && IntrospectionUtils.isMethodInvocationConvertible(type, argClass, false))
+ {
+ // create a 1-length array to hold and replace the last param
+ Object lastActual = Array.newInstance(type, 1);
+ Array.set(lastActual, 0, actual[index]);
+ actual[index] = lastActual;
+ }
+ }
+ // if multiple values are being passed into the vararg
+ else if (actual.length > index + 1)
+ {
+ // put the last and extra actual in an array of the expected type
+ int size = actual.length - index;
+ Object lastActual = Array.newInstance(type, size);
+ for (int i = 0; i < size; i++)
+ {
+ Array.set(lastActual, i, actual[index + i]);
+ }
+
+ // put all into a new actual array of the appropriate size
+ Object[] newActual = new Object[index + 1];
+ System.arraycopy(actual, 0, newActual, 0, index);
+ newActual[index] = lastActual;
+
+ // replace the old actual array
+ actual = newActual;
+ }
+ return actual;
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelMethod#isCacheable()
+ */
+ @Override
+ public boolean isCacheable()
+ {
+ return true;
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelMethod#getMethodName()
+ */
+ @Override
+ public String getMethodName()
+ {
+ return method.getName();
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelMethod#getMethod()
+ */
+ @Override
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelMethod#getReturnType()
+ */
+ @Override
+ public Class<?> getReturnType()
+ {
+ return method.getReturnType();
+ }
+ }
+
+ /**
+ *
+ *
+ */
+ public static class VelGetterImpl implements VelPropertyGet
+ {
+ final AbstractExecutor getExecutor;
+
+ /**
+ * @param exec
+ */
+ public VelGetterImpl(AbstractExecutor exec)
+ {
+ getExecutor = exec;
+ }
+
+ private VelGetterImpl()
+ {
+ getExecutor = null;
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelPropertyGet#invoke(java.lang.Object)
+ */
+ @Override
+ public Object invoke(Object o)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ return getExecutor.execute(o);
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelPropertyGet#isCacheable()
+ */
+ @Override
+ public boolean isCacheable()
+ {
+ return true;
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelPropertyGet#getMethodName()
+ */
+ @Override
+ public String getMethodName()
+ {
+ return getExecutor.isAlive() ? getExecutor.getMethod().getName() : null;
+ }
+ }
+
+ /**
+ *
+ */
+ public static class VelSetterImpl implements VelPropertySet
+ {
+ private final SetExecutor setExecutor;
+
+ /**
+ * @param setExecutor
+ */
+ public VelSetterImpl(final SetExecutor setExecutor)
+ {
+ this.setExecutor = setExecutor;
+ }
+
+ private VelSetterImpl()
+ {
+ setExecutor = null;
+ }
+
+ /**
+ * Invoke the found Set Executor.
+ *
+ * @param o is the Object to invoke it on.
+ * @param value in the Value to set.
+ * @return The resulting Object.
+ */
+ @Override
+ public Object invoke(final Object o, final Object value)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ return setExecutor.execute(o, value);
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelPropertySet#isCacheable()
+ */
+ @Override
+ public boolean isCacheable()
+ {
+ return true;
+ }
+
+ /**
+ * @see org.apache.velocity.util.introspection.VelPropertySet#getMethodName()
+ */
+ @Override
+ public String getMethodName()
+ {
+ return setExecutor.isAlive() ? setExecutor.getMethod().getName() : null;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectPublicFields.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectPublicFields.java
new file mode 100644
index 00000000..59ef91cb
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/UberspectPublicFields.java
@@ -0,0 +1,139 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 org.apache.velocity.runtime.parser.node.PublicFieldExecutor;
+import org.apache.velocity.runtime.parser.node.SetPublicFieldExecutor;
+import org.apache.velocity.util.RuntimeServicesAware;
+import org.apache.velocity.util.introspection.UberspectImpl.VelGetterImpl;
+import org.apache.velocity.util.introspection.UberspectImpl.VelSetterImpl;
+import org.slf4j.Logger;
+
+import java.util.Iterator;
+
+/**
+ * Implementation of Uberspect to additionally provide access to public fields.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @author <a href="mailto:cdauth@cdauth.eu">Candid Dauth</a>
+ */
+public class UberspectPublicFields implements Uberspect, RuntimeServicesAware
+{
+ /**
+ * Our runtime logger.
+ */
+ protected Logger log;
+
+ /**
+ * the default Velocity introspector
+ */
+ protected Introspector introspector;
+
+ /**
+ * init - generates the Introspector. As the setup code
+ * makes sure that the log gets set before this is called,
+ * we can initialize the Introspector using the log object.
+ */
+ @Override
+ public void init()
+ {
+ introspector = new Introspector(log);
+ }
+
+ /**
+ * Property getter
+ * @param obj
+ * @param identifier
+ * @param i
+ * @return A Velocity Getter Method.
+ */
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
+ {
+ if (obj == null)
+ {
+ return null;
+ }
+
+ Class<?> claz = obj.getClass();
+
+ PublicFieldExecutor executor = new PublicFieldExecutor(log, introspector, claz, identifier);
+
+ return (executor.isAlive()) ? new VelGetterImpl(executor) : null;
+ }
+
+ /**
+ * Property setter
+ * @param obj
+ * @param identifier
+ * @param arg
+ * @param i
+ * @return A Velocity Setter method.
+ */
+ @Override
+ public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i)
+ {
+ if (obj == null)
+ {
+ return null;
+ }
+
+ Class<?> claz = obj.getClass();
+
+ SetPublicFieldExecutor executor = new SetPublicFieldExecutor(log, introspector, claz, identifier, arg);
+
+ return (executor.isAlive()) ? new VelSetterImpl(executor) : null;
+ }
+
+ /**
+ * @param obj
+ * @param info
+ * @return iterator
+ */
+ @Override
+ public Iterator getIterator(Object obj, Info info)
+ {
+ return null;
+ }
+
+ /**
+ * @param obj
+ * @param method
+ * @param args
+ * @param info
+ * @return method wrapper
+ */
+ @Override
+ public VelMethod getMethod(Object obj, String method, Object[] args, Info info)
+ {
+ return null;
+ }
+
+ /**
+ * @param rs RuntimeServices object assigned during initialization
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs)
+ {
+ log = rs.getLog("rendering");
+ }
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java
new file mode 100644
index 00000000..6c8869bd
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelMethod.java
@@ -0,0 +1,74 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Method used for regular method invocation
+ *
+ * $foo.bar()
+ *
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface VelMethod
+{
+ /**
+ * invocation method - called when the method invocation should be
+ * performed and a value returned
+ * @param o
+ * @param params
+ * @return The resulting object.
+ * @throws IllegalAccessException
+ * @throws InvocationTargetException
+ */
+ Object invoke(Object o, Object[] params)
+ throws IllegalAccessException, InvocationTargetException;
+
+ /**
+ * specifies if this VelMethod is cacheable and able to be
+ * reused for this class of object it was returned for
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ boolean isCacheable();
+
+ /**
+ * returns the method name used
+ * @return The method name used
+ */
+ String getMethodName();
+
+ /**
+ * returns the underlying Method
+ * @return the method
+ * @since 2.0
+ */
+ Method getMethod();
+
+ /**
+ * returns the return type of the method invoked
+ * @return The return type of the method invoked
+ */
+ Class<?> getReturnType();
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertyGet.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertyGet.java
new file mode 100644
index 00000000..1d0b9e23
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertyGet.java
@@ -0,0 +1,55 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 defining a 'getter'. For uses when looking for resolution of
+ * property references
+ *
+ * $foo.bar
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface VelPropertyGet
+{
+ /**
+ * invocation method - called when the 'get action' should be
+ * preformed and a value returned
+ * @param o
+ * @return The resulting Object.
+ * @throws Exception
+ */
+ Object invoke(Object o) throws Exception;
+
+ /**
+ * specifies if this VelPropertyGet is cacheable and able to be
+ * reused for this class of object it was returned for
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ boolean isCacheable();
+
+ /**
+ * returns the method name used to return this 'property'
+ * @return The method name used to return this 'property'
+ */
+ String getMethodName();
+}
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertySet.java b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertySet.java
new file mode 100644
index 00000000..6891b515
--- /dev/null
+++ b/velocity-engine-core/src/main/java/org/apache/velocity/util/introspection/VelPropertySet.java
@@ -0,0 +1,56 @@
+package org.apache.velocity.util.introspection;
+
+/*
+ * 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 for setting values that appear to be properties in
+ * Velocity. Ex.
+ *
+ * #set($foo.bar = "hello")
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public interface VelPropertySet
+{
+ /**
+ * method used to set the value in the object
+ *
+ * @param o Object on which the method will be called with the arg
+ * @param arg value to be set
+ * @return the value returned from the set operation (impl specific)
+ * @throws Exception
+ */
+ Object invoke(Object o, Object arg) throws Exception;
+
+ /**
+ * specifies if this VelPropertySet is cacheable and able to be
+ * reused for this class of object it was returned for
+ *
+ * @return true if can be reused for this class, false if not
+ */
+ boolean isCacheable();
+
+ /**
+ * returns the method name used to set this 'property'
+ * @return The method name used to set this 'property'
+ */
+ String getMethodName();
+}
diff --git a/velocity-engine-core/src/main/parser/Parser.jjt b/velocity-engine-core/src/main/parser/Parser.jjt
new file mode 100644
index 00000000..593d044b
--- /dev/null
+++ b/velocity-engine-core/src/main/parser/Parser.jjt
@@ -0,0 +1,2642 @@
+/*
+ * 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.
+ */
+
+/*
+ * NOTE : please see documentation at bottom of this file. (It was placed there its tiring
+ * to always have to page past it... :)
+ */
+options
+{
+ /** The default package for this parser kit. This is now done from Maven.
+ NODE_PACKAGE="org.apache.velocity.runtime.parser";
+ */
+
+ /** A source file will be generated for each non-terminal */
+ MULTI=true;
+
+ /**
+ * Each node will have access to the parser, I did this so
+ * some global information can be shared via the parser. I
+ * think this will come in handly keeping track of
+ * context, and being able to push changes back into
+ * the context when nodes make modifications to the
+ * context by setting properties, variables and
+ * what not.
+ */
+ NODE_USES_PARSER=true;
+
+ /**
+ * The parser must be non-static in order for the
+ * above option to work, otherwise the parser value
+ * is passed in as null, which isn't all the useful ;)
+ */
+ STATIC=false;
+
+ /**
+ * Enables the use of a visitor that each of nodes
+ * will accept. This way we can separate the logic
+ * of node processing in a visitor and out of the
+ * nodes themselves. If processing changes then
+ * the nothing has to change in the node code.
+ */
+ VISITOR=true;
+
+ /**
+ * Declare that we are accepting unicode input and
+ * that we are using a custom character stream class
+ * Note that the char stream class is really a slightly
+ * modified ASCII_CharStream, as it appears we are safe
+ * because we only deal with pre-encoding-converted
+ * Readers rather than raw input streams.
+ */
+ UNICODE_INPUT=true;
+ USER_CHAR_STREAM=true;
+
+ /**
+ * for debugging purposes. Those are now handled from within javacc-maven-plugin debugging flags in pom.xml
+ DEBUG_PARSER = true;
+ DEBUG_LOOKAHEAD = true;
+ DEBUG_TOKEN_MANAGER = true;
+ */
+
+
+}
+
+PARSER_BEGIN(${parser.basename}Parser)
+package ${parser.package};
+
+import java.io.*;
+import java.util.*;
+import org.apache.velocity.Template;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.parser.*;
+import org.apache.velocity.runtime.parser.node.*;
+import org.apache.velocity.runtime.directive.*;
+import org.apache.velocity.runtime.directive.MacroParseException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import static org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+
+import org.slf4j.Logger;
+
+/**
+ * This class is responsible for parsing a Velocity
+ * template. This class was generated by JavaCC using
+ * the JJTree extension to produce an Abstract
+ * Syntax Tree (AST) of the template.
+ *
+ * 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="hps@intermeta.de">Henning P. Schmiedehausen</a>
+ * @version $Id$
+*/
+public class ${parser.basename}Parser implements Parser
+{
+ /**
+ * Parser debugging flag.
+ * When debug is active, javacc Parser will contain (among other things)
+ * a trace_call() method. So we use the presence of this method to
+ * initialize our flag.
+ */
+ private static boolean debugParser;
+ static
+ {
+ try
+ {
+ ${parser.basename}Parser.class.getDeclaredMethod("trace_call", String.class);
+ debugParser = true;
+ }
+ catch(NoSuchMethodException nsfe)
+ {
+ debugParser = false;
+ }
+ }
+
+ /**
+ * Our own trace method. Use sparsingly in production, since each
+ * and every call will introduce an execution branch and slow down parsing.
+ */
+ public static void trace(String message)
+ {
+ if (debugParser) System.out.println(message);
+ }
+
+ /**
+ * Keep track of defined macros, used for escape processing
+ */
+ private Map macroNames = new HashMap();
+
+ /**
+ * Current template we are parsing. Passed to us in parse()
+ */
+ public Template currentTemplate = null;
+
+ /**
+ * Set to true if the property
+ * RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE is set to true
+ */
+ public boolean strictEscape = false;
+
+ /**
+ * Set to true if the propoerty
+ * RuntimeConstants.PARSER_HYPHEN_ALLOWED is set to true
+ */
+ public boolean hyphenAllowedInIdentifiers = false;
+
+ VelocityCharStream velcharstream = null;
+
+ private RuntimeServices rsvc = null;
+
+ @Override
+ public RuntimeServices getRuntimeServices()
+ {
+ return rsvc;
+ }
+
+ private Logger log = null;
+
+ /**
+ * This constructor was added to allow the re-use of parsers.
+ * The normal constructor takes a single argument which
+ * an InputStream. This simply creates a re-usable parser
+ * object, we satisfy the requirement of an InputStream
+ * by using a newline character as an input stream.
+ */
+ public ${parser.basename}Parser( RuntimeServices rs)
+ {
+ /*
+ * need to call the CTOR first thing.
+ */
+
+ this( new VelocityCharStream(
+ new ByteArrayInputStream("\n".getBytes()), 1, 1 ));
+
+ /*
+ * then initialize logger
+ */
+
+ log = rs.getLog("parser");
+
+
+ /*
+ * now setup a VCS for later use
+ */
+ velcharstream = new VelocityCharStream(
+ new ByteArrayInputStream("\n".getBytes()), 1, 1 );
+
+
+ strictEscape =
+ rs.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, false);
+
+ hyphenAllowedInIdentifiers =
+ rs.getBoolean(RuntimeConstants.PARSER_HYPHEN_ALLOWED, false);
+
+ /*
+ * and save the RuntimeServices
+ */
+ rsvc = rs;
+
+ /*
+ * then initialize customizable characters
+ */
+ dollar = '${parser.char.dollar}';
+ hash = '${parser.char.hash}';
+ at = '${parser.char.at}';
+ asterisk = '${parser.char.asterisk}';
+ }
+
+ /**
+ * This was also added to allow parsers to be
+ * re-usable. Normal JavaCC use entails passing an
+ * input stream to the constructor and the parsing
+ * process is carried out once. We want to be able
+ * to re-use parsers: we do this by adding this
+ * method and re-initializing the lexer with
+ * the new stream that we want parsed.
+ */
+ @Override
+ public SimpleNode parse( Reader reader, Template template )
+ throws ParseException
+ {
+ SimpleNode sn = null;
+
+ currentTemplate = template;
+
+ try
+ {
+ token_source.clearStateVars();
+
+ /*
+ * reinitialize the VelocityCharStream
+ * with the new reader
+ */
+ velcharstream.ReInit( reader, 1, 1 );
+
+ /*
+ * now reinit the Parser with this CharStream
+ */
+ ReInit( velcharstream );
+
+ /*
+ * do that voodoo...
+ */
+ sn = process();
+ }
+ catch (MacroParseException mee)
+ {
+ /*
+ * thrown by the Macro class when something is amiss in the
+ * Macro specification
+ */
+ log.error("{}: {}", template.getName(), mee.getMessage(), mee);
+ throw mee;
+ }
+ catch (ParseException pe)
+ {
+ log.error("{}: {}", currentTemplate.getName(), pe.getMessage());
+ throw new TemplateParseException (pe.currentToken,
+ pe.expectedTokenSequences, pe.tokenImage, currentTemplate.getName());
+ }
+ catch (TokenMgrError tme)
+ {
+ throw new ParseException("Lexical error: " + tme.toString());
+ }
+ catch (Exception e)
+ {
+ String msg = template.getName() + ": " + e.getMessage();
+ log.error(msg, e);
+ throw new VelocityException(msg, e, getRuntimeServices().getLogContext().getStackTrace());
+ }
+
+ currentTemplate = null;
+
+ return sn;
+ }
+
+ /**
+ * This method gets a Directive from the directives Hashtable
+ */
+ @Override
+ public Directive getDirective(String directive)
+ {
+ return (Directive) rsvc.getDirective(directive);
+ }
+
+ /**
+ * This method finds out of the directive exists in the directives Map.
+ */
+ @Override
+ public boolean isDirective(String directive)
+ {
+ return rsvc.getDirective(directive) != null;
+ }
+
+
+ /**
+ * Produces a processed output for an escaped control or
+ * pluggable directive
+ */
+ private String escapedDirective( String strImage )
+ {
+ int iLast = strImage.lastIndexOf("\\");
+
+ String strDirective = strImage.substring(iLast + 1);
+
+ boolean bRecognizedDirective = false;
+
+ // we don't have to call substring method all the time in this method
+ String dirTag = strDirective.substring(1);
+ if (dirTag.charAt(0) == '{')
+ {
+ dirTag = dirTag.substring(1, dirTag.length() - 1);
+ }
+
+ /*
+ * If this is a predefined derective or if we detect
+ * a macro definition (this is aproximate at best) then
+ * we absorb the forward slash. If in strict reference
+ * mode then we always absord the forward slash regardless
+ * if the derective is defined or not.
+ */
+
+ if (strictEscape
+ || isDirective(dirTag)
+ || macroNames.containsKey(dirTag)
+ || rsvc.isVelocimacro(dirTag, currentTemplate))
+ {
+ bRecognizedDirective = true;
+ }
+ else
+ {
+ /* order for speed? */
+
+ if ( dirTag.equals("if")
+ || dirTag.equals("end")
+ || dirTag.equals("set")
+ || dirTag.equals("else")
+ || dirTag.equals("elseif")
+ )
+ {
+ bRecognizedDirective = true;
+ }
+ }
+
+ /*
+ * if so, make the proper prefix string (let the escapes do their thing..)
+ * otherwise, just return what it is..
+ */
+
+ if (bRecognizedDirective)
+ return ( strImage.substring(0,iLast/2) + strDirective);
+ else
+ return ( strImage );
+ }
+
+ /**
+ * Check whether there is a left parenthesis with leading optional
+ * whitespaces. This method is used in the semantic look ahead of
+ * Directive method. This is done in code instead of as a production
+ * for simplicity and efficiency.
+ */
+ private boolean isLeftParenthesis()
+ {
+ char c;
+ int no = 0;
+ try {
+ while(true)
+ {
+ /**
+ * Read a character
+ */
+ c = velcharstream.readChar();
+ no++;
+ if (c == '(')
+ {
+ return true;
+ }
+ /**
+ * if not a white space return
+ */
+ else if (c != ' ' && c != '\n' && c != '\r' && c != '\t')
+ {
+ return false;
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ }
+ finally
+ {
+ /**
+ * Backup the stream to the initial state
+ */
+ velcharstream.backup(no);
+ }
+ return false;
+ }
+
+ /**
+ * Check whether there is a right parenthesis with leading optional
+ * whitespaces. This method is used in the semantic look ahead of
+ * Directive method. This is done in code instead of as a production
+ * for simplicity and efficiency.
+ */
+ private boolean isRightParenthesis()
+ {
+ char c;
+ int no = -1;
+ try {
+ while(true)
+ {
+ /**
+ * Read a character
+ */
+ if (no == -1)
+ {
+ switch (getToken(1).kind)
+ {
+ case RPAREN:
+ return true;
+ case WHITESPACE:
+ case NEWLINE:
+ no = 0;
+ break;
+ default:
+ return false;
+ }
+ }
+ c = velcharstream.readChar();
+ no++;
+ if (c == ')')
+ {
+ return true;
+ }
+ /**
+ * if not a white space return
+ */
+ else if (c != ' ' && c != '\n' && c != '\r' && c != '\t')
+ {
+ return false;
+ }
+ }
+ }
+ catch(IOException e)
+ {
+ }
+ finally
+ {
+ /**
+ * Backup the stream to the initial state
+ */
+ if (no > 0) velcharstream.backup(no);
+ }
+ return false;
+ }
+
+ /**
+ * We use this method in a lookahead to determine if we are in a macro
+ * default value assignment. The standard lookahead is not smart enough.
+ * here we look for the equals after the reference.
+ */
+ private boolean isAssignment()
+ {
+ // Basically if the last character read was not '$' then false
+ if (token_source.getCurrentLexicalState() != REFERENCE) return false;
+
+ char c = ' ';
+ int backup = 0;
+ try
+ {
+ // Read through any white space
+ while(Character.isWhitespace(c))
+ {
+ c = velcharstream.readChar();
+ backup++;
+ }
+
+ // This is what we are ultimately looking for
+ if (c != '=') return false;
+ }
+ catch (IOException e)
+ {
+ }
+ finally
+ {
+ velcharstream.backup(backup);
+ }
+
+ return true;
+ }
+
+ @Override
+ public Template getCurrentTemplate()
+ {
+ return currentTemplate;
+ }
+
+ @Override
+ public void resetCurrentTemplate()
+ {
+ currentTemplate = null;
+ }
+
+ @Override
+ public char dollar()
+ {
+ return dollar;
+ }
+
+ @Override
+ public char hash()
+ {
+ return hash;
+ }
+
+ @Override
+ public char at()
+ {
+ return at;
+ }
+
+ @Override
+ public char asterisk()
+ {
+ return asterisk;
+ }
+
+ private char dollar = '$';
+ private char hash = '#';
+ private char at = '@';
+ private char asterisk = '*';
+}
+
+PARSER_END(${parser.basename}Parser)
+
+TOKEN_MGR_DECLS:
+{
+ private int fileDepth = 0;
+
+ private int lparen = 0;
+ private int rparen = 0;
+ private int curlyLevel = 0;
+ List stateStack = new ArrayList(50);
+
+ private boolean inComment;
+ private boolean inSet;
+
+ /**
+ * Our own trace method. Use sparsingly in production, since each
+ * and every call will introduce an execution branch and slow down parsing.
+ */
+ public static void trace(String message)
+ {
+ ${parser.basename}Parser.trace(message);
+ }
+
+ /**
+ * Switches to a new state (add some log to the default method)
+ */
+ public void switchTo(int lexState)
+ {
+ trace(" switch to " + lexStateNames[lexState]);
+ SwitchTo(lexState);
+ }
+
+ public int getCurrentLexicalState()
+ {
+ return curLexState;
+ }
+
+ /**
+ * pops a state off the stack, and restores paren counts
+ *
+ * @return boolean : success of operation
+ */
+ public boolean stateStackPop()
+ {
+ ParserState s;
+ try
+ {
+ s = (ParserState) stateStack.remove(stateStack.size() - 1); // stack.pop
+ }
+ catch(IndexOutOfBoundsException e)
+ {
+ // empty stack
+ lparen=0;
+ switchTo(DEFAULT);
+ return false;
+ }
+
+ trace(" stack pop (" + stateStack.size() + ")");
+ lparen = s.lparen;
+ rparen = s.rparen;
+ curlyLevel = s.curlyLevel;
+
+ switchTo(s.lexstate);
+
+ return true;
+ }
+
+ /**
+ * pushes the current state onto the 'state stack',
+ * and maintains the parens counts
+ * public because we need it in PD &amp; VM handling
+ *
+ * @return boolean : success. It can fail if the state machine
+ * gets messed up (do don't mess it up :)
+ */
+ public boolean stateStackPush()
+ {
+ trace(" (" + stateStack.size() + ") pushing cur state : " + lexStateNames[curLexState] );
+
+ ParserState s = new ParserState();
+ s.lparen = lparen;
+ s.rparen = rparen;
+ s.curlyLevel = curlyLevel;
+ s.lexstate = curLexState;
+
+ stateStack.add(s); // stack.push
+
+ lparen = 0;
+ curlyLevel = 0;
+
+ return true;
+ }
+
+ /**
+ * Clears all state variables, resets to
+ * start values, clears stateStack. Call
+ * before parsing.
+ */
+ public void clearStateVars()
+ {
+ stateStack.clear();
+
+ lparen = 0;
+ rparen = 0;
+ curlyLevel = 0;
+ inComment = false;
+ inSet = false;
+
+ return;
+ }
+
+ public void setInSet(boolean value)
+ {
+ inSet = value;
+ }
+
+ public boolean isInSet()
+ {
+ return inSet;
+ }
+
+ /**
+ * Holds the state of the parsing process.
+ */
+ private static class ParserState
+ {
+ int lparen;
+ int rparen;
+ int curlyLevel;
+ int lexstate;
+ }
+
+ /**
+ * handles the dropdown logic when encountering a RPAREN
+ */
+ private void RPARENHandler()
+ {
+ /*
+ * Ultimately, we want to drop down to the state below
+ * the one that has an open (if we hit bottom (DEFAULT),
+ * that's fine. It's just text schmoo.
+ */
+
+ boolean closed = false;
+
+ if (inComment)
+ closed = true;
+
+ while( !closed )
+ {
+ /*
+ * look at current state. If we haven't seen a lparen
+ * in this state then we drop a state, because this
+ * lparen clearly closes our state
+ */
+
+ if( lparen > 0)
+ {
+ /*
+ * if rparen + 1 == lparen, then this state is closed.
+ * Otherwise, increment and keep parsing
+ */
+
+ if( lparen == rparen + 1)
+ {
+ stateStackPop();
+ }
+ else
+ {
+ rparen++;
+ }
+
+ closed = true;
+ }
+ else
+ {
+ /*
+ * now, drop a state
+ */
+
+ if(!stateStackPop())
+ break;
+ }
+ }
+ }
+}
+
+/* ------------------------------------------------------------------------
+ *
+ * Tokens
+ *
+ * ------------------------------------------------------------------------- */
+
+/* The VelocityCharStream will send a zero-width whitespace
+ just before EOF to let us accept a terminal $ or #
+*/
+<PRE_DIRECTIVE,PRE_REFERENCE,PRE_OLD_REFERENCE>
+TOKEN :
+{
+ <LONE_SYMBOL: "\u001C" >
+ {
+ stateStackPop();
+ }
+}
+
+/* In all other states, keep the zero-width whitespace for now */
+<REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD3,REFINDEX,DIRECTIVE,REFMOD2,DEFAULT,REFMOD,IN_TEXTBLOCK,IN_MULTILINE_COMMENT,IN_FORMAL_COMMENT,IN_SINGLE_LINE_COMMENT>
+TOKEN :
+{
+ <ZERO_WIDTH_WHITESPACE: "\u001C">
+}
+
+<REFERENCE, REFMODIFIER, OLD_REFMODIFIER, REFMOD3>
+TOKEN:
+{
+ <INDEX_LBRACKET: "[">
+ {
+ stateStackPush();
+ switchTo(REFINDEX);
+ }
+ |
+ /* we need to give precedence to the logical 'or' here, it's a hack to avoid multiplying parsing modes */
+ <LOGICAL_OR_2: "||">
+ {
+ stateStackPop();
+ }
+ |
+ <PIPE: "|">
+ {
+ if (curlyLevel == 1)
+ {
+ switchTo(ALT_VAL);
+ }
+ else
+ {
+ stateStackPop();
+ }
+ }
+}
+
+<REFINDEX>
+TOKEN:
+{
+ <INDEX_RBRACKET: "]">
+ {
+ stateStackPop();
+ }
+}
+
+
+<DIRECTIVE,REFMOD2,ALT_VAL>
+TOKEN:
+{
+ <LBRACKET: "[">
+| <RBRACKET: "]">
+| <COMMA:",">
+}
+
+<DIRECTIVE,REFMOD2,ALT_VAL>
+TOKEN:
+{
+ <DOUBLEDOT : ".." >
+}
+
+<DIRECTIVE, REFMOD2,ALT_VAL>
+TOKEN:
+{
+ <COLON : ":" >
+}
+
+<DIRECTIVE, REFMOD2, ALT_VAL>
+TOKEN :
+{
+ <LEFT_CURLEY : "{" >
+ {
+ ++curlyLevel;
+ }
+ |
+ <RIGHT_CURLEY : "}" >
+ {
+ --curlyLevel;
+ if (curLexState == ALT_VAL && curlyLevel == 0)
+ {
+ stateStackPop();
+ }
+ }
+}
+
+<DIRECTIVE,REFMODIFIER,OLD_REFMODIFIER>
+TOKEN:
+{
+ <LPAREN: "(">
+ {
+ if (!inComment)
+ lparen++;
+
+ /*
+ * If in REFERENCE and we have seen the dot, then move
+ * to REFMOD2 -> Modifier()
+ */
+
+ if (curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER )
+ switchTo( REFMOD2 );
+ }
+}
+
+/*
+ * we never will see a ')' in anything but DIRECTIVE and REFMOD2.
+ * Each have their own
+ */
+<DIRECTIVE>
+TOKEN:
+{
+ <RPAREN: ")">
+ {
+ RPARENHandler();
+ }
+}
+
+
+<REFMOD2>
+TOKEN:
+{
+ /*
+ * in REFMOD2, we don't want to bind the whitespace and \n like we
+ * do when closing a directive.
+ */
+ <REFMOD2_RPAREN: ")">
+ {
+ /*
+ * need to simply switch back to REFERENCE, not drop down the stack
+ * because we can (infinitely) chain, ala
+ * $foo.bar().blargh().woogie().doogie()
+ */
+
+ switchTo( REFMOD3 );
+ }
+}
+
+/*----------------------------------------------
+ *
+ * escape "\\" handling for the built-in directives
+ *
+ *--------------------------------------------- */
+TOKEN:
+{
+ /*
+ * We have to do this, because we want these to be a Text node, and
+ * whatever follows to be peer to this text in the tree.
+ *
+ * We need to touch the ASTs for these, because we want an even # of \'s
+ * to render properly in front of the block
+ *
+ * This is really simplistic. I actually would prefer to find them in
+ * grammatical context, but I am neither smart nor rested, a receipe
+ * for disaster, another long night with Mr. Parser, or both.
+ */
+
+ <ESCAPE_DIRECTIVE : (<DOUBLE_ESCAPE>)* "\\${parser.char.hash}" (<WORD> | <BRACKETED_WORD>) >
+}
+
+
+/*
+ * We added the lexical states REFERENCE, REFMODIFIER, REFMOD2 to
+ * address JIRA issue VELOCITY-631. With SET_DIRECTIVE only in the
+ * DEFAULT lexical state the following VTL fails "$a#set($b = 1)"
+ * because the Reference token uses LOOKAHEAD(2) combined with the
+ * fact that we explicity set the lex state to REFERENCE with the $
+ * token, which means we would never evaluate this token during the
+ * look ahead. This general issue is disscussed here:
+ *
+ * http://www.engr.mun.ca/~theo/JavaCC-FAQ/javacc-faq-ie.htm#tth_sEc3.12
+ *
+ */
+<DEFAULT, PRE_REFERENCE, PRE_OLD_REFERENCE, REFERENCE, REFMODIFIER, OLD_REFMODIFIER, REFMOD2, REFMOD3>
+TOKEN:
+{
+ <SET_DIRECTIVE: ("${parser.char.hash}set" | "${parser.char.hash}{set}") (" "|"\t")* "(">
+ {
+ if (! inComment)
+ {
+ trace(" #set : going to DIRECTIVE" );
+
+ stateStackPush();
+ setInSet(true);
+ switchTo(DIRECTIVE);
+ }
+
+ /*
+ * need the LPAREN action
+ */
+
+ if (!inComment)
+ {
+ lparen++;
+
+ /*
+ * If in REFERENCE and we have seen the dot, then move
+ * to REFMOD2 -> Modifier()
+ */
+
+ if (curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER )
+ switchTo( REFMOD2 );
+ }
+ }
+}
+
+<*>
+MORE :
+{
+ /*
+ * Note : DOLLARBANG is a duplicate of DOLLAR. They must be identical.
+ */
+
+ <DOLLAR: ("\\")* "${parser.char.dollar}">
+ {
+ if (! inComment)
+ {
+ /*
+ * if we find ourselves in REFERENCE or PRE_REFERENCE, we need to pop down
+ * to end the previous ref
+ */
+
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE)
+ {
+ stateStackPop();
+ }
+
+ int preReferenceState = parser.hyphenAllowedInIdentifiers ? PRE_OLD_REFERENCE : PRE_REFERENCE;
+
+ trace( " $ : going to " + lexStateNames[preReferenceState]);
+
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo(preReferenceState);
+ }
+ }
+
+| <DOLLARBANG: ("\\")* "${parser.char.dollar}" ("\\")* "!">
+ {
+ if (! inComment)
+ {
+ /*
+ * if we find ourselves in REFERENCE or PRE_REFERENCE, we need to pop down
+ * to end the previous ref
+ */
+
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE)
+ {
+ stateStackPop();
+ }
+
+ int preReferenceState = parser.hyphenAllowedInIdentifiers ? PRE_OLD_REFERENCE : PRE_REFERENCE;
+
+ trace( " $ : going to " + lexStateNames[preReferenceState]);
+
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo(preReferenceState);
+ }
+ }
+
+| "${parser.char.hash}[["
+ {
+ if (!inComment)
+ {
+ inComment = true;
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo( IN_TEXTBLOCK );
+ }
+ }
+
+| <"${parser.char.hash}${parser.char.asterisk}${parser.char.asterisk}" ~["${parser.char.hash}","\u001C"]>
+ {
+ if (!inComment)
+ {
+ input_stream.backup(1);
+ inComment = true;
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo( IN_FORMAL_COMMENT);
+ }
+ }
+
+| "${parser.char.hash}${parser.char.asterisk}"
+ {
+ if (!inComment)
+ {
+ inComment=true;
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo( IN_MULTI_LINE_COMMENT );
+ }
+ }
+
+| <HASH : "${parser.char.hash}" >
+ {
+ if (! inComment)
+ {
+ /*
+ * We can have the situation where #if($foo)$foo#end.
+ * We need to transition out of REFERENCE before going to DIRECTIVE.
+ * I don't really like this, but I can't think of a legal way
+ * you are going into DIRECTIVE while in REFERENCE. -gmj
+ */
+
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE || curLexState == REFMODIFIER || curLexState == OLD_REFMODIFIER )
+ {
+ stateStackPop();
+ }
+
+ trace(" # : going to PRE_DIRECTIVE" );
+
+ /* do not push PRE states */
+ if (curLexState != PRE_REFERENCE && curLexState != PRE_DIRECTIVE && curLexState != PRE_OLD_REFERENCE)
+ {
+ stateStackPush();
+ }
+ switchTo(PRE_DIRECTIVE);
+ }
+ }
+}
+
+// treat the single line comment case separately
+// to avoid ##<EOF> errors
+<DEFAULT,PRE_DIRECTIVE,DIRECTIVE,REFERENCE,PRE_REFERENCE,PRE_OLD_REFERENCE,REFMOD2,REFMOD3,REFMODIFIER,OLD_REFMODIFIER>
+TOKEN :
+{
+ <SINGLE_LINE_COMMENT_START: "${parser.char.hash}${parser.char.hash}">
+ {
+ if (!inComment)
+ {
+ if (curLexState == REFERENCE || curLexState == PRE_REFERENCE || curLexState == PRE_OLD_REFERENCE)
+ {
+ stateStackPop();
+ }
+
+ inComment = true;
+ stateStackPush();
+ switchTo(IN_SINGLE_LINE_COMMENT);
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------
+ *
+ * *_COMMENT Lexical tokens
+ *
+ *-----------------------------------------------------------------------*/
+<IN_SINGLE_LINE_COMMENT>
+TOKEN :
+{
+ <SINGLE_LINE_COMMENT: "\n" | "\r" | "\r\n">
+ {
+ inComment = false;
+ stateStackPop();
+ if (curLexState == REFERENCE || curLexState == REFMOD3)
+ {
+ // end of reference: pop again
+ stateStackPop();
+ }
+ }
+
+}
+
+<IN_FORMAL_COMMENT>
+TOKEN :
+{
+ <FORMAL_COMMENT: "${parser.char.asterisk}${parser.char.hash}" >
+ {
+ inComment = false;
+ stateStackPop();
+ if (curLexState == REFERENCE || curLexState == REFMOD3)
+ {
+ // end of reference: pop again
+ stateStackPop();
+ }
+ }
+}
+
+<IN_MULTI_LINE_COMMENT>
+TOKEN :
+{
+ <MULTI_LINE_COMMENT: "${parser.char.asterisk}${parser.char.hash}" >
+ {
+ inComment = false;
+ stateStackPop();
+ if (curLexState == REFERENCE || curLexState == REFMOD3)
+ {
+ // end of reference: pop again
+ stateStackPop();
+ }
+ }
+}
+
+<IN_TEXTBLOCK>
+TOKEN :
+{
+ <TEXTBLOCK: "]]${parser.char.hash}" >
+ {
+ inComment = false;
+ stateStackPop();
+ }
+}
+
+<IN_SINGLE_LINE_COMMENT,IN_FORMAL_COMMENT,IN_MULTI_LINE_COMMENT>
+SKIP :
+{
+ < ~[] >
+}
+
+<IN_TEXTBLOCK>
+MORE :
+{
+ < ~["\u001C"] >
+}
+
+/* -----------------------------------------------------------------------
+ *
+ * DIRECTIVE Lexical State (some of it, anyway)
+ *
+ * ---------------------------------------------------------------------- */
+
+<DEFAULT,REFINDEX,REFMOD2,DIRECTIVE,ALT_VAL>
+TOKEN:
+{
+ <WHITESPACE : ([" ","\t"])+>
+| <NEWLINE : ("\n" | "\r" | "\r\n") >
+ {
+ trace(" NEWLINE :");
+
+ /* if (isInSet()) */
+ setInSet(false);
+ }
+}
+
+/* needed for stuff like #foo() followed by ( '$' | '#' )* followed by ( <WHITESPACE> | <ENDLINE> )
+ so that directive postfix doesn't eat the '$'s and '#'s
+*/
+<PRE_DIRECTIVE, PRE_REFERENCE, PRE_OLD_REFERENCE>
+TOKEN:
+{
+ <SUFFIX: ([" ","\t"])* ("\n" | "\r" | "\r\n")>
+ {
+ stateStackPop();
+ }
+}
+
+<DIRECTIVE,REFMOD2,REFINDEX,ALT_VAL>
+TOKEN :
+{
+// <STRING_LITERAL: ( "\"" ( ~["\"","\n","\r"] )* "\"" ) | ( "'" ( ~["'","\n","\r"] )* "'" ) >
+ < STRING_LITERAL:
+ ("\""
+ ( (~["\"","\u001C"])
+ | ("\\"
+ ( ["n","t","b","r","f"]
+ | ["0"-"7"] ( ["0"-"7"] )?
+ | ["0"-"3"] ["0"-"7"] ["0"-"7"]
+ | "u" ["0"-"9", "a"-"f", "A"-"F"] ["0"-"9", "a"-"f", "A"-"F"] ["0"-"9", "a"-"f", "A"-"F"] ["0"-"9", "a"-"f", "A"-"F"]
+ )
+ )
+ | ("\"\"")
+ | ( "\\" (" ")* "\n")
+ )*
+ "\""
+ )
+ |
+ ("\'"
+ ( (~["\'","\u001C"])
+ | ("''")
+ | ( "\\" (" ")* "\n")
+ )*
+ "\'"
+ )
+ >
+
+ {
+ /*
+ * - if we are in DIRECTIVE and haven't seen ( yet, then also drop out.
+ * don't forget to account for the beloved yet wierd #set
+ * - finally, if we are in REFMOD2 (remember : $foo.bar( ) then " is ok!
+ */
+
+ if( curLexState == DIRECTIVE && !isInSet() && lparen == 0)
+ stateStackPop();
+ }
+}
+
+<REFERENCE,DIRECTIVE,REFMODIFIER,OLD_REFMODIFIER,REFMOD2,REFINDEX,ALT_VAL>
+TOKEN:
+{
+ <TRUE: "true">
+| <FALSE: "false">
+}
+
+<DIRECTIVE,REFMOD2,REFINDEX,ALT_VAL>
+TOKEN :
+{
+ <MINUS: "-">
+| <PLUS: "+">
+| <MULTIPLY: "*">
+| <DIVIDE: "/">
+| <MODULUS: "%">
+| <LOGICAL_AND: "&&" | "and" >
+| <LOGICAL_OR: "||" | "or" >
+| <LOGICAL_LT: "<" | "lt" >
+| <LOGICAL_LE: "<=" | "le" >
+| <LOGICAL_GT: ">" | "gt" >
+| <LOGICAL_GE: ">=" | "ge" >
+| <LOGICAL_EQUALS: "==" | "eq" >
+| <LOGICAL_NOT_EQUALS: "!=" | "ne" >
+| <LOGICAL_NOT: "!" | "not" >
+| <EQUALS: "=" >
+}
+
+<PRE_DIRECTIVE>
+TOKEN :
+{
+ <END: ( "end" | "{end}" )>
+ {
+ stateStackPop();
+ }
+
+| <IF_DIRECTIVE: "if" | "{if}">
+ {
+ switchTo(DIRECTIVE);
+ }
+
+| <ELSEIF: "elseif" | "{elseif}">
+ {
+ switchTo(DIRECTIVE);
+ }
+
+| <ELSE: "else" | "{else}">
+ {
+ stateStackPop();
+ }
+}
+
+<PRE_DIRECTIVE,DIRECTIVE,REFMOD2,REFINDEX,ALT_VAL>
+TOKEN:
+{
+ <#DIGIT: [ "0"-"9" ] >
+
+ /*
+ * treat FLOATING_POINT_LITERAL and INTEGER_LITERAL differently as a range can only handle integers.
+ */
+
+ /**
+ * Note -- we also define an integer as ending with a double period,
+ * in order to avoid 1..3 being defined as floating point (1.) then a period, then a integer
+ */
+| <INTEGER_LITERAL: ("-")? (<DIGIT>)+ ("..")? >
+ {
+
+ /*
+ * Remove the double period if it is there
+ */
+ if (matchedToken.image.endsWith("..")) {
+ input_stream.backup(2);
+ matchedToken.image = matchedToken.image.substring(0,matchedToken.image.length()-2);
+ }
+
+ /*
+ * check to see if we are in set
+ * ex. #set($foo = $foo + 3)
+ * because we want to handle the \n after
+ */
+
+ if ( lparen == 0 && !isInSet() && curLexState != REFMOD2 && curLexState != REFINDEX && curLexState != ALT_VAL)
+ {
+ stateStackPop();
+ }
+ }
+
+| <FLOATING_POINT_LITERAL:
+ ("-")? (<DIGIT>)+ "." (<DIGIT>)* (<EXPONENT>)?
+ | ("-")? "." (<DIGIT>)+ (<EXPONENT>)?
+ | ("-")? (<DIGIT>)+ <EXPONENT>
+ >
+ {
+ /*
+ * check to see if we are in set
+ * ex. #set $foo = $foo + 3
+ * because we want to handle the \n after
+ */
+
+ if ( lparen == 0 && !isInSet() && curLexState != REFMOD2 && curLexState != ALT_VAL)
+ {
+ stateStackPop();
+ }
+}
+|
+ <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >
+
+}
+
+/**
+ * TODO, the "@" symbol for block macros to be correct really should prefix WORD
+ * and BRACKETED_WORD, e.g., <WORD ["@"] ( <LETTER... etc...
+ * However, having the conditional character at the beginning screws up
+ * Macro parse. As it is now you can have #@1234 defined as a macro
+ * Which is not correct.
+ */
+
+<PRE_DIRECTIVE,DIRECTIVE>
+TOKEN:
+{
+ <#LETTER: [ "a"-"z", "A"-"Z" ] >
+| <#DIRECTIVE_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_" ] >
+| <WORD: ( <LETTER> | ["_"] | ["${parser.char.at}"]) (<DIRECTIVE_CHAR>)* >
+| <BRACKETED_WORD: "{" ( <LETTER> | ["_"] | ["${parser.char.at}"]) (<DIRECTIVE_CHAR>)* "}" >
+}
+
+/* -----------------------------------------------------------------------
+ *
+ * REFERENCE Lexical States
+ *
+ * This is more than a single state, because of the structure of
+ * the VTL references. We use three states because the set of tokens
+ * for each state can be different.
+ *
+ * $foo.bar( "arg" )
+ * ^ ^ ^ ^ ^
+ * | | | | |
+ * |_________________ > PRE_REFERENCE : state initiated by the '$' character.
+ * | | | | (or PRE_OLD_REFERENCE if '-' is allowed in identifiers)
+ * |________________> REFERENCE : state initiated by the identifier. Continues
+ * | | | until end of the reference, or the . character.
+ * |_____________ > REFMODIFIER : state switched to when the <DOT> is encountered.
+ * | | (or OLD_REFMODIFIER if '-' is allowed in identifiers)
+ * | | note that this is a switch, not a push. See notes at bottom.
+ * |_________ > REFMOD2 : state switch to when the LPAREN is encountered.
+ * | again, this is a switch, not a push.
+ * |_ > REFMOD3 : state only checking for a possible '.' or '[' continuation.
+ *
+ * During the REFERENCE, REFMODIFIER or REFMOD3 lex states we will switch to:
+ * - REFINDEX if a bracket '[' is encountered: $foo[1], $foo.bar[1], $foo.bar( "arg" )[1]
+ * - ALT_VAL if a pipe '|' is encountered (only for formal references): ${foo|'foo'}
+ * ---------------------------------------------------------------------------- */
+
+<PRE_REFERENCE,REFMODIFIER,REFMOD2>
+TOKEN :
+{
+ <#ALPHA_CHAR: ["a"-"z", "A"-"Z", "_"] >
+| <#IDENTIFIER_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_" ] >
+| <IDENTIFIER: ( <ALPHA_CHAR> ) (<IDENTIFIER_CHAR>)* >
+ {
+ if (curLexState == PRE_REFERENCE)
+ {
+ switchTo(REFERENCE);
+ }
+ }
+}
+
+<PRE_OLD_REFERENCE,OLD_REFMODIFIER>
+TOKEN :
+{
+ <#OLD_ALPHA_CHAR: ["a"-"z", "A"-"Z", "_"] >
+| <#OLD_IDENTIFIER_CHAR: [ "a"-"z", "A"-"Z", "0"-"9", "_", "-" ] >
+| <OLD_IDENTIFIER: ( <OLD_ALPHA_CHAR> ) (<OLD_IDENTIFIER_CHAR>)* >
+ {
+ if (curLexState == PRE_OLD_REFERENCE)
+ {
+ switchTo(REFERENCE);
+ }
+ }
+}
+
+
+<REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD2,REFMOD3>
+TOKEN:
+{
+ <DOT: "." <ALPHA_CHAR>>
+ {
+ /*
+ * push the alpha char back into the stream so the following identifier
+ * is complete
+ */
+
+ input_stream.backup(1);
+
+ /*
+ * and munge the <DOT> so we just get a . when we have normal text that
+ * looks like a ref.ident
+ */
+
+ matchedToken.image = ".";
+
+ int refModifierState = parser.hyphenAllowedInIdentifiers ? OLD_REFMODIFIER : REFMODIFIER;
+
+ trace("DOT : switching to " + lexStateNames[refModifierState]);
+ switchTo(refModifierState);
+
+ }
+}
+
+<PRE_REFERENCE,PRE_OLD_REFERENCE,REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD3>
+TOKEN :
+{
+ <LCURLY: "{">
+ {
+ ++curlyLevel;
+ }
+| <RCURLY: "}">
+ {
+ /* maybe it wasn't for our state */
+ while (curlyLevel == 0 && curLexState != DEFAULT)
+ {
+ stateStackPop();
+ }
+ /* At this point, here are all the possible states:
+ * - DEFAULT, which means the '}' is schmoo
+ * - DIRECTIVE or REFMOD2, which means the '}' is a closing map curly
+ * - one of the other REFERENCE states or ALT_VAL, which means the '}' ends the reference
+ * If we're in the last case, pop up state.
+ */
+ if (curLexState != DEFAULT && curLexState != DIRECTIVE && curLexState != REFMOD2)
+ {
+ stateStackPop();
+ }
+ }
+}
+
+<PRE_REFERENCE,PRE_OLD_REFERENCE,REFERENCE,REFMODIFIER,OLD_REFMODIFIER,REFMOD,REFMOD3>
+SPECIAL_TOKEN :
+{
+ <REFERENCE_TERMINATOR: ~[] >
+ {
+ /*
+ * push every terminator character back into the stream
+ */
+
+ input_stream.backup(1);
+
+ trace("REF_TERM :");
+
+ stateStackPop();
+ }
+}
+
+<PRE_DIRECTIVE>
+SPECIAL_TOKEN :
+{
+ <DIRECTIVE_TERMINATOR: ~[] >
+ {
+ trace("DIRECTIVE_TERM :");
+
+ input_stream.backup(1);
+ stateStackPop();
+ }
+}
+
+/* TEXT must end with a newline, and contain at least one non-whitespace character in the first line,
+ so that the <WHITESPACE> <NEWLINE> sequence is not read as a TEXT (needed for space gobbling)
+*/
+TOKEN :
+{
+ <DOUBLE_ESCAPE : "\\\\">
+| <ESCAPE: "\\" >
+| <TEXT: (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])* (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n", " ", "\t","\u001C"])+ (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])* <NEWLINE> ((~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])* <NEWLINE>)* >
+}
+
+TOKEN :
+{
+ <INLINE_TEXT: (~["${parser.char.dollar}", "${parser.char.hash}", "\\", "\r", "\n","\u001C"])+ >
+}
+
+/**
+ * This method is what starts the whole parsing
+ * process. After the parsing is complete and
+ * the template has been turned into an AST,
+ * this method returns the root of AST which
+ * can subsequently be traversed by a visitor
+ * which implements the ParserVisitor interface
+ * which is generated automatically by JavaCC
+ */
+SimpleNode process() :
+{
+ boolean afterNewline = true;
+}
+{
+ ( LOOKAHEAD({ getToken(1).kind != EOF }) afterNewline = Statement(afterNewline) )* <EOF>
+ { return jjtThis; }
+}
+
+/**
+ * These are the types of statements that
+ * are acceptable in Velocity templates.
+ */
+boolean Statement(boolean afterNewline) #void :
+{
+}
+{
+ LOOKAHEAD( { getToken(1).kind == IF_DIRECTIVE || afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == IF_DIRECTIVE } ) afterNewline = IfStatement(afterNewline) { return afterNewline; }
+| LOOKAHEAD(2) Reference() { return false; }
+| LOOKAHEAD(2) afterNewline = Comment() { return afterNewline; }
+| Textblock() { return false; }
+| LOOKAHEAD( { getToken(1).kind == SET_DIRECTIVE || afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == SET_DIRECTIVE } ) afterNewline = SetDirective(afterNewline) { return afterNewline; }
+| EscapedDirective() { return false; }
+| Escape() { return false; }
+| LOOKAHEAD( { getToken(1).kind == WORD || getToken(1).kind == BRACKETED_WORD || afterNewline && getToken(1).kind == WHITESPACE && ( getToken(2).kind == WORD || getToken(2).kind == BRACKETED_WORD ) } ) afterNewline = Directive(afterNewline) { return afterNewline; }
+| afterNewline = Text() { return afterNewline; }
+| (<NEWLINE>) #Text { return true; }
+| (((<INLINE_TEXT>) { afterNewline = false; } ) ((<TEXT>) { afterNewline = true; })? ) #Text { return afterNewline; }
+| (<WHITESPACE>) #Text { return false; }
+| (<SUFFIX>) #Text { return true; }
+| LOOKAHEAD(2) EndingZeroWidthWhitespace() { return afterNewline; }
+| (<LOGICAL_OR_2>) #Text { return afterNewline; } // needed here since it can be triggered in <REFERENCE> mode out of any boolean evaluation
+| (<ZERO_WIDTH_WHITESPACE>) #Text { afterNewline = !afterNewline; return false; }
+}
+
+void EndingZeroWidthWhitespace() #void : {}
+{
+ <ZERO_WIDTH_WHITESPACE> <EOF> { }
+}
+
+/**
+ * used to separate the notion of a valid directive that has been
+ * escaped, versus something that looks like a directive and
+ * is just schmoo. This is important to do as a separate production
+ * that creates a node, because we want this, in either case, to stop
+ * the further parsing of the Directive() tree.
+ */
+void EscapedDirective() : {}
+{
+ {
+ Token t = null;
+ }
+
+ t = <ESCAPE_DIRECTIVE>
+ {
+ /*
+ * churn and burn..
+ */
+ t.image = escapedDirective( t.image );
+ }
+}
+
+/**
+ * Used to catch and process escape sequences in grammatical constructs
+ * as escapes outside of VTL are just characters. Right now we have both
+ * this and the EscapeDirective() construction because in the EscapeDirective()
+ * case, we want to suck in the #&lt;directive&gt; and here we don't. We just want
+ * the escapes to render correctly
+ */
+void Escape() : {}
+{
+ {
+ Token t = null;
+ int count = 0;
+ boolean control = false;
+ }
+
+ ( LOOKAHEAD(2) t = <DOUBLE_ESCAPE>
+ {
+ count++;
+ }
+ )+
+ {
+ /*
+ * first, check to see if we have a control directive
+ */
+ switch(t.next.kind ) {
+ case IF_DIRECTIVE :
+ case ELSE :
+ case ELSEIF :
+ case END :
+ control = true;
+ break;
+ }
+
+ /*
+ * if that failed, lets lookahead to see if we matched a PD or a VM
+ */
+ String nTag = t.next.image.substring(1);
+ if (strictEscape
+ || isDirective(nTag)
+ || macroNames.containsKey(nTag)
+ || rsvc.isVelocimacro(nTag, currentTemplate))
+ {
+ control = true;
+ }
+
+ jjtThis.val = "";
+
+ for( int i = 0; i < count; i++)
+ jjtThis.val += ( control ? "\\" : "\\\\");
+ }
+
+}
+
+boolean Comment() : {}
+{
+ <SINGLE_LINE_COMMENT_START> ( <SINGLE_LINE_COMMENT> ) ? { return true; }
+| <MULTI_LINE_COMMENT> { return false; }
+| <FORMAL_COMMENT> { return false; }
+}
+
+void Textblock() : {}
+{
+ <TEXTBLOCK>
+}
+
+void FloatingPointLiteral() : {}
+{
+ <FLOATING_POINT_LITERAL>
+}
+
+void IntegerLiteral() : {}
+{
+ <INTEGER_LITERAL>
+}
+
+void StringLiteral() : {}
+{
+ <STRING_LITERAL>
+}
+
+/**
+ * This method corresponds to variable
+ * references in Velocity templates.
+ * The following are examples of variable
+ * references that may be found in a
+ * template:
+ *
+ * $foo
+ * $bar
+ *
+ */
+void Identifier() : {}
+{
+ <IDENTIFIER> | <OLD_IDENTIFIER>
+}
+
+void Word() : {}
+{
+ <WORD>
+}
+
+/**
+ * Supports the arguments for the Pluggable Directives
+ */
+int DirectiveArg() #void : {}
+{
+ Reference()
+ {
+ return ParserTreeConstants.JJTREFERENCE;
+ }
+| Word()
+ {
+ return ParserTreeConstants.JJTWORD;
+ }
+| StringLiteral()
+ {
+ return ParserTreeConstants.JJTSTRINGLITERAL;
+ }
+
+| IntegerLiteral()
+ {
+ return ParserTreeConstants.JJTINTEGERLITERAL;
+ }
+ /*
+ * Need to put this before the floating point expansion
+ */
+| LOOKAHEAD( <LBRACKET> (<WHITESPACE> | <NEWLINE>)* ( Reference() | IntegerLiteral()) (<WHITESPACE> | <NEWLINE>)* <DOUBLEDOT> ) IntegerRange()
+ {
+ return ParserTreeConstants.JJTINTEGERRANGE;
+ }
+| FloatingPointLiteral()
+ {
+ return ParserTreeConstants.JJTFLOATINGPOINTLITERAL;
+ }
+| Map()
+ {
+ return ParserTreeConstants.JJTMAP;
+ }
+| ObjectArray()
+ {
+ return ParserTreeConstants.JJTOBJECTARRAY;
+ }
+| True()
+ {
+ return ParserTreeConstants.JJTTRUE;
+ }
+| False()
+ {
+ return ParserTreeConstants.JJTFALSE;
+ }
+}
+
+void DirectiveAssign() : {}
+{
+ Reference()
+}
+
+
+/**
+ * Supports the Pluggable Directives
+ * #foo( arg+ )
+ * @return true if ends with a newline
+ */
+boolean Directive(boolean afterNewline) :
+{
+ Token id = null, t = null, u = null, end = null, _else = null;
+ int argType;
+ int argPos = 0;
+ Directive d;
+ int directiveType;
+ boolean isVM = false;
+ boolean isMacro = false;
+ ArrayList argtypes = new ArrayList(4);
+ String blockPrefix = "";
+ ASTBlock block = null, elseBlock = null;
+ boolean hasParentheses = false;
+ boolean newlineAtStart = afterNewline;
+}
+{
+ [
+ (t = <WHITESPACE>)
+ {
+ // only possible if not after new line
+ jjtThis.setPrefix(t.image);
+ t = null;
+ }
+ ]
+ /*
+ * note that if we were escaped, that is now handled by
+ * EscapedDirective()
+ */
+ ((id = <WORD>) | (id = <BRACKETED_WORD>))
+ {
+ String directiveName;
+ int p = id.image.lastIndexOf(hash);
+ if (id.kind == StandardParserConstants.BRACKETED_WORD)
+ {
+ directiveName = id.image.substring(p + 2, id.image.length() - 1);
+ }
+ else
+ {
+ directiveName = id.image.substring(p + 1);
+ }
+
+ d = getDirective(directiveName);
+
+ /*
+ * Velocimacro support : if the directive is macro directive
+ * then set the flag so after the block parsing, we add the VM
+ * right then. (So available if used w/in the current template )
+ */
+
+ if (directiveName.equals("macro"))
+ {
+ isMacro = true;
+ }
+
+ /*
+ * set the directive name from here. No reason for the thing to know
+ * about parser tokens
+ */
+
+ jjtThis.setDirectiveName(directiveName);
+
+ if ( d == null)
+ {
+ if( directiveName.charAt(0) == at )
+ {
+ // block macro call of type: #@foobar($arg1 $arg2) astBody #end
+ directiveType = Directive.BLOCK;
+ }
+ else
+ {
+ /*
+ * if null, then not a real directive, but maybe a Velocimacro
+ */
+ isVM = rsvc.isVelocimacro(directiveName, currentTemplate);
+
+ directiveType = Directive.LINE;
+ }
+ }
+ else
+ {
+ directiveType = d.getType();
+ }
+
+ /*
+ * now, switch us out of PRE_DIRECTIVE
+ */
+
+ token_source.switchTo(DIRECTIVE);
+ argPos = 0;
+ }
+
+
+ /**
+ * Look for the pattern [WHITESPACE] <LPAREN>
+ */
+ (
+ LOOKAHEAD( { isLeftParenthesis() } )
+ /*
+ * if this is indeed a token, match the #foo ( arg, arg... ) pattern
+ */
+ (
+ (<WHITESPACE> | <NEWLINE>)* <LPAREN>
+ (
+ LOOKAHEAD({ !isRightParenthesis() }) (<WHITESPACE> | <NEWLINE>)* [<COMMA> (<WHITESPACE> | <NEWLINE>)*]
+ (
+ [
+ LOOKAHEAD( { isMacro && isAssignment() })
+ DirectiveAssign() (<WHITESPACE> | <NEWLINE>)* <EQUALS> ( <WHITESPACE> | <NEWLINE> )*
+ {
+ argtypes.add(ParserTreeConstants.JJTDIRECTIVEASSIGN);
+ }
+ ]
+ LOOKAHEAD( { !isRightParenthesis() } )
+ (
+ argType = DirectiveArg()
+ {
+ argtypes.add(argType);
+ if (d == null && argType == ParserTreeConstants.JJTWORD)
+ {
+ if (isVM)
+ {
+ throw new MacroParseException("Invalid argument "
+ + (argPos+1) + " in macro call " + id.image, currentTemplate.getName(), id);
+ }
+ }
+ argPos++;
+ }
+ )
+ |
+ {
+ if (!isMacro)
+ {
+ // We only allow line comments in macro definitions for now
+ throw new MacroParseException("A Line comment is not allowed in " + id.image
+ + " arguments", currentTemplate.getName(), id);
+ }
+ }
+ <SINGLE_LINE_COMMENT_START> [<SINGLE_LINE_COMMENT>]
+ )
+ )* (<WHITESPACE> | <NEWLINE>)* <RPAREN>
+ { hasParentheses = true; }
+ )
+ |
+ {
+ token_source.stateStackPop();
+ }
+ )
+ { afterNewline = false; }
+ [
+ // Conditions where whitespace and newline postfix is eaten by space gobbling at this point:
+ // - block directive
+ // - new line before directive without backward compatibility mode
+ // - backward compatibility mode *with parentheses*
+ // - #include() or #parse()
+ LOOKAHEAD(2, { directiveType != Directive.LINE || newlineAtStart && rsvc.getSpaceGobbling() != SpaceGobbling.BC || rsvc.getSpaceGobbling() == SpaceGobbling.BC && hasParentheses || d != null && (d instanceof Include || d instanceof Parse) })
+ ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ afterNewline = true;
+ if (directiveType == Directive.LINE)
+ {
+ jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+ }
+ else
+ {
+ blockPrefix = (t == null ? u.image : t.image + u.image);
+ }
+ t = u = null;
+ }
+ ]
+ {
+ if (d != null)
+ {
+ d.checkArgs(argtypes, id, currentTemplate.getName());
+ }
+ if (directiveType == Directive.LINE)
+ {
+ return afterNewline;
+ }
+ }
+ /*
+ * and the following block if the PD needs it
+ */
+ (
+ (
+ (
+ LOOKAHEAD( { getToken(1).kind != END && getToken(1).kind != ELSE && ( !afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END && getToken(2).kind != ELSE ) }) afterNewline = Statement(afterNewline)
+ )*
+ {
+ block = jjtThis;
+ block.setPrefix(blockPrefix);
+ blockPrefix = "";
+ }
+ )
+ #Block
+ )
+ [
+ LOOKAHEAD( 1, { afterNewline })
+ (t = <WHITESPACE>)
+ {
+ block.setPostfix(t.image);
+ t = null;
+ }
+ ]
+ /*
+ * then an optional #else for the #foreach directive
+ */
+ (
+ [
+ LOOKAHEAD( { d != null && (d instanceof Foreach) && getToken(1).kind == ELSE } )
+ (
+ (_else = <ELSE>)
+ (
+ [
+ LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ }
+ ]
+ (
+ LOOKAHEAD( { getToken(1).kind != END && (!afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END) })
+ afterNewline = Statement(afterNewline)
+ )*
+ {
+ elseBlock = jjtThis;
+ }
+ )
+ #Block
+ {
+ int pos = _else.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ block.setMorePostfix(_else.image.substring(0, pos));
+ }
+ block = elseBlock;
+ }
+ )
+ ]
+ )
+ [
+ LOOKAHEAD( 1, { afterNewline })
+ (t = <WHITESPACE>)
+ {
+ block.setPostfix(t.image);
+ t = null;
+ afterNewline = false;
+ }
+ ]
+ (
+ (end = <END>)
+ { afterNewline = false; }
+ [
+ LOOKAHEAD(2, { newlineAtStart || rsvc.getSpaceGobbling() == SpaceGobbling.BC })
+ ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ }
+ ]
+ {
+ int pos = end.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ block.setMorePostfix(end.image.substring(0, pos));
+ }
+ }
+ )
+ {
+ /*
+ * VM : if we are processing a #macro directive, we need to
+ * process the block. In truth, I can just register the name
+ * and do the work later when init-ing. That would work
+ * as long as things were always defined before use. This way
+ * we don't have to worry about forward references and such...
+ */
+ if (isMacro)
+ {
+ // Add the macro name so that we can peform escape processing
+ // on defined macros
+ String macroName = jjtThis.jjtGetChild(0).getFirstToken().image;
+ macroNames.put(macroName, macroName);
+ }
+ if (d != null)
+ {
+ d.checkArgs(argtypes, id, currentTemplate.getName());
+ }
+ /*
+ * VM : end
+ */
+ return afterNewline;
+ }
+}
+
+/**
+ * for creating a map in a #set
+ *
+ * #set($foo = {$foo : $bar, $blargh : $thingy})
+ */
+void Map() : {}
+{
+ <LEFT_CURLEY>
+ (
+ LOOKAHEAD(( <WHITESPACE> | <NEWLINE> )* Parameter() <COLON>) ( Parameter() <COLON> Parameter() (<COMMA> Parameter() <COLON> Parameter() )* )
+ |
+ ( <WHITESPACE> | <NEWLINE> )*
+ )
+
+ /** note: need both tokens as they are generated in different states **/
+ ( <RIGHT_CURLEY> | <RCURLY> )
+}
+
+void ObjectArray() : {}
+{
+ <LBRACKET> [ Parameter() ( <COMMA> Parameter() )* ] <RBRACKET>
+}
+
+
+/**
+ * supports the [n..m] vector generator for use in
+ * the #foreach() to generate measured ranges w/o
+ * needing explicit support from the app/servlet
+ */
+void IntegerRange() : {}
+{
+ <LBRACKET> (<WHITESPACE> | <NEWLINE>)*
+ ( Reference() | IntegerLiteral())
+ (<WHITESPACE>|<NEWLINE>)* <DOUBLEDOT> (<WHITESPACE>|<NEWLINE>)*
+ (Reference() | IntegerLiteral())
+ (<WHITESPACE>|<NEWLINE>)* <RBRACKET>
+}
+
+
+/**
+ * A Simplified parameter more suitable for an index position: $foo[$index]
+ */
+void IndexParameter() #void: {}
+{
+ (<WHITESPACE>|<NEWLINE>)*
+ (
+ Expression()
+ )
+ (<WHITESPACE>|<NEWLINE>)*
+}
+
+
+/**
+ * This method has yet to be fully implemented
+ * but will allow arbitrarily nested method
+ * calls
+ */
+void Parameter() #void: {}
+{
+ (<WHITESPACE>|<NEWLINE>)*
+ (
+ StringLiteral()
+ | IntegerLiteral()
+ | LOOKAHEAD( <LBRACKET> ( <WHITESPACE> | <NEWLINE> )* ( Reference() | IntegerLiteral()) ( <WHITESPACE> | <NEWLINE> )* <DOUBLEDOT> ) IntegerRange()
+ | Map()
+ | ObjectArray()
+ | True()
+ | False()
+ | Reference()
+ | FloatingPointLiteral()
+ )
+ (<WHITESPACE>|<NEWLINE>)*
+}
+
+/**
+ * This method has yet to be fully implemented
+ * but will allow arbitrarily nested method
+ * calls
+ */
+void Method() : {}
+{
+ Identifier() <LPAREN> [ Expression() ( <COMMA> Expression() )* ] <REFMOD2_RPAREN>
+}
+
+
+void Index() : {}
+{
+ <INDEX_LBRACKET> IndexParameter() <INDEX_RBRACKET>
+}
+
+void Reference() : {}
+{
+ /*
+ * A reference is either $<FOO> or ${<FOO>} or ${<FOO>'|'<ALTERNATE_VALUE>)
+ */
+
+ (
+ ( <IDENTIFIER> | <OLD_IDENTIFIER> ) (Index())*
+ (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) (Index())* )*
+ )
+ |
+ (
+ <LCURLY>
+ ( <IDENTIFIER> | <OLD_IDENTIFIER> ) (Index())*
+ (LOOKAHEAD(2) <DOT> (LOOKAHEAD(3) Method() | Identifier() ) (Index())* )*
+ [ <PIPE> Expression() ]
+ ( <RCURLY> | <RIGHT_CURLEY> )
+ )
+}
+
+void True() : {}
+{
+ <TRUE>
+}
+
+void False() : {}
+{
+ <FALSE>
+}
+
+
+/**
+ * This is somewhat of a kludge, the problem is that the parser picks
+ * up on '$[' , or '$![' as being a Reference, and does not dismiss it even though
+ * there is no <Identifier> between $ and [, This has something to do
+ * with the LOOKAHEAD in Reference, but I never found a way to resolve
+ * it in a more fashionable way..
+ */
+<DEFAULT,PRE_REFERENCE,PRE_OLD_REFERENCE>
+TOKEN :
+{
+ <EMPTY_INDEX : ("$[" | "$![" | "$\\![" | "$.")>
+}
+
+
+/**
+ * This method is responsible for allowing
+ * all non-grammar text to pass through
+ * unscathed.
+ * @return true if last read token was a newline
+ */
+boolean Text() :
+{
+ Token t = null;
+}
+{
+ <TEXT> { return true; }
+ | <DOT> { return false; }
+ | <RPAREN> { return false; }
+ | <LPAREN> { return false; }
+ | <INTEGER_LITERAL> { return false; }
+ | <FLOATING_POINT_LITERAL> { return false; }
+ | <STRING_LITERAL> { return false; }
+ | <ESCAPE> { return false; }
+ | <LCURLY> { return false; }
+ | <RCURLY> { return false; }
+ | <EMPTY_INDEX> { return false; }
+ | <PIPE> { return false; }
+ | t=<LONE_SYMBOL>
+ {
+ /* Drop the ending zero-width whitespace */
+ t.image = t.image.substring(0, t.image.length() - 1); return false;
+ }
+}
+
+/* -----------------------------------------------------------------------
+ *
+ * Defined Directive Syntax
+ *
+ * ----------------------------------------------------------------------*/
+
+boolean IfStatement(boolean afterNewline) :
+{
+ Token t = null, u = null, end = null;
+ ASTBlock lastBlock = null;
+ boolean newlineAtStart = afterNewline;
+}
+{
+ [ ( t = <WHITESPACE> )
+ {
+ // only possible if not after new line
+ jjtThis.setPrefix(t.image);
+ t = null;
+ }
+ ]
+ <IF_DIRECTIVE> ( <WHITESPACE> | <NEWLINE> )* <LPAREN> Expression() <RPAREN>
+ (
+ [
+ LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ }
+ ]
+ ( LOOKAHEAD(
+ {
+ (getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && getToken(1).kind != END) &&
+ (!afterNewline || getToken(1).kind != WHITESPACE || (getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != END))
+ })
+ afterNewline = Statement(afterNewline) )*
+ {
+ lastBlock = jjtThis;
+ }
+ ) #Block
+ [ LOOKAHEAD( { getToken(1).kind == ELSEIF || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF) })
+ ( LOOKAHEAD( { getToken(1).kind == ELSEIF || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSEIF) }) ( lastBlock = ElseIfStatement(lastBlock, afterNewline) { afterNewline = lastBlock.endsWithNewline; } ))+ ]
+ [ LOOKAHEAD( { getToken(1).kind == ELSE || (afterNewline && getToken(1).kind == WHITESPACE && getToken(2).kind == ELSE) } ) lastBlock = ElseStatement(lastBlock, afterNewline) { afterNewline = lastBlock.endsWithNewline; } ]
+ [ LOOKAHEAD( 1, { afterNewline } ) ( t = <WHITESPACE> )
+ {
+ lastBlock.setPostfix(t.image);
+ t = null;
+ }
+ ]
+ (end = <END>)
+ { afterNewline = false; }
+ [
+ LOOKAHEAD(2, { newlineAtStart || rsvc.getSpaceGobbling() == SpaceGobbling.BC } )
+ ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+ afterNewline = true;
+ }
+ ]
+ {
+ int pos = end.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ lastBlock.setMorePostfix(end.image.substring(0, pos));
+ }
+ return afterNewline;
+ }
+}
+
+ASTBlock ElseStatement(ASTBlock previousBlock, boolean afterNewline) :
+{
+ Token t = null, u = null, _else = null;
+ ASTBlock block = null;
+}
+{
+ [ ( t = <WHITESPACE> )
+ {
+ previousBlock.setPostfix(t.image);
+ t = null;
+ }
+ ]
+ (_else = <ELSE>)
+ (
+ [
+ LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ }
+ ]
+ ( LOOKAHEAD( { getToken(1).kind != END && (!afterNewline || getToken(1).kind != WHITESPACE || getToken(2).kind != END) }) afterNewline = Statement(afterNewline) )*
+ {
+ block = jjtThis;
+ block.endsWithNewline = afterNewline;
+ }
+ )
+ #Block
+ {
+ int pos = _else.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ previousBlock.setMorePostfix(_else.image.substring(0, pos));
+ }
+ return block;
+ }
+}
+
+ASTBlock ElseIfStatement(ASTBlock previousBlock, boolean afterNewline) :
+{
+ Token t = null, u = null, elseif = null;
+ ASTBlock block = null;
+}
+{
+ [ ( t = <WHITESPACE> )
+ {
+ previousBlock.setPostfix(t.image);
+ t = null;
+ }
+ ]
+ (elseif = <ELSEIF>) ( <WHITESPACE> | <NEWLINE> )*
+ <LPAREN> Expression() <RPAREN>
+ (
+ [
+ LOOKAHEAD(2) ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPrefix(t == null ? u.image : t.image + u.image);
+ t = u = null;
+ afterNewline = true;
+ }
+ ]
+ ( LOOKAHEAD( { (getToken(1).kind != ELSEIF && getToken(1).kind != ELSE && getToken(1).kind != END) && (!afterNewline || getToken(1).kind != WHITESPACE || (getToken(2).kind != ELSEIF && getToken(2).kind != ELSE && getToken(2).kind != END)) }) afterNewline = Statement(afterNewline) )*
+ {
+ block = jjtThis;
+ block.endsWithNewline = afterNewline;
+ }
+ )
+ #Block
+ {
+ int pos = elseif.image.lastIndexOf(hash);
+ if (pos > 0)
+ {
+ previousBlock.setMorePostfix(elseif.image.substring(0, pos));
+ }
+ return block;
+ }
+}
+
+/**
+ * Currently support both types of set :
+ * #set( expr )
+ * #set expr
+ */
+boolean SetDirective(boolean afterNewline) :
+{
+ Token t = null, u = null;
+ boolean endsWithNewline = false;
+}
+{
+ [ ( t = <WHITESPACE> )
+ {
+ // only possible after new line
+ jjtThis.setPrefix(t.image);
+ t = null;
+ }
+ ]
+ <SET_DIRECTIVE>(( <WHITESPACE> | <NEWLINE> )* Reference() ( <WHITESPACE> | <NEWLINE> )* <EQUALS> Expression() <RPAREN>
+ {
+ /*
+ * ensure that inSet is false. Leads to some amusing bugs...
+ */
+
+ token_source.setInSet(false);
+ }
+ [
+ LOOKAHEAD(2, { afterNewline || rsvc.getSpaceGobbling() == SpaceGobbling.BC } )
+ ( [ ( t = <WHITESPACE> ) ] ( u = <NEWLINE> ) )
+ {
+ jjtThis.setPostfix(t == null ? u.image : t.image + u.image);
+ endsWithNewline = true;
+ }
+ ] )
+ {
+ return endsWithNewline;
+ }
+}
+
+/* -----------------------------------------------------------------------
+ *
+ * Expression Syntax
+ *
+ * ----------------------------------------------------------------------*/
+
+void Expression() : {}
+{
+// LOOKAHEAD( PrimaryExpression() <EQUALS> ) Assignment()
+//|
+ConditionalOrExpression()
+}
+
+void Assignment() #Assignment(2) : {}
+{
+ PrimaryExpression() <EQUALS> Expression()
+}
+
+void ConditionalOrExpression() #void : {}
+{
+ ConditionalAndExpression()
+ ( ( <LOGICAL_OR> | <LOGICAL_OR_2> ) ConditionalAndExpression() #OrNode(2) )*
+}
+
+
+void ConditionalAndExpression() #void : {}
+{
+ EqualityExpression()
+ ( <LOGICAL_AND> EqualityExpression() #AndNode(2) )*
+}
+
+void EqualityExpression() #void : {}
+{
+ RelationalExpression()
+ (
+ <LOGICAL_EQUALS> RelationalExpression() #EQNode(2)
+ | <LOGICAL_NOT_EQUALS> RelationalExpression() #NENode(2)
+ )*
+}
+
+void RelationalExpression() #void : {}
+{
+ AdditiveExpression()
+ (
+ <LOGICAL_LT> AdditiveExpression() #LTNode(2)
+ | <LOGICAL_GT> AdditiveExpression() #GTNode(2)
+ | <LOGICAL_LE> AdditiveExpression() #LENode(2)
+ | <LOGICAL_GE> AdditiveExpression() #GENode(2)
+ )*
+}
+
+void AdditiveExpression() #void : {}
+{
+ MultiplicativeExpression()
+ (
+ <PLUS> MultiplicativeExpression() #AddNode(2)
+ | <MINUS> MultiplicativeExpression() #SubtractNode(2)
+ )*
+}
+
+void MultiplicativeExpression() #void : {}
+{
+ UnaryExpression()
+ (
+ <MULTIPLY> UnaryExpression() #MulNode(2)
+ | <DIVIDE> UnaryExpression() #DivNode(2)
+ | <MODULUS> UnaryExpression() #ModNode(2)
+ )*
+}
+
+void UnaryExpression() #void : {}
+{
+ ( <WHITESPACE> | <NEWLINE> )*
+ (
+ <LOGICAL_NOT> UnaryExpression() #NotNode(1)
+ | <MINUS> PrimaryExpression() #NegateNode(1)
+ | PrimaryExpression()
+ )
+}
+
+void PrimaryExpression() #void : {}
+{
+ ( <WHITESPACE> | <NEWLINE> )*
+ (
+ StringLiteral()
+ | Reference()
+ | IntegerLiteral()
+ | LOOKAHEAD( <LBRACKET> ( <WHITESPACE> | <NEWLINE> )* ( Reference() | IntegerLiteral()) ( <WHITESPACE> | <NEWLINE> )* <DOUBLEDOT> ) IntegerRange()
+ | FloatingPointLiteral()
+ | Map()
+ | ObjectArray()
+ | True()
+ | False()
+ | <LPAREN> Expression() <RPAREN>
+ )
+ ( <WHITESPACE> | <NEWLINE> )*
+}
+
+/* ======================================================================
+
+ Notes
+ -----
+
+ template == the input stream for this parser, contains 'VTL'
+ mixed in with 'schmoo'
+
+ VTL == Velocity Template Language : the references, directives, etc
+
+ schmoo == the non-VTL component of a template
+
+ reference == VTL entity that represents data within the context. ex. $foo
+
+ directive == VTL entity that denotes 'action' (#set, #foreach, #if )
+
+ defined directive (DD) == VTL directive entity that is expressed
+ explicitly w/in this grammar
+
+ pluggable directive (PD) == VTL directive entity that is defined outside of the
+ grammar. PD's allow VTL to be easily expandable w/o parser modification.
+
+ The problem with parsing VTL is that an input stream consists generally of
+ little bits of VTL mixed in with 'other stuff, referred to as 'schmoo'.
+ Unlike other languages, like C or Java, where the parser can punt whenever
+ it encounters input that doesn't conform to the grammar, the VTL parser can't do
+ that. It must simply output the schmoo and keep going.
+
+ There are a few things that we do here :
+ - define a set of parser states (DEFAULT, DIRECTIVE, REFERENCE, etc)
+ - define for each parser state a set of tokens for each state
+ - define the VTL grammar, expressed (mostly) in the productions such as Text(),
+ SetStatement(), etc.
+
+ It is clear that this expression of the VTL grammar (the contents
+ of this .jjt file) is maturing and evolving as we learn more about
+ how to parse VTL ( and as I learn about parsing...), so in the event
+ this documentation is in disagreement w/ the source, the source
+ takes precedence. :)
+
+ Parser States
+ -------------
+ DEFAULT : This is the base or starting state, and strangely enough, the
+ default state.
+
+ PRE_DIRECTIVE : State immediately following '#' before we figure out which
+ defined or pluggable directive (or neither) we are working with.
+
+ DIRECTIVE : This state is triggered by the a match of a DD or a PD.
+
+ PRE_REFERENCE : Triggered by '$'. Analagous to PRE_DIRECTIVE. When '-' is
+ allowed in identifiers, this state is called PRE_OLD_REFERENCE.
+
+ REFERENCE : Triggered by the <IDENTIFIER>
+
+ REFMODIFIER : Triggered by .<alpha> when in REFERENCE, REFMODIFIER or REFMOD3. When '-'
+ is allowed in identifiers, this state is called OLD_REFMODIFIER.
+
+ REFMOD2 : Triggered by '(' when in REFMODIFIER
+
+ REFMOD3 : Triggered by the corresponding ')'
+
+ REFINDEX : Array index. Triggered by '[' in REFERENCE, REFMODIFIER, REFMOD3.
+
+ ALT_VAL : Alternate value. Triggered by '|' in REFERENCE, REFMODIFIER, REFMOD3.
+
+ (cont)
+
+ Escape Sequences
+ ----------------
+ The escape processing in VTL is very simple. The '\' character acts
+ only as an escape when :
+
+ 1) On or more touch a VTL element.
+
+ A VTL element is either :
+
+ 1) It preceeds a reference that is in the context.
+
+ 2) It preceeds a defined directive (#set, #if, #end, etc) or a valid
+ pluggable directive, such as #foreach
+
+ In all other cases the '\' is just another piece of text. The purpose of this
+ is to allow the non-VTL parts of a template (the 'schmoo') to not have to be
+ altered for processing by Velocity.
+
+ So if in the context $foo and $bar were defined and $woogie was not
+
+ \$foo \$bar \$woogie
+
+ would output
+
+ $foo $bar \$woogie
+
+ Further, you can stack them and they affect left to right, just like convention
+ escape characters in other languages.
+
+ \$foo = $foo
+ \\$foo = \<foo>
+ \\\$foo = \$foo
+
+
+ What You Expect
+ ---------------
+ The recent versions of the parser are trying to support precise output to
+ support general template use. The directives do not render trailing
+ whitespace and newlines if followed by a newline. They will render
+ preceeding whitespace. The only exception is #set, which also eats
+ preceeding whitespace.
+
+ So, with a template :
+
+ ------
+ #set($foo="foo")
+ #if($foo)
+ \$foo = $foo
+ #end
+ ------
+
+ it will render precisely :
+
+ ------
+ $foo = foo
+ ------
+
+*/
diff --git a/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/directive.properties b/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/directive.properties
new file mode 100644
index 00000000..1f9a972d
--- /dev/null
+++ b/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/directive.properties
@@ -0,0 +1,24 @@
+# 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.
+directive.1=org.apache.velocity.runtime.directive.Foreach
+directive.2=org.apache.velocity.runtime.directive.Include
+directive.3=org.apache.velocity.runtime.directive.Parse
+directive.4=org.apache.velocity.runtime.directive.Macro
+directive.5=org.apache.velocity.runtime.directive.Evaluate
+directive.6=org.apache.velocity.runtime.directive.Break
+directive.7=org.apache.velocity.runtime.directive.Define
+directive.8=org.apache.velocity.runtime.directive.Stop
diff --git a/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties b/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
new file mode 100644
index 00000000..3d96b636
--- /dev/null
+++ b/velocity-engine-core/src/main/resources/org/apache/velocity/runtime/defaults/velocity.properties
@@ -0,0 +1,245 @@
+# 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 controls whether invalid references are logged.
+# ----------------------------------------------------------------------------
+
+runtime.log.log_invalid_references = true
+
+# ----------------------------------------------------------------------------
+# Strings interning
+# ----------------------------------------------------------------------------
+# Set to true to optimize memory, to false to optimize speed
+
+runtime.string_interning = true
+
+# ----------------------------------------------------------------------------
+# F O R E A C H P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# This property controls how many loops #foreach can execute. The default
+# is -1, which means there is no limit.
+# ----------------------------------------------------------------------------
+
+directive.foreach.max_loops = -1
+
+# ----------------------------------------------------------------------------
+# I F P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# This property controls whether empty strings and collections,
+# as long as zero numbers, do evaluate to false.
+# ----------------------------------------------------------------------------
+
+directive.if.empty_check = true
+
+# ----------------------------------------------------------------------------
+# P A R S E P R O P E R T I E S
+# ----------------------------------------------------------------------------
+
+directive.parse.max_depth = 10
+
+# ----------------------------------------------------------------------------
+# S C O P E P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# These are the properties that govern whether or not a Scope object
+# is automatically provided for each of the given scopes to serve as a
+# scope-safe reference namespace and "label" for #break calls. The default
+# for most of these is false. Note that <bodymacroname> should be replaced by
+# name of macros that take bodies for which you want to suppress the scope.
+# ----------------------------------------------------------------------------
+# context.scope_control.template = false
+# context.scope_control.evaluate = false
+context.scope_control.foreach = true
+# context.scope_control.macro = false
+# context.scope_control.define = false
+# context.scope_control.<bodymacroname> = false
+
+# ----------------------------------------------------------------------------
+# T E M P L A T E L O A D E R S
+# ----------------------------------------------------------------------------
+#
+#
+# ----------------------------------------------------------------------------
+
+resource.default_encoding=UTF-8
+
+resource.loaders = file
+
+resource.loader.file.description = Velocity File Resource Loader
+resource.loader.file.class = org.apache.velocity.runtime.resource.loader.FileResourceLoader
+resource.loader.file.path = .
+resource.loader.file.cache = false
+resource.loader.file.modification_check_interval = 2
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO PROPERTIES
+# ----------------------------------------------------------------------------
+# global : name of default global library. It is expected to be in the regular
+# template path. You may remove it (either the file or this property) if
+# you wish with no harm.
+# ----------------------------------------------------------------------------
+# velocimacro.library = VM_global_library.vm
+
+velocimacro.inline.allow = true
+velocimacro.inline.replace_global = false
+velocimacro.inline.local_scope = false
+velocimacro.max_depth = 20
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO STRICT MODE
+# ----------------------------------------------------------------------------
+# if true, will throw an exception for incorrect number
+# of arguments. false by default (for backwards compatibility)
+# but this option will eventually be removed and will always
+# act as if true
+# ----------------------------------------------------------------------------
+velocimacro.arguments.strict = false
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO BODY REFERENCE
+# ----------------------------------------------------------------------------
+# Defines name of the reference that can be used to render the AST block passed to
+# block macro call as an argument inside a macro.
+# ----------------------------------------------------------------------------
+velocimacro.body_reference = bodyContent
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO ENABLE BC MODE
+# ----------------------------------------------------------------------------
+# Backward compatibility for 1.7 macros behavior.
+# If true, when a macro has to render a null or invalid argument reference
+# which is not quiet, it will print the provided literal reference instead
+# of the one found in the body of the macro ; and if a macro argument is
+# without an explicit default value is missing from the macro call, its value
+# will be looked up in the global context
+# ----------------------------------------------------------------------------
+velocimacro.enable_bc_mode = false
+
+# ----------------------------------------------------------------------------
+# STRICT REFERENCE MODE
+# ----------------------------------------------------------------------------
+# if true, will throw a MethodInvocationException for references
+# that are not defined in the context, or have not been defined
+# with a #set directive. This setting will also throw an exception
+# if an attempt is made to call a non-existing property on an object
+# or if the object is null.
+# ----------------------------------------------------------------------------
+runtime.strict_mode.enable = false
+
+# ----------------------------------------------------------------------------
+# INTERPOLATION
+# ----------------------------------------------------------------------------
+# turn off and on interpolation of references and directives in string
+# literals. ON by default :)
+# ----------------------------------------------------------------------------
+runtime.interpolate_string_literals = true
+
+
+# ----------------------------------------------------------------------------
+# RESOURCE MANAGEMENT
+# ----------------------------------------------------------------------------
+# Allows alternative ResourceManager and ResourceCache implementations
+# to be plugged in.
+# ----------------------------------------------------------------------------
+resource.manager.class = org.apache.velocity.runtime.resource.ResourceManagerImpl
+resource.manager.cache.class = org.apache.velocity.runtime.resource.ResourceCacheImpl
+
+# ----------------------------------------------------------------------------
+# PARSER POOL
+# ----------------------------------------------------------------------------
+# Selects a custom factory class for the parser pool. Must implement
+# ParserPool. parser.pool.size is used by the default implementation
+# ParserPoolImpl
+# ----------------------------------------------------------------------------
+
+parser.pool.class = org.apache.velocity.runtime.ParserPoolImpl
+parser.pool.size = 20
+
+
+# ----------------------------------------------------------------------------
+# EVENT HANDLER
+# ----------------------------------------------------------------------------
+# Allows alternative event handlers to be plugged in. Note that each
+# class property is actually a comma-separated list of classes (which will
+# be called in order).
+# ----------------------------------------------------------------------------
+# event_handler.reference_insertion.class =
+# event_handler.invalid_reference.class =
+# event_handler.method_exception.class =
+# event_handler.include.class =
+
+
+# ----------------------------------------------------------------------------
+# PLUGGABLE INTROSPECTOR
+# ----------------------------------------------------------------------------
+# Allows alternative introspection and all that can of worms brings.
+# ----------------------------------------------------------------------------
+
+introspector.uberspect.class = org.apache.velocity.util.introspection.UberspectImpl
+
+# ----------------------------------------------------------------------------
+# CONVERSION HANDLER
+# ----------------------------------------------------------------------------
+# Sets the data types Conversion Handler used by the default uberspector
+# ----------------------------------------------------------------------------
+
+introspector.conversion_handler.class = org.apache.velocity.util.introspection.TypeConversionHandlerImpl
+1
+
+# ----------------------------------------------------------------------------
+# SECURE INTROSPECTOR
+# ----------------------------------------------------------------------------
+# If selected, prohibits methods in certain classes and packages from being
+# accessed.
+# ----------------------------------------------------------------------------
+
+introspector.restrict.packages = java.lang.reflect
+
+# The two most dangerous classes
+
+introspector.restrict.classes = java.lang.Class
+introspector.restrict.classes = java.lang.ClassLoader
+
+# Restrict these for extra safety
+
+introspector.restrict.classes = java.lang.Compiler
+introspector.restrict.classes = java.lang.InheritableThreadLocal
+introspector.restrict.classes = java.lang.Package
+introspector.restrict.classes = java.lang.Process
+introspector.restrict.classes = java.lang.Runtime
+introspector.restrict.classes = java.lang.RuntimePermission
+introspector.restrict.classes = java.lang.SecurityManager
+introspector.restrict.classes = java.lang.System
+introspector.restrict.classes = java.lang.Thread
+introspector.restrict.classes = java.lang.ThreadGroup
+introspector.restrict.classes = java.lang.ThreadLocal
+
+# ----------------------------------------------------------------------------
+# SPACE GOBBLING
+# ----------------------------------------------------------------------------
+# Possible values: none, bc (aka Backward Compatible), lines, structured
+# ----------------------------------------------------------------------------
+
+parser.space_gobbling = lines
+
+# ----------------------------------------------------------------------------
+# HYPHEN IN IDENTIFIERS
+# ----------------------------------------------------------------------------
+# Set to true to allow '-' in reference identifiers (backward compatibility option)
+# ----------------------------------------------------------------------------
+
+parser.allow_hyphen_in_identifiers = false
diff --git a/velocity-engine-core/src/test/java/classloader/Foo.java b/velocity-engine-core/src/test/java/classloader/Foo.java
new file mode 100644
index 00000000..436c780b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/classloader/Foo.java
@@ -0,0 +1,45 @@
+package classloader;
+
+/*
+ * 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.
+ */
+
+/**
+ * Simple class Foo to be used in classloader testing
+ * This class should be kept here and not in velocity.jar
+ * to keep out of the parent classloader of the test
+ * classloader
+ */
+public class Foo
+{
+ /*
+ * the ClassloaderChangeTest
+ * depends on this string as
+ * is. If this changes (there is no reason
+ * to ever do that, BTW), then
+ * udpate ClassloaderChangeTest as well.
+ */
+ private static String MSG =
+ "Hello From Foo";
+
+ public String doIt()
+ {
+ return MSG;
+ }
+}
+
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/io/UnicodeInputStreamTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/io/UnicodeInputStreamTestCase.java
new file mode 100644
index 00000000..68c10842
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/io/UnicodeInputStreamTestCase.java
@@ -0,0 +1,239 @@
+package org.apache.velocity.io;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.commons.lang3.ArrayUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+
+/**
+ * Test the UnicodeInputStream.
+ *
+ * @author $author$
+ * @version $Revision$, $Date$
+ */
+public class UnicodeInputStreamTestCase
+ extends TestCase
+{
+
+ public UnicodeInputStreamTestCase(final String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(UnicodeInputStreamTestCase.class);
+ }
+
+ public void testSimpleStream()
+ throws Exception
+ {
+ testRun(null, "Ich bin zwei Oeltanks", "US-ASCII", true);
+ testRun(null, "Ich bin zwei Oeltanks", "US-ASCII", false);
+ }
+
+ public void testSimpleUTF8()
+ throws Exception
+ {
+ testRun(null, "Ich bin zwei Oeltanks", "UTF-8", true);
+ testRun(null, "Ich bin zwei Oeltanks", "UTF-8", false);
+ }
+
+ public void testRealUTF8()
+ throws Exception
+ {
+ testRun(null, "Ich bin zwei \u00d6ltanks", "UTF-8", true);
+ testRun(null, "Ich bin zwei \u00d6ltanks", "UTF-8", false);
+ }
+
+ public void testRealUTF8WithBOM()
+ throws Exception
+ {
+ testRun(UnicodeInputStream.UTF8_BOM, "Ich bin ein Test",
+ "UTF-8", true);
+ testRun(UnicodeInputStream.UTF8_BOM, "Ich bin ein Test",
+ "UTF-8", false);
+ }
+
+ public void testRealUTF16BEWithBOM()
+ throws Exception
+ {
+ testRun(UnicodeInputStream.UTF16BE_BOM, "Ich bin ein Test",
+ "UTF-16BE", true);
+ testRun(UnicodeInputStream.UTF16BE_BOM, "Ich bin ein Test",
+ "UTF-16BE", false);
+ }
+
+ public void testRealUTF16LEWithBOM()
+ throws Exception
+ {
+ testRun(UnicodeInputStream.UTF16LE_BOM, "Ich bin ein Test",
+ "UTF-16LE", true);
+ testRun(UnicodeInputStream.UTF16LE_BOM, "Ich bin ein Test",
+ "UTF-16LE", false);
+ }
+
+ public void testRealUTF32BEWithBOM()
+ throws Exception
+ {
+ testRun(UnicodeInputStream.UTF32BE_BOM, null,
+ "UTF-32BE", true);
+ testRun(UnicodeInputStream.UTF32BE_BOM, null,
+ "UTF-32BE", false);
+ }
+
+ public void testRealUTF32LEWithBOM()
+ throws Exception
+ {
+ testRun(UnicodeInputStream.UTF32LE_BOM, null,
+ "UTF-32LE", true);
+ testRun(UnicodeInputStream.UTF32LE_BOM, null,
+ "UTF-32LE", false);
+ }
+
+
+ protected void testRun(final UnicodeInputStream.UnicodeBOM bom, final String str, final String testEncoding, final boolean skipBOM)
+ throws Exception
+ {
+
+ byte [] testString = buildTestString(bom, str, testEncoding, skipBOM);
+
+ InputStream is = null;
+ UnicodeInputStream uis = null;
+
+ try
+ {
+ is = createInputStream(bom, str, testEncoding);
+ uis = new UnicodeInputStream(is, skipBOM);
+
+ assertEquals("BOM Skipping problem", skipBOM, uis.isSkipBOM());
+
+ if (bom != null)
+ {
+ assertEquals("Wrong Encoding detected", testEncoding, uis.getEncodingFromStream());
+ }
+
+ byte [] result = readAllBytes(uis, testEncoding);
+
+ assertNotNull(testString);
+ assertNotNull(result);
+ assertEquals("Wrong result length", testString.length, result.length);
+
+ for (int i = 0; i < result.length; i++)
+ {
+ assertEquals("Wrong Byte at " + i, testString[i], result[i]);
+ }
+ }
+ finally
+ {
+
+ if (uis != null)
+ {
+ uis.close();
+ }
+
+ if (is != null)
+ {
+ is.close();
+ }
+ }
+ }
+
+ protected InputStream createInputStream(final UnicodeInputStream.UnicodeBOM bom, final String str, final String enc)
+ throws Exception
+ {
+
+ if (bom == null)
+ {
+ if (str != null)
+ {
+ return new ByteArrayInputStream(str.getBytes(enc));
+ }
+ else
+ {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+ }
+ else
+ {
+ if (str != null)
+ {
+ return new ByteArrayInputStream(ArrayUtils.addAll(bom.getBytes(), str.getBytes(enc)));
+ }
+ else
+ {
+ return new ByteArrayInputStream(ArrayUtils.addAll(bom.getBytes(), new byte[0]));
+ }
+ }
+ }
+
+ protected byte [] buildTestString(final UnicodeInputStream.UnicodeBOM bom, final String str, final String enc, final boolean skipBOM)
+ throws Exception
+ {
+
+ byte [] strBytes = (str != null) ? str.getBytes(enc) : new byte[0];
+
+ if ((bom == null) || skipBOM)
+ {
+ return strBytes;
+ }
+ else
+ {
+ return ArrayUtils.addAll(bom.getBytes(), strBytes);
+ }
+ }
+
+ protected byte [] readAllBytes(final InputStream inputStream, final String enc)
+ throws Exception
+ {
+ InputStreamReader isr = null;
+
+ byte [] res = new byte[0];
+
+ try
+ {
+ byte[] buf = new byte[1024];
+ int read = 0;
+
+ while ((read = inputStream.read(buf)) >= 0)
+ {
+ res = ArrayUtils.addAll(res, ArrayUtils.subarray(buf, 0, read));
+ }
+ }
+ finally
+ {
+
+ if (isr != null)
+ {
+ isr.close();
+ }
+ }
+
+ return res;
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/runtime/RuntimeInstanceTest.java b/velocity-engine-core/src/test/java/org/apache/velocity/runtime/RuntimeInstanceTest.java
new file mode 100644
index 00000000..9b88e209
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/runtime/RuntimeInstanceTest.java
@@ -0,0 +1,49 @@
+package org.apache.velocity.runtime;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.ResourceManager;
+import org.junit.Test;
+
+public class RuntimeInstanceTest {
+
+ @Test
+ public void givenOverridenInputEncoding_whenInitializing_defaultEncodingIsOverridden() {
+ RuntimeInstance instance = new RuntimeInstance();
+ MockResourceManager manager = new MockResourceManager();
+ String value = "testDummyEncoding";
+ instance.addProperty(RuntimeConstants.INPUT_ENCODING, value);
+ instance.addProperty(RuntimeConstants.RESOURCE_MANAGER_INSTANCE, manager);
+ instance.init();
+
+ instance.getTemplate("some template");
+
+ assertEquals(value, manager.encoding);
+
+ }
+
+ class MockResourceManager implements ResourceManager {
+
+ String encoding = null;
+
+ @Override
+ public String getLoaderNameForResource(String resourceName) {
+ return null;
+ }
+
+ @Override
+ public Resource getResource(String resourceName, int resourceType, String encoding)
+ throws ResourceNotFoundException, ParseErrorException {
+ this.encoding = encoding;
+ return null;
+ }
+
+ @Override
+ public void initialize(RuntimeServices rs) {
+
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/AbsoluteFileResourceLoaderTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/AbsoluteFileResourceLoaderTestCase.java
new file mode 100644
index 00000000..abfe75cc
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/AbsoluteFileResourceLoaderTestCase.java
@@ -0,0 +1,150 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Test use of an absolute path with the FileResourceLoader
+ *
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class AbsoluteFileResourceLoaderTestCase extends BaseTestCase
+{
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path to template file. This will get combined with the
+ * application directory to form an absolute path
+ */
+ private final static String TEMPLATE_PATH = TEST_COMPARE_DIR + "/absolute/absolute";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/absolute/results";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/absolute/compare";
+
+ /**
+ * Default constructor.
+ */
+ AbsoluteFileResourceLoaderTestCase()
+ {
+ super("AbsoluteFileResourceLoaderTest");
+ }
+
+ public static Test suite ()
+ {
+ return new AbsoluteFileResourceLoaderTestCase();
+ }
+
+ /**
+ * Runs the test.
+ */
+ @Override
+ public void runTest ()
+ {
+
+ try
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Velocity.reset();
+
+ // signify we want to use an absolute path
+ Velocity.addProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, "");
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+ catch (Exception e)
+ {
+ String msg = "Cannot setup AbsoluteFileResourceLoaderTest!";
+ info(msg, e);
+ fail(msg);
+ }
+ try
+ {
+
+ String curdir = System.getProperty("user.dir");
+ String f = getFileName(curdir, TEMPLATE_PATH, TMPL_FILE_EXT);
+
+ System.out.println("Retrieving template at absolute path: " + f);
+
+ Template template1 = RuntimeSingleton.getTemplate(f);
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "absolute", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+
+ /*
+ * put the Vector into the context, and merge both
+ */
+ VelocityContext context = new VelocityContext();
+
+ template1.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "absolute",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/AlternateValuesTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/AlternateValuesTestCase.java
new file mode 100644
index 00000000..a141c065
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/AlternateValuesTestCase.java
@@ -0,0 +1,61 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 test case that provides utility methods for
+ * the rest of the tests.
+ *
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author Nathan Bubna
+ * @version $Id$
+ */
+public class AlternateValuesTestCase extends BaseTestCase
+{
+ public AlternateValuesTestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testDefault()
+ {
+ assertEvalEquals("<foo>", "<${foo|'foo'}>");
+ assertEvalEquals("bar", "#set($bar='bar')${foo|$bar}");
+ assertEvalEquals("bar", "#set($bar='bar')${bar|'foo'}");
+ assertEvalEquals("bar", "#set($bar='bar')${foo|${bar}}");
+ assertEvalEquals("baz", "${foo|${baz|'baz'}}");
+ assertEvalEquals("hop", "${foo.bar.baz()[5]|'hop'}");
+ assertEvalEquals("{foo}", "{${foo|'foo'}}");
+ assertEvalEquals("<1>", "<${foo|1}>");
+ assertEvalEquals("<1.1>", "<${foo|1.1}>");
+ }
+
+ public void testComplexEval()
+ {
+ assertEvalEquals("<no date tool>", "<${date.format('medium', $date.date)|'no date tool'}>");
+ assertEvalEquals("true", "#set($val=false)${val.toString().replace(\"false\", \"true\")|'so what'}");
+ assertEvalEquals("so what", "#set($foo='foo')${foo.contains('bar')|'so what'}");
+ assertEvalEquals("so what", "#set($val=false)${val.toString().contains('bar')|'so what'}");
+ assertEvalEquals("true", "#set($val=false)${val.toString().contains('false')|'so what'}");
+ assertEvalEquals("", "$!{null|$null}");
+ assertEvalEquals("null", "$!{null|'null'}");
+ assertEvalEquals("so what", "#set($spaces=' ')${spaces.trim()|'so what'}");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ArithmeticTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ArithmeticTestCase.java
new file mode 100644
index 00000000..3bde4a88
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ArithmeticTestCase.java
@@ -0,0 +1,244 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.runtime.parser.node.MathUtils;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * Test arithmetic operations. Introduced after extending from Integer-only
+ * to Number-handling.
+ *
+ * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a>
+ */
+public class ArithmeticTestCase extends TestCase
+{
+
+ public ArithmeticTestCase(String testName)
+ {
+ super(testName);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(ArithmeticTestCase.class);
+ }
+
+ public void testAdd()
+ {
+ addHelper (10, (short) 20, 30, Integer.class);
+ addHelper ((byte) 10, (short) 20, 30, Short.class);
+ addHelper (10f, (short) 20, 30, Float.class);
+ addHelper ((byte) 10, 20d, 30, Double.class);
+ addHelper (BigInteger.valueOf(10), 20, 30, BigInteger.class);
+ addHelper (20, BigDecimal.valueOf(10), 30, BigDecimal.class);
+
+ // Test overflow
+ addHelper (Integer.MAX_VALUE, (short) 20, (double)Integer.MAX_VALUE+20, Long.class);
+ addHelper (20, Long.MAX_VALUE, (double)Long.MAX_VALUE+20, BigInteger.class);
+ addHelper (-20, Long.MIN_VALUE, (double)Long.MIN_VALUE-20, BigInteger.class);
+ }
+
+ private void addHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
+ {
+ Number result = MathUtils.add( n1, n2);
+ assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
+ assertEquals("ResultType does not match.", expectedResultType, result.getClass());
+ }
+
+ public void testSubtract()
+ {
+ subtractHelper (100, (short) 20, 80, Integer.class);
+ subtractHelper ((byte) 100, (short) 20, 80, Short.class);
+ subtractHelper (100f, (short) 20, 80, Float.class);
+ subtractHelper ((byte) 100, 20d, 80, Double.class);
+ subtractHelper (BigInteger.valueOf(100), 20, 80, BigInteger.class);
+ subtractHelper (100, BigDecimal.valueOf(20), 80, BigDecimal.class);
+
+ // Test overflow
+ subtractHelper (Integer.MIN_VALUE, (short) 20, (double)Integer.MIN_VALUE-20, Long.class);
+ subtractHelper(-20, Long.MAX_VALUE, -20d - (double) Long.MAX_VALUE, BigInteger.class);
+ subtractHelper(Integer.MAX_VALUE, Long.MIN_VALUE, (double) Long.MAX_VALUE + (double) Integer.MAX_VALUE, BigInteger.class);
+ }
+
+ private void subtractHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
+ {
+ Number result = MathUtils.subtract( n1, n2);
+ assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
+ assertEquals("ResultType does not match.", expectedResultType, result.getClass());
+ }
+
+ public void testMultiply()
+ {
+ multiplyHelper (10, (short) 20, 200, Integer.class);
+ multiplyHelper ((byte) 100, (short) 20, 2000, Short.class);
+ multiplyHelper ((byte) 100, (short) 2000, 200000, Integer.class);
+ multiplyHelper (100f, (short) 20, 2000, Float.class);
+ multiplyHelper ((byte) 100, 20d, 2000, Double.class);
+ multiplyHelper (BigInteger.valueOf(100), 20, 2000, BigInteger.class);
+ multiplyHelper (100, BigDecimal.valueOf(20), 2000, BigDecimal.class);
+
+ // Test overflow
+ multiplyHelper (Integer.MAX_VALUE, (short) 10, (double)Integer.MAX_VALUE*10d, Long.class);
+ multiplyHelper(Integer.MAX_VALUE, (short) -10, (double) Integer.MAX_VALUE * -10d, Long.class);
+ multiplyHelper(20, Long.MAX_VALUE, 20d * (double) Long.MAX_VALUE, BigInteger.class);
+ }
+
+ private void multiplyHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
+ {
+ Number result = MathUtils.multiply( n1, n2);
+ assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
+ assertEquals("ResultType does not match.", expectedResultType, result.getClass());
+ }
+
+ public void testDivide()
+ {
+ divideHelper (10, (short) 2, 5, Integer.class);
+ divideHelper ((byte) 10, (short) 2, 5, Short.class);
+ divideHelper (BigInteger.valueOf(10), (short) 2, 5, BigInteger.class);
+ divideHelper (10, (short) 4, 2, Integer.class);
+ divideHelper (10, 2.5f, 4, Float.class);
+ divideHelper(10, 2.5, 4, Double.class);
+ divideHelper(10, new BigDecimal(2.5), 4, BigDecimal.class);
+ }
+
+ private void divideHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
+ {
+ Number result = MathUtils.divide( n1, n2);
+ assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
+ assertEquals("ResultType does not match.", expectedResultType, result.getClass());
+ }
+
+ public void testModulo()
+ {
+ moduloHelper (10, (short) 2, 0, Integer.class);
+ moduloHelper ((byte) 10, (short) 3, 1, Short.class);
+ moduloHelper(BigInteger.valueOf(10), (short) 4, 2, BigInteger.class);
+ moduloHelper(10, 5.5f, 4.5, Float.class);
+
+ try
+ {
+ moduloHelper (10, new BigDecimal( 2.5), 4, BigDecimal.class);
+ fail ("Modulo with BigDecimal is not allowed! Should have thrown an ArithmeticException.");
+ }
+ catch( ArithmeticException e)
+ {
+ // do nothing
+ }
+ }
+
+ private void moduloHelper (Number n1, Number n2, double expectedResult, Class expectedResultType)
+ {
+ Number result = MathUtils.modulo( n1, n2);
+ assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
+ assertEquals("ResultType does not match.", expectedResultType, result.getClass());
+ }
+
+ public void testCompare()
+ {
+ compareHelper (10, (short) 10, 0);
+ compareHelper (10, (short) 11, -1);
+ compareHelper (BigInteger.valueOf(10), (short) 11, -1);
+ compareHelper ((byte) 10, (short) 3, 1);
+ compareHelper(10f, (short) 11, -1);
+ compareHelper(10d, (short) 11, -1);
+ }
+
+ private void compareHelper (Number n1, Number n2, int expectedResult)
+ {
+ int result = MathUtils.compare( n1, n2 );
+ assertEquals("The arithmetic operation produced an unexpected result.", expectedResult, result);
+ }
+
+ public void testNegate()
+ {
+ negateHelper((byte) 1, -1, Byte.class);
+ negateHelper((short) 1, -1, Short.class);
+ negateHelper(1, -1, Integer.class);
+ negateHelper(1L, -1, Long.class);
+ negateHelper(BigInteger.valueOf(1), -1, BigInteger.class);
+ negateHelper(BigDecimal.valueOf(1), -1, BigDecimal.class);
+ negateHelper(Long.MIN_VALUE, BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.valueOf(1)).doubleValue(), BigInteger.class);
+ }
+
+ private void negateHelper(Number n, double expectedResult, Class expectedResultType)
+ {
+ Number result = MathUtils.negate(n);
+ assertEquals ("The arithmetic operation produced an unexpected result.", expectedResult, result.doubleValue(), 0.01);
+ assertEquals ("ResultType does not match.", expectedResultType, result.getClass());
+ }
+
+/*
+ *
+ * COMMENT OUT FOR PERFORMANCE-MEASSUREMENTS
+ *
+ * public void testProfile()
+ * {
+ *
+ * long start = System.currentTimeMillis();
+ *
+ * Number v1 = new Long (1000);
+ * Number v2 = new Double (10.23);
+ * Number result = null;
+ * for (int a = 0; a < 10000; a++)
+ * {
+ *
+ * result = MathUtils.typeConvert (
+ * new BigDecimal (v1.doubleValue()).add (
+ * new BigDecimal (v2.doubleValue())), v1, v2, false);
+ *
+ * }
+ *
+ * System.out.println ("took: "+(System.currentTimeMillis()-start));
+ *
+ * start = System.currentTimeMillis();
+ * for (int a = 0; a < 10000; a++)
+ * {
+ *
+ * result = MathUtils.divide( v1, v2);
+ * }
+ *
+ * Number result2 = result;
+ * System.out.println ("took: "+(System.currentTimeMillis()-start));
+ * }
+ *
+ */
+
+ /**
+ * Test additional functions
+ */
+ public void testIsZero()
+ {
+ assertTrue (MathUtils.isZero (0));
+ assertTrue (!MathUtils.isZero (1));
+ assertTrue (!MathUtils.isZero (-1));
+
+ assertTrue (MathUtils.isZero (0f));
+ assertTrue (!MathUtils.isZero (0.00001f));
+ assertTrue (!MathUtils.isZero (-0.00001f));
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ArrayMethodsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ArrayMethodsTestCase.java
new file mode 100644
index 00000000..ceb82e80
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ArrayMethodsTestCase.java
@@ -0,0 +1,211 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Used to check that method calls on Array references work properly
+ * and that they produce the same results as the same methods would on
+ * a fixed-size {@link List}.
+ */
+public class ArrayMethodsTestCase extends BaseTestCase
+{
+ public ArrayMethodsTestCase(final String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testArrayMethods() throws Exception
+ {
+ // test an array of string objects
+ Object array = new String[] { "foo", "bar", "baz" };
+ checkResults(array, "woogie", true);
+
+ // test an array of primitive ints
+ array = new int[] { 1, 3, 7 };
+ checkResults(array, 11, false);
+
+ // test an array of mixed objects, including null
+ array = new Object[] { 2.2, null };
+ checkResults(array, "whatever", true);
+ // then set all the values to null
+ checkResults(array, null, true);
+
+ // then try an empty array
+ array = new Object[] {};
+ checkResults(array, null, true);
+
+ // while we have an empty array and list in the context,
+ // make sure $array.get(0) and $list.get(0) throw
+ // the same type of exception (MethodInvocationException)
+ Throwable lt = null;
+ Throwable at = null;
+ try
+ {
+ evaluate("$list.get(0)");
+ }
+ catch (Throwable t)
+ {
+ lt = t;
+ }
+ try
+ {
+ evaluate("$array.get(0)");
+ }
+ catch (Throwable t)
+ {
+ at = t;
+ }
+ assertEquals(lt.getClass(), at.getClass());
+ }
+
+ private void checkResults(Object array, Object setme,
+ boolean compareToList) throws Exception
+ {
+ context.put("array", array);
+ if (compareToList)
+ {
+ // create a list to match...
+ context.put("list", new ArrayList(Arrays.asList((Object[])array)));
+ }
+
+ // if the object to be set is null, then remove instead of put
+ if (setme != null)
+ {
+ context.put("setme", setme);
+ }
+ else
+ {
+ context.remove("setme");
+ }
+
+ info("Changing to an array of: " + array.getClass().getComponentType());
+ info("Changing setme to: " + setme);
+
+ int size = Array.getLength(array);
+ checkResult("size()", String.valueOf(size), compareToList);
+
+ boolean isEmpty = (size == 0);
+ checkResult("isEmpty()", String.valueOf(isEmpty), compareToList);
+
+ checkPropertyResult("empty", String.valueOf(isEmpty), compareToList);
+
+ // Since 2.1, arrays are rendered the same way as lists
+ String renderArray = evaluate("$array");
+ String renderList = evaluate("$list");
+ System.err.println("<<< " + renderArray);
+ System.err.println(">>> " + renderList);
+ if (compareToList) assertTrue(renderArray.equals(renderList));
+ else assertFalse(renderArray.equals(renderList));
+
+ for (int i=0; i < size; i++)
+ {
+ // put the index in the context, so we can try
+ // both an explicit index and a reference index
+ context.put("index", i);
+
+ Object value = Array.get(array, i);
+ String get = "get($index)";
+ String set = "set("+i+", $setme)";
+ if (value == null)
+ {
+ checkEmptyResult(get, compareToList);
+ // set should return null
+ checkEmptyResult(set, compareToList);
+ }
+ else
+ {
+ checkResult(get, value.toString(), compareToList);
+ // set should return the old get value
+ checkResult(set, value.toString(), compareToList);
+ }
+
+ // check that set() actually changed the value
+ assertEquals(setme, Array.get(array, i));
+
+ // and check that get() now returns setme
+ if (setme == null)
+ {
+ checkEmptyResult(get, compareToList);
+ }
+ else
+ {
+ checkResult(get, setme.toString(), compareToList);
+
+ // now check that contains() properly finds the new value
+ checkResult("contains($setme)", "true", compareToList);
+ }
+ }
+ }
+
+ private void checkEmptyResult(String method, boolean compareToList)
+ throws Exception
+ {
+ checkResult(method, "", compareToList);
+ }
+
+ private void checkResult(String method, String expected,
+ boolean compareToList) throws Exception
+ {
+ String result = evaluate("$!array."+method);
+ assertEquals(expected, result);
+
+ String listResult = null;
+ if (compareToList)
+ {
+ listResult = evaluate("$!list."+method);
+ assertEquals(result, listResult);
+ }
+
+ info(" <$!array." + method + "> resolved to <" + result + ">");
+ if (compareToList)
+ {
+ info(" <$!list."+method+"> resolved to "+listResult+">");
+ }
+ }
+
+ private void checkPropertyResult(String property, String expected,
+ boolean compareToList) throws Exception
+ {
+ String result = evaluate("$!array."+property);
+ assertEquals(expected, result);
+
+ String listResult = null;
+ if (compareToList)
+ {
+ listResult = evaluate("$!list."+property);
+ assertEquals(result, listResult);
+ }
+
+ info(" <$!array."+property+"> resolved to <"+result+">");
+ if (compareToList)
+ {
+ info(" <$!list."+property+"> resolved to "+listResult+">");
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java
new file mode 100644
index 00000000..c6456dff
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/BaseTestCase.java
@@ -0,0 +1,515 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Locale;
+
+/**
+ * Base test case that provides utility methods for
+ * the rest of the tests.
+ *
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author Nathan Bubna
+ * @version $Id$
+ */
+public abstract class BaseTestCase extends TestCase implements TemplateTestBase
+{
+ protected VelocityEngine engine;
+ protected VelocityContext context;
+ protected boolean DEBUG = Boolean.getBoolean("test.debug");
+ protected TestLogger log;
+ protected String stringRepoName = "string.repo";
+
+ public BaseTestCase(String name)
+ {
+ super(name);
+
+ // if we're just running one case, then have DEBUG
+ // automatically set to true
+ String test = System.getProperty("test");
+ if (test != null)
+ {
+ DEBUG = test.equals(getClass().getSimpleName());
+ }
+ }
+
+ protected VelocityEngine createEngine()
+ {
+ VelocityEngine ret = new VelocityEngine();
+ ret.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, log);
+
+ // use string resource loader by default, instead of file
+ ret.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file,string");
+ ret.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+ ret.addProperty("string.resource.loader.repository.name", stringRepoName);
+ ret.addProperty("string.resource.loader.repository.static", "false");
+
+ setUpEngine(ret);
+ return ret;
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ //by default, make the engine's log output go to the test-report
+ log = new TestLogger(false, false);
+ engine = createEngine();
+ context = new VelocityContext();
+ setUpContext(context);
+ }
+
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ // extension hook
+ }
+
+ protected void setUpContext(VelocityContext context)
+ {
+ // extension hook
+ }
+
+ protected StringResourceRepository getStringRepository()
+ {
+ StringResourceRepository repo =
+ (StringResourceRepository)engine.getApplicationAttribute(stringRepoName);
+ if (repo == null)
+ {
+ engine.init();
+ repo =
+ (StringResourceRepository)engine.getApplicationAttribute(stringRepoName);
+ }
+ return repo;
+ }
+
+ protected void addTemplate(String name, String template)
+ {
+ info("Template '"+name+"': "+template);
+ getStringRepository().putStringResource(name, template);
+ }
+
+ protected void removeTemplate(String name)
+ {
+ info("Removed: '"+name+"'");
+ getStringRepository().removeStringResource(name);
+ }
+
+ @Override
+ public void tearDown()
+ {
+ engine = null;
+ context = null;
+ }
+
+ protected void info(String msg)
+ {
+ info(msg, null);
+ }
+
+ protected void info(String msg, Throwable t)
+ {
+ if (DEBUG)
+ {
+ try
+ {
+ if (engine == null)
+ {
+ Velocity.getLog().info(msg, t);
+ }
+ else
+ {
+ engine.getLog().info(msg, t);
+ }
+ }
+ catch (Throwable t2)
+ {
+ System.out.println("Failed to log: "+msg+(t!=null?" - "+t: ""));
+ System.out.println("Cause: "+t2);
+ t2.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Compare an expected string with the given loaded template
+ */
+ protected void assertTmplEquals(String expected, String template)
+ {
+ info("Expected: " + expected + " from '" + template + "'");
+
+ StringWriter writer = new StringWriter();
+ try
+ {
+ engine.mergeTemplate(template, "utf-8", context, writer);
+ }
+ catch (RuntimeException re)
+ {
+ info("RuntimeException!", re);
+ throw re;
+ }
+ catch (Exception e)
+ {
+ info("Exception!", e);
+ throw new RuntimeException(e);
+ }
+
+ info("Result: " + writer.toString());
+ assertEquals(expected, writer.toString());
+ }
+
+ /**
+ * Ensure that a context value is as expected.
+ */
+ protected void assertContextValue(String key, Object expected)
+ {
+ info("Expected value of '"+key+"': "+expected);
+ Object value = context.get(key);
+ info("Result: "+value);
+ assertEquals(expected, value);
+ }
+
+ /**
+ * Ensure that a template renders as expected.
+ */
+ protected void assertEvalEquals(String expected, String template)
+ {
+ info("Expectation: "+expected);
+ assertEquals(expected, evaluate(template));
+ }
+
+ /**
+ * Ensure that the given string renders as itself when evaluated.
+ */
+ protected void assertSchmoo(String templateIsExpected)
+ {
+ assertEvalEquals(templateIsExpected, templateIsExpected);
+ }
+
+ /**
+ * Ensure that an exception occurs when the string is evaluated.
+ */
+ protected Exception assertEvalException(String evil)
+ {
+ return assertEvalException(evil, null);
+ }
+
+ /**
+ * Ensure that a specified type of exception occurs when evaluating the string.
+ */
+ protected Exception assertEvalException(String evil, Class<?> exceptionType)
+ {
+ try
+ {
+ if (!DEBUG)
+ {
+ log.off();
+ }
+ if (exceptionType != null)
+ {
+ info("Expectation: "+exceptionType.getName());
+ }
+ else
+ {
+ info("Expectation: "+Exception.class.getName());
+ }
+ evaluate(evil);
+ String msg = "Template '"+evil+"' should have thrown an exception.";
+ info("Fail: "+msg);
+ fail(msg);
+ }
+ catch (Exception e)
+ {
+ if (exceptionType != null && !exceptionType.isAssignableFrom(e.getClass()))
+ {
+ String msg = "Was expecting template '"+evil+"' to throw "+exceptionType+" not "+e;
+ info("Fail: "+msg);
+ fail(msg);
+ }
+ return e;
+ }
+ finally
+ {
+ if (!DEBUG)
+ {
+ log.on();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Ensure that the error message of the expected exception has the proper location info.
+ */
+ protected Exception assertEvalExceptionAt(String evil, String template,
+ int line, int col)
+ {
+ String loc = template+"[line "+line+", column "+col+"]";
+ info("Expectation: Exception at "+loc);
+ Exception e = assertEvalException(evil);
+
+ info("Result: "+e.getClass().getName()+" - "+e.getMessage());
+ if (e.getMessage().indexOf(loc) < 1)
+ {
+ fail("Was expecting exception at "+loc+" instead of "+e.getMessage());
+ }
+ return e;
+ }
+
+ /**
+ * Only ensure that the error message of the expected exception
+ * has the proper line and column info.
+ */
+ protected Exception assertEvalExceptionAt(String evil, int line, int col)
+ {
+ return assertEvalExceptionAt(evil, "", line, col);
+ }
+
+ /**
+ * Evaluate the specified String as a template and return the result as a String.
+ */
+ protected String evaluate(String template)
+ {
+ StringWriter writer = new StringWriter();
+ try
+ {
+ info("Template: "+template);
+
+ // use template as its own name, since our templates are short
+ // unless it's not that short, then shorten it...
+ String name = (template.length() <= 15) ? template : template.substring(0,15);
+ engine.evaluate(context, writer, name, template);
+
+ String result = writer.toString();
+ info("Result: "+result);
+ return result;
+ }
+ catch (RuntimeException re)
+ {
+ info("RuntimeException!", re);
+ throw re;
+ }
+ catch (Exception e)
+ {
+ info("Exception!", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Concatenates the file name parts together appropriately.
+ *
+ * @return The full path to the file.
+ */
+ protected String getFileName(final String dir, final String base, final String ext)
+ {
+ return getFileName(dir, base, ext, false);
+ }
+
+ protected String getFileName(final String dir, final String base, final String ext, final boolean mustExist)
+ {
+ StringBuilder buf = new StringBuilder();
+ try
+ {
+ File baseFile = new File(base);
+ if (dir != null)
+ {
+ if (!baseFile.isAbsolute())
+ {
+ baseFile = new File(dir, base);
+ }
+
+ buf.append(baseFile.getCanonicalPath());
+ }
+ else
+ {
+ buf.append(baseFile.getPath());
+ }
+
+ if (org.apache.commons.lang3.StringUtils.isNotEmpty(ext))
+ {
+ buf.append('.').append(ext);
+ }
+
+ if (mustExist)
+ {
+ File testFile = new File(buf.toString());
+
+ if (!testFile.exists())
+ {
+ String msg = "getFileName() result " + testFile.getPath() + " does not exist!";
+ info(msg);
+ fail(msg);
+ }
+
+ if (!testFile.isFile())
+ {
+ String msg = "getFileName() result " + testFile.getPath() + " is not a file!";
+ info(msg);
+ fail(msg);
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ fail("IO Exception while running getFileName(" + dir + ", " + base + ", "+ ext + ", " + mustExist + "): " + e.getMessage());
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * Assures that the results directory exists. If the results directory
+ * cannot be created, fails the test.
+ */
+ protected void assureResultsDirectoryExists(String resultsDirectory)
+ {
+ File dir = new File(resultsDirectory);
+ if (!dir.exists())
+ {
+ info("Template results directory ("+resultsDirectory+") does not exist");
+ if (dir.mkdirs())
+ {
+ info("Created template results directory");
+ if (DEBUG)
+ {
+ info("Created template results directory: "+resultsDirectory);
+ }
+ }
+ else
+ {
+ String errMsg = "Unable to create '"+resultsDirectory+"'";
+ info(errMsg);
+ fail(errMsg);
+ }
+ }
+ }
+
+
+ /**
+ * Normalizes lines to account for platform differences. Macs use
+ * a single \r, DOS derived operating systems use \r\n, and Unix
+ * uses \n. Replace each with a single \n.
+ *
+ * @return source with all line terminations changed to Unix style
+ */
+ protected String normalizeNewlines (String source)
+ {
+ return source.replaceAll("\r\n?", "\n");
+ }
+
+ /**
+ * Returns whether the processed template matches the
+ * content of the provided comparison file.
+ *
+ * @return Whether the output matches the contents
+ * of the comparison file.
+ *
+ * @exception Exception Test failure condition.
+ */
+ protected boolean isMatch (String resultsDir,
+ String compareDir,
+ String baseFileName,
+ String resultExt,
+ String compareExt) throws Exception
+ {
+ if (DEBUG)
+ {
+ info("Result: "+resultsDir+'/'+baseFileName+'.'+resultExt);
+ }
+ String result = getFileContents(resultsDir, baseFileName, resultExt);
+ return isMatch(result,compareDir,baseFileName,compareExt);
+ }
+
+
+ protected String getFileContents(String dir, String baseFileName, String ext)
+ {
+ String fileName = getFileName(dir, baseFileName, ext, true);
+ return getFileContents(fileName);
+ }
+
+ protected String getFileContents(String file)
+ {
+ String contents = null;
+
+ try
+ {
+ contents = new String(Files.readAllBytes(Paths.get(file)), StandardCharsets.UTF_8);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ return contents;
+ }
+
+ /**
+ * Returns whether the processed template matches the
+ * content of the provided comparison file.
+ *
+ * @return Whether the output matches the contents
+ * of the comparison file.
+ *
+ * @exception Exception Test failure condition.
+ */
+ protected boolean isMatch (String result,
+ String compareDir,
+ String baseFileName,
+ String compareExt) throws Exception
+ {
+ String compare = getFileContents(compareDir, baseFileName, compareExt);
+
+ // normalize each wrt newline
+ result = normalizeNewlines(result);
+ compare = normalizeNewlines(compare);
+ if (DEBUG)
+ {
+ info("Expection: "+compareDir+'/'+baseFileName+'.'+compareExt);
+ }
+ return result.equals(compare);
+ }
+
+ /**
+ * Turns a base file name into a test case name.
+ *
+ * @param s The base file name.
+ * @return The test case name.
+ */
+ protected static String getTestCaseName(String s)
+ {
+ StringBuilder name = new StringBuilder();
+ name.append(Character.toTitleCase(s.charAt(0)));
+ name.append(s.substring(1, s.length()).toLowerCase(Locale.ROOT));
+ return name.toString();
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/BlockMacroTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/BlockMacroTestCase.java
new file mode 100644
index 00000000..8eb3f7b5
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/BlockMacroTestCase.java
@@ -0,0 +1,141 @@
+package org.apache.velocity.test;
+
+/*
+ * 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;
+
+/**
+ * This class tests the BlockMacro functionality.
+ */
+public class BlockMacroTestCase extends BaseTestCase
+{
+ public BlockMacroTestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testMultipleBodyContentIncludes() throws Exception
+ {
+ String template = "#macro(foo $txt) Yeah, $txt! $bodyContent $bodyContent#end #@foo(\"woohoo\")jee#end";
+ String result = " Yeah, woohoo! jee jee";
+
+ assertEvalEquals(result, template);
+ }
+
+ public void testNestedVelocityLogic() throws Exception
+ {
+ String template = "#macro(foo $txt) Yeah, $txt! $bodyContent#end #@foo(\"woohoo\")#foreach($i in [1..3])$i:#{end}#end";
+ String result = " Yeah, woohoo! 1:2:3:";
+
+ assertEvalEquals(result, template);
+ }
+
+ public void testEmptyBody() throws Exception
+ {
+ String template = "#macro(foo $txt) Yeah, $txt! $bodyContent#end #@foo(\"woohoo\")#end";
+ String result = " Yeah, woohoo! ";
+
+ assertEvalEquals(result, template);
+ }
+
+ public void testNoArgumentsEmptyBodyCall() throws Exception
+ {
+ String template = "#macro(foo) Yeah! $bodyContent#end #@foo()#end";
+ String result = " Yeah! ";
+
+ assertEvalEquals(result, template);
+ }
+
+ public void testCustomBodyReference() throws Exception
+ {
+ engine.setProperty(RuntimeConstants.VM_BODY_REFERENCE, "myBody");
+ String template = "#macro(foo) Yeah! $myBody#end #@foo()#end";
+ String result = " Yeah! ";
+
+ assertEvalEquals(result, template);
+ }
+
+ public void testVelocity671() throws Exception
+ {
+ engine.setProperty(RuntimeConstants.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ String template = "#macro(echo)$bodyContent#end #@echo()Yeah!#end";
+ String result = " Yeah!";
+ assertEvalEquals(result, template);
+ }
+
+ public void testStrict()
+ {
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, true);
+ assertEvalException("#@foo#end");
+ assertEvalException("#@foo()#end");
+ }
+
+ public void testVelocity690()
+ {
+ assertEvalEquals(" output ", "#macro(foo) output #end#@foo #end");
+ assertEvalEquals("#[ output )", "#macro(foo2)#[$bodyContent)#end#@foo2 output #end");
+ assertEvalEquals("#[output)", "#macro(foo2)#[$bodyContent)#end#{@foo2}output#end");
+ assertEvalException("#macro(foo) output #end#@foo");
+ }
+
+ public void testVelocity675() throws Exception
+ {
+ assertEvalEquals("#@foo#end", "#@foo#end");
+ }
+
+ public void testVelocity685() throws Exception
+ {
+ engine.setProperty(RuntimeConstants.VM_ARGUMENTS_STRICT, Boolean.TRUE);
+ assertEvalEquals(" ", "#macro(foo)#end #@foo() junk #end");
+ }
+
+ public void testVelocity686() throws Exception
+ {
+ String template = "#macro(foo)#set( $x = $bodyContent )#end"+
+ "#@foo()b#end a $x ";
+ assertEvalEquals(" a b ", template);
+ }
+
+ public void testNestedBlockMacro()
+ {
+ String template = "#macro(foo)foo:$bodyContent#end"+
+ "#macro(bar)bar:$bodyContent#end"+
+ "#@foo()foo,#@bar()bar#end#end";
+ assertEvalEquals("foo:foo,bar:bar", template);
+ }
+
+ public void testRecursiveBlockMacro()
+ {
+ engine.setProperty(RuntimeConstants.VM_MAX_DEPTH, 3);
+ String template = "#macro(foo)start:$bodyContent#end"+
+ "#@foo()call:$bodyContent#end";
+ assertEvalEquals("start:call:call:call:$bodyContent", template);
+ }
+
+ public void testBlueJoesProblem()
+ {
+ engine.setProperty("macro."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, Boolean.TRUE);
+ addTemplate("a", "#macro(wrap $layout)$!macro.put($layout,$bodyContent)#parse($layout)#end"+
+ "#@wrap('b')a#end");
+ addTemplate("b", "#@wrap('c')b$!macro.get('b')b#end");
+ addTemplate("c", "c$!macro.get('c')c");
+ assertTmplEquals("cbabc", "a");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/BreakDirectiveTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/BreakDirectiveTestCase.java
new file mode 100644
index 00000000..0f0cb616
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/BreakDirectiveTestCase.java
@@ -0,0 +1,117 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityEngine;
+
+/**
+ * This class tests the break directive.
+ */
+public class BreakDirectiveTestCase extends BaseTestCase
+{
+ public BreakDirectiveTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty("a.provide.scope.control", "true");
+ engine.setProperty("define.provide.scope.control", "true");
+ engine.setProperty("evaluate.provide.scope.control", "true");
+ engine.setProperty("macro.provide.scope.control", "true");
+ engine.setProperty("template.provide.scope.control", "true");
+ }
+
+ public void testBadArgs()
+ {
+ context.put("foo","foo");
+ assertEvalException("#break($null)");
+ assertEvalException("#break($foo)");
+ assertEvalException("#break(true)");
+ assertEvalException("#break(1.2)");
+ assertEvalException("#break([0..1])");
+ assertEvalException("#break( $too $many )");
+ }
+
+ public void testStopForeach()
+ {
+ String template = "#foreach($i in [1..5])$i#if($i>2)#break($foreach)#end#end test";
+ assertEvalEquals("123 test", template);
+
+ // only inner should be stopped, not outer
+ String t2 = "#foreach($j in [1..2])"+template+"#end";
+ assertEvalEquals("123 test123 test", t2);
+
+ // stop outer using #break($foreach.parent)
+ String t3 = "#foreach($i in [1..2])#foreach($j in [2..3])$i$j#if($i+$j==5)#break($foreach.parent)#end#end test#end";
+ assertEvalEquals("1213 test2223", t3);
+
+ // without specifying scope...
+ assertEvalEquals("1, 2, 3, 4, 5",
+ "#foreach($i in [1..10])$i#if($i > 4)#break#end, #end");
+ assertEvalEquals("1", "#foreach($i in [1..5])$i#break #end");
+ assertEvalEquals("~~~, ~~, ~, ",
+ "#foreach($i in [1..3])#foreach($j in [2..4])#if($i*$j >= 8)#break#end~#end, #end");
+ }
+
+ public void testStopTemplate()
+ {
+ addTemplate("a", "a#break($template)b");
+ assertTmplEquals("a", "a");
+ assertEvalEquals("ac", "#parse('a')c");
+
+ addTemplate("b", "b#{break}a");
+ assertTmplEquals("b", "b");
+ }
+
+ public void testStopEvaluate()
+ {
+ assertEvalEquals("a", "a#break($evaluate)b");
+ assertEvalEquals("a", "#evaluate('a#break($evaluate)b')");
+ assertEvalEquals("a", "a#evaluate('#break($evaluate.topmost)')b");
+ assertEvalEquals("a", "a#{break}b");
+ }
+
+ public void testStopDefineBlock()
+ {
+ assertEvalEquals("a", "#define($a)a#break($define)b#end$a");
+ assertEvalEquals("aa", "#define($a)a#break($define.parent)b#end#define($b)a${a}b#end$b");
+ assertEvalEquals("a", "#define($a)a#{break}b#end$a");
+ }
+
+ public void testStopMacro()
+ {
+ assertEvalEquals("a ", "#macro(a)a #break($macro) b#end#a");
+ assertEvalEquals("b c ", "#macro(c)c #break($macro.parent) d#end"+
+ "#macro(b)b #c c#end"+
+ "#b");
+ assertEvalEquals("d", "#macro(d)d#{break}e#end#d");
+ }
+
+ public void testStopMacroBodyBlock()
+ {
+ assertEvalEquals(" a ", "#macro(a) $bodyContent #end"+
+ "#@a()a#break($a)b#end");
+ assertEvalEquals(" b ", "#macro(b) $bodyContent #end"+
+ "#@b()b#{break}c#end");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/BuiltInEventHandlerTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/BuiltInEventHandlerTestCase.java
new file mode 100644
index 00000000..c415e476
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/BuiltInEventHandlerTestCase.java
@@ -0,0 +1,592 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.app.event.EventCartridge;
+import org.apache.velocity.app.event.implement.EscapeHtmlReference;
+import org.apache.velocity.app.event.implement.EscapeJavaScriptReference;
+import org.apache.velocity.app.event.implement.EscapeReference;
+import org.apache.velocity.app.event.implement.EscapeSqlReference;
+import org.apache.velocity.app.event.implement.EscapeXmlReference;
+import org.apache.velocity.app.event.implement.InvalidReferenceInfo;
+import org.apache.velocity.app.event.implement.ReportInvalidReferences;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests the operation of the built in event handlers.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class BuiltInEventHandlerTestCase extends BaseTestCase {
+
+ protected boolean DEBUG = false;
+
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/includeevent";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/includeevent";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/includeevent/compare";
+
+ /**
+ * Default constructor.
+ */
+ public BuiltInEventHandlerTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+ super.setUp();
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(BuiltInEventHandlerTestCase.class);
+ }
+
+ protected void log(String out)
+ {
+ if (DEBUG)
+ {
+ System.out.println (out);
+ }
+ }
+
+ /**
+ * Test reporting of invalid syntax
+ * @throws Exception
+ */
+ public void testReportInvalidReferences1() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ReportInvalidReferences reporter = new ReportInvalidReferences();
+ ve.init();
+
+ VelocityContext context = new VelocityContext();
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(reporter);
+ ec.attachToContext(context);
+
+ context.put("a1","test");
+ context.put("b1","test");
+ context.put("n1", null);
+ Writer writer = new StringWriter();
+
+ ve.evaluate(context,writer,"test","$a1 $c1 $a1.length() $a1.foobar() $!c1 $n1 $!n1 #if($c1) nop #end");
+
+ List errors = reporter.getInvalidReferences();
+ assertEquals(2,errors.size());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(0)).getInvalidReference());
+ assertEquals("$a1.foobar()",((InvalidReferenceInfo) errors.get(1)).getInvalidReference());
+
+ log("Caught invalid references (local configuration).");
+ }
+
+ public void testReportInvalidReferences2() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty("event_handler.invalid_references.exception","true");
+ ReportInvalidReferences reporter = new ReportInvalidReferences();
+ ve.init();
+
+ VelocityContext context = new VelocityContext();
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(reporter);
+ ec.attachToContext(context);
+
+ context.put("a1","test");
+ context.put("b1","test");
+ Writer writer = new StringWriter();
+
+ ve.evaluate(context,writer,"test","$a1 no problem");
+
+ try {
+ ve.evaluate(context,writer,"test","$a1 $c1 $a1.length() $a1.foobar()");
+ fail ("Expected exception.");
+ } catch (RuntimeException E) {}
+
+
+ log("Caught invalid references (global configuration).");
+
+ }
+
+ /**
+ * Test reporting of invalid syntax
+ * @throws Exception
+ */
+ public void testReportQuietInvalidReferences() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty("event_handler.invalid_references.quiet","true");
+ ReportInvalidReferences reporter = new ReportInvalidReferences();
+ ve.init();
+
+ VelocityContext context = new VelocityContext();
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(reporter);
+ ec.attachToContext(context);
+
+ context.put("a1","test");
+ context.put("b1","test");
+ context.put("n1", null);
+ Writer writer = new StringWriter();
+
+ ve.evaluate(context,writer,"test","$a1 $c1 $a1.length() $a1.foobar() $!c1 $n1 $!n1 #if($c1) nop #end");
+
+ List errors = reporter.getInvalidReferences();
+ assertEquals(3,errors.size());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(0)).getInvalidReference());
+ assertEquals("$a1.foobar()",((InvalidReferenceInfo) errors.get(1)).getInvalidReference());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(2)).getInvalidReference());
+
+ log("Caught invalid references (local configuration).");
+ }
+
+ /**
+ * Test reporting of invalid syntax
+ * @throws Exception
+ */
+ public void testReportNullInvalidReferences() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty("event_handler.invalid_references.null","true");
+ ReportInvalidReferences reporter = new ReportInvalidReferences();
+ ve.init();
+
+ VelocityContext context = new VelocityContext();
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(reporter);
+ ec.attachToContext(context);
+
+ context.put("a1","test");
+ context.put("b1","test");
+ context.put("n1", null);
+ Writer writer = new StringWriter();
+
+ ve.evaluate(context,writer,"test","$a1 $c1 $a1.length() $a1.foobar() $!c1 $n1 $!n1 #if($c1) nop #end");
+
+ List errors = reporter.getInvalidReferences();
+ assertEquals(3,errors.size());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(0)).getInvalidReference());
+ assertEquals("$a1.foobar()",((InvalidReferenceInfo) errors.get(1)).getInvalidReference());
+ assertEquals("$n1",((InvalidReferenceInfo) errors.get(2)).getInvalidReference());
+
+ log("Caught invalid references (local configuration).");
+ }
+
+ /**
+ * Test reporting of invalid syntax
+ * @throws Exception
+ */
+ public void testReportNullQuietInvalidReferences() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty("event_handler.invalid_references.quiet","true");
+ ve.setProperty("event_handler.invalid_references.null","true");
+ ReportInvalidReferences reporter = new ReportInvalidReferences();
+ ve.init();
+
+ VelocityContext context = new VelocityContext();
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(reporter);
+ ec.attachToContext(context);
+
+ context.put("a1","test");
+ context.put("b1","test");
+ context.put("n1", null);
+ Writer writer = new StringWriter();
+
+ ve.evaluate(context,writer,"test","$a1 $c1 $a1.length() $a1.foobar() $!c1 $n1 $!n1 #if($c1) nop #end");
+
+ List errors = reporter.getInvalidReferences();
+ assertEquals(5,errors.size());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(0)).getInvalidReference());
+ assertEquals("$a1.foobar()",((InvalidReferenceInfo) errors.get(1)).getInvalidReference());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(2)).getInvalidReference());
+ assertEquals("$n1",((InvalidReferenceInfo) errors.get(3)).getInvalidReference());
+ assertEquals("$n1",((InvalidReferenceInfo) errors.get(4)).getInvalidReference());
+
+ log("Caught invalid references (local configuration).");
+ }
+
+ /**
+ * Test reporting of invalid syntax
+ * @throws Exception
+ */
+ public void testReportTestedInvalidReferences() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty("event_handler.invalid_references.tested","true");
+ ReportInvalidReferences reporter = new ReportInvalidReferences();
+ ve.init();
+
+ VelocityContext context = new VelocityContext();
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(reporter);
+ ec.attachToContext(context);
+
+ context.put("a1","test");
+ context.put("b1","test");
+ context.put("n1", null);
+ Writer writer = new StringWriter();
+
+ ve.evaluate(context,writer,"test","$a1 $c1 $a1.length() $a1.foobar() $!c1 $n1 $!n1 #if($c1) nop #end");
+
+ List errors = reporter.getInvalidReferences();
+ assertEquals(3,errors.size());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(0)).getInvalidReference());
+ assertEquals("$a1.foobar()",((InvalidReferenceInfo) errors.get(1)).getInvalidReference());
+ assertEquals("$c1",((InvalidReferenceInfo) errors.get(2)).getInvalidReference());
+
+ log("Caught invalid references (local configuration).");
+ }
+
+ /**
+ * Test escaping
+ * @throws Exception
+ */
+ public void testEscapeHtml() throws Exception
+ {
+ EscapeReference esc = new EscapeHtmlReference();
+ assertEquals("test string&amp;another&lt;b&gt;bold&lt;/b&gt;test",esc.referenceInsert(null,"","test string&another<b>bold</b>test"));
+ assertEquals("&lt;&quot;&gt;",esc.referenceInsert(null,"","<\">"));
+ assertEquals("test string",esc.referenceInsert(null,"","test string"));
+
+ log("Correctly escaped HTML");
+
+ }
+
+ /**
+ * Test escaping
+ * @throws Exception
+ */
+ public void testEscapeXml() throws Exception
+ {
+ EscapeReference esc = new EscapeXmlReference();
+ assertEquals("test string&amp;another&lt;b&gt;bold&lt;/b&gt;test",esc.referenceInsert(null,"","test string&another<b>bold</b>test"));
+ assertEquals("&lt;&quot;&gt;",esc.referenceInsert(null,"","<\">"));
+ assertEquals("&apos;",esc.referenceInsert(null,"","'"));
+ assertEquals("test string",esc.referenceInsert(null,"","test string"));
+
+ log("Correctly escaped XML");
+
+ }
+
+ /**
+ * Test escaping
+ * @throws Exception
+ */
+ public void testEscapeSql() throws Exception
+ {
+ EscapeReference esc = new EscapeSqlReference();
+ assertEquals("Jimmy''s Pizza",esc.referenceInsert(null,"","Jimmy's Pizza"));
+ assertEquals("test string",esc.referenceInsert(null,"","test string"));
+
+ log("Correctly escaped SQL");
+
+ }
+
+ /**
+ * Test escaping
+ * @throws Exception
+ */
+ public void testEscapeJavaScript() throws Exception
+ {
+ EscapeReference esc = new EscapeJavaScriptReference();
+ assertEquals("Jimmy\\'s Pizza",esc.referenceInsert(null,"","Jimmy's Pizza"));
+ assertEquals("test string",esc.referenceInsert(null,"","test string"));
+
+
+ log("Correctly escaped Javascript");
+ }
+
+ /**
+ * test that escape reference handler works with no match restrictions
+ * @throws Exception
+ */
+ public void testEscapeReferenceMatchAll() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, "org.apache.velocity.app.event.implement.EscapeHtmlReference");
+ ve.init();
+
+ Context context;
+ Writer writer;
+
+ // test normal reference
+ context = new VelocityContext();
+ writer = new StringWriter();
+ context.put("bold","<b>");
+ ve.evaluate(context,writer,"test","$bold test & test");
+ assertEquals("&lt;b&gt; test & test",writer.toString());
+
+ // test method reference
+ context = new VelocityContext();
+ writer = new StringWriter();
+ context.put("bold","<b>");
+ ve.evaluate(context,writer,"test","$bold.substring(0,1)");
+ assertEquals("&lt;",writer.toString());
+
+ log("Escape matched all references (global configuration)");
+
+ }
+
+ /**
+ * test that escape reference handler works with match restrictions
+ * @throws Exception
+ */
+ public void testEscapeReferenceMatch() throws Exception
+ {
+ // set up HTML match on everything, JavaScript match on _js*
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, "org.apache.velocity.app.event.implement.EscapeHtmlReference,org.apache.velocity.app.event.implement.EscapeJavaScriptReference");
+ ve.setProperty("eventhandler.escape.javascript.match", "/.*_js.*/");
+ ve.init();
+
+ Writer writer;
+
+ // Html no JavaScript
+ writer = new StringWriter();
+ ve.evaluate(newEscapeContext(),writer,"test","$test1");
+ assertEquals("Jimmy's &lt;b&gt;pizza&lt;/b&gt;",writer.toString());
+
+ // comment out bad test -- requires latest commons-lang
+ /*
+
+ // JavaScript and HTML
+ writer = new StringWriter();
+ ve.evaluate(newEscapeContext(),writer,"test","$test1_js");
+ assertEquals("Jimmy\\'s &lt;b&gt;pizza&lt;/b&gt;",writer.toString());
+
+ // JavaScript and HTML
+ writer = new StringWriter();
+ ve.evaluate(newEscapeContext(),writer,"test","$test1_js_test");
+ assertEquals("Jimmy\\'s &lt;b&gt;pizza&lt;/b&gt;",writer.toString());
+
+ // JavaScript and HTML (method call)
+ writer = new StringWriter();
+ ve.evaluate(newEscapeContext(),writer,"test","$test1_js.substring(0,7)");
+ assertEquals("Jimmy\\'s",writer.toString());
+
+ **/
+
+ log("Escape selected references (global configuration)");
+
+
+
+ }
+
+ private Context newEscapeContext()
+ {
+ Context context = new VelocityContext();
+ context.put("test1","Jimmy's <b>pizza</b>");
+ context.put("test1_js","Jimmy's <b>pizza</b>");
+ context.put("test1_js_test","Jimmy's <b>pizza</b>");
+ return context;
+ }
+
+ public void testPrintExceptionHandler() throws Exception
+ {
+ VelocityEngine ve1 = new VelocityEngine();
+ ve1.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, "org.apache.velocity.app.event.implement.PrintExceptions");
+ ve1.init();
+
+ VelocityEngine ve2 = new VelocityEngine();
+ ve2.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, "org.apache.velocity.app.event.implement.PrintExceptions");
+ ve2.setProperty("eventhandler.methodexception.templateinfo","true");
+ ve2.init();
+
+ VelocityEngine ve3 = new VelocityEngine();
+ ve3.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, "org.apache.velocity.app.event.implement.PrintExceptions");
+ ve3.setProperty("eventhandler.methodexception.stacktrace","true");
+ ve3.init();
+
+ Context context;
+ StringWriter writer;
+
+ context = new VelocityContext();
+ context.put("list",new ArrayList());
+
+ // exception and message only
+ writer = new StringWriter();
+ ve1.evaluate(context, writer, "test", "$list.get(0)");
+ String result = writer.toString();
+ assertTrue(result.contains("IndexOutOfBoundsException"));
+ assertTrue(
+ result.contains("Index: 0, Size: 0") // JDK8
+ || result.contains("Index 0 out of bounds for length 0") // JDK 11 / JDK 15
+ );
+ assertTrue(!result.contains("at test (line 1, column 7)"));
+ assertFalse(
+ result.contains("rangeCheck") // JDK 8
+ || result.contains("Preconditions.outOfBounds") // JDK 11 / JDK 15
+ );
+
+ // exception, message and template info
+ writer = new StringWriter();
+ ve2.evaluate(context,writer,"test","$list.get(0)");
+ result = writer.toString();
+ assertTrue(result.contains("IndexOutOfBoundsException"));
+ assertTrue(
+ result.contains("Index: 0, Size: 0") // JDK8
+ || result.contains("Index 0 out of bounds for length 0") // JDK 11 / JDK 15
+ );
+ assertTrue(result.contains("at test (line 1, column 7)"));
+ assertFalse(
+ result.contains("rangeCheck") // JDK 8
+ || result.contains("Preconditions.outOfBounds") // JDK 11 / JDK 15
+ );
+
+ // exception, message and stack trace
+ writer = new StringWriter();
+ ve3.evaluate(context,writer,"test","$list.get(0)");
+ result = writer.toString();
+ assertTrue(result.contains("IndexOutOfBoundsException"));
+ assertTrue(
+ result.contains("Index: 0, Size: 0") // JDK8
+ || result.contains("Index 0 out of bounds for length 0") // JDK 11 / JDK 15
+ );
+ assertTrue(!result.contains("at test (line 1, column 7)"));
+ assertTrue(
+ result.contains("rangeCheck") // JDK 8
+ || result.contains("Preconditions.outOfBounds") // JDK 11 / JDK 15
+ );
+
+ log("PrintException handler successful.");
+
+ }
+
+ public void testIncludeNotFound() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, "org.apache.velocity.app.event.implement.IncludeNotFound");
+ ve.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ ve.init();
+
+ Template template;
+ FileOutputStream fos;
+ Writer fwriter;
+ Context context;
+
+ template = ve.getTemplate( getFileName(null, "test6", TMPL_FILE_EXT) );
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, "test6", RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ context = new VelocityContext();
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "test6", RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+
+ log("IncludeNotFound handler successful.");
+
+ }
+
+ public void testIncludeNotFoundMissingResourceName() throws Exception
+ {
+ // uses base test support
+ engine.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, "org.apache.velocity.app.event.implement.IncludeNotFound");
+ addTemplate("notfound.vm", "$missingResource");
+ assertEvalEquals("foo", "#parse('foo')");
+ }
+
+ public void testIncludeRelativePath() throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, "org.apache.velocity.app.event.implement.IncludeRelativePath");
+ ve.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ ve.init();
+
+ Template template;
+ FileOutputStream fos;
+ Writer fwriter;
+ Context context;
+
+ template = ve.getTemplate( getFileName(null, "subdir/test2", TMPL_FILE_EXT) );
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, "test2", RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ context = new VelocityContext();
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "test2", RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+
+ log("IncludeRelativePath handler successful.");
+
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java
new file mode 100644
index 00000000..096dbe7b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ClassloaderChangeTestCase.java
@@ -0,0 +1,169 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.commons.io.IOUtils;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.IntrospectorCache;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+
+/**
+ * Tests if we can hand Velocity an arbitrary class for logging.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ClassloaderChangeTestCase extends TestCase
+{
+ private VelocityEngine ve = null;
+ private TestLogger logger = null;
+
+ private static String OUTPUT = "Hello From Foo";
+
+ /**
+ * Default constructor.
+ */
+ public ClassloaderChangeTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ logger = new TestLogger(false, true);
+ logger.setEnabledLevel(TestLogger.LOG_LEVEL_DEBUG);
+ ve.setProperty(VelocityEngine.RUNTIME_LOG_INSTANCE, logger);
+ ve.init();
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(ClassloaderChangeTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testClassloaderChange()
+ throws Exception
+ {
+ logger.on();
+
+ VelocityContext vc = new VelocityContext();
+ Object foo = null;
+
+ /*
+ * first, we need a classloader to make our foo object
+ */
+
+ TestClassloader cl = new TestClassloader();
+ Class<?> fooclass = cl.loadClass("Foo");
+ foo = fooclass.newInstance();
+
+ /*
+ * put it into the context
+ */
+ vc.put("foo", foo);
+
+ /*
+ * and render something that would use it
+ * that will get it into the introspector cache
+ */
+ StringWriter writer = new StringWriter();
+ ve.evaluate( vc, writer, "test", "$foo.doIt()");
+
+ /*
+ * Check to make sure ok. note the obvious
+ * dependency on the Foo class...
+ */
+
+ if ( !writer.toString().equals( OUTPUT ))
+ {
+ fail("Output from doIt() incorrect");
+ }
+
+ /*
+ * and do it again :)
+ */
+ cl = new TestClassloader();
+ fooclass = cl.loadClass("Foo");
+ foo = fooclass.newInstance();
+
+ vc.put("foo", foo);
+
+ writer = new StringWriter();
+ ve.evaluate( vc, writer, "test", "$foo.doIt()");
+
+ if ( !writer.toString().equals( OUTPUT ))
+ {
+ fail("Output from doIt() incorrect");
+ }
+
+ if (!logger.getLog().contains(IntrospectorCache.CACHEDUMP_MSG))
+ {
+ fail("Didn't see introspector cache dump.");
+ }
+ }
+
+ /**
+ * Simple (real simple...) classloader that depends
+ * on a Foo.class being located in the classloader
+ * directory under test
+ */
+ public static class TestClassloader extends ClassLoader
+ {
+ private final static String testclass =
+ "classloader/Foo.class";
+
+ private Class<?> fooClass = null;
+
+ public TestClassloader()
+ throws Exception
+ {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ InputStream fis = getClass().getResourceAsStream("/" + testclass);
+ IOUtils.copy(fis, os);
+ fis.close();
+ os.close();
+
+ byte[] barr = os.toByteArray();
+
+ fooClass = defineClass("classloader.Foo", barr, 0, barr.length);
+ }
+
+
+ @Override
+ public Class<?> findClass(String name)
+ {
+ return fooClass;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ClasspathResourceTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ClasspathResourceTestCase.java
new file mode 100644
index 00000000..41285fab
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ClasspathResourceTestCase.java
@@ -0,0 +1,166 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Load templates from the Classpath.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a>
+ * @version $Id$
+ */
+public class ClasspathResourceTestCase extends BaseTestCase
+{
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/cpload";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/cpload/compare";
+
+ /**
+ * Default constructor.
+ */
+ public ClasspathResourceTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Velocity.reset();
+ Velocity.setProperty(Velocity.RESOURCE_LOADERS, "classpath");
+
+ /*
+ * I don't think I should have to do this, these should
+ * be in the default config file.
+ */
+
+ Velocity.addProperty(
+ "classpath." + Velocity.RESOURCE_LOADER + ".class",
+ "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+
+ Velocity.setProperty(
+ "classpath." + Velocity.RESOURCE_LOADER + ".cache", "false");
+
+ Velocity.setProperty(
+ "classpath." + Velocity.RESOURCE_LOADER + ".modificationCheckInterval",
+ "2");
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(ClasspathResourceTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testClasspathResource ()
+ throws Exception
+ {
+ /*
+ * lets ensure the results directory exists
+ */
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Template template1 = RuntimeSingleton.getTemplate("/includeevent/test1-cp." + TMPL_FILE_EXT);
+
+ // Uncomment when http://jira.codehaus.org/browse/MPTEST-57 has been resolved
+ // Template template2 = RuntimeSingleton.getTemplate(
+ // getFileName(null, "template/test2", TMPL_FILE_EXT));
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "test1", RESULT_FILE_EXT));
+
+ // Uncomment when http://jira.codehaus.org/browse/MPTEST-57 has been resolved
+ // FileOutputStream fos2 =
+ // new FileOutputStream (
+ // getFileName(RESULTS_DIR, "test2", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+ // Uncomment when http://jira.codehaus.org/browse/MPTEST-57 has been resolved
+ // Writer writer2 = new BufferedWriter(new OutputStreamWriter(fos2));
+
+ /*
+ * put the Vector into the context, and merge both
+ */
+
+ VelocityContext context = new VelocityContext();
+
+ template1.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ // Uncomment when http://jira.codehaus.org/browse/MPTEST-57 has been resolved
+ // template2.merge(context, writer2);
+ // writer2.flush();
+ // writer2.close();
+
+ if (!isMatch(RESULTS_DIR,COMPARE_DIR,"test1",RESULT_FILE_EXT,CMP_FILE_EXT)
+ // Uncomment when http://jira.codehaus.org/browse/MPTEST-57 has been resolved
+ // || !isMatch(RESULTS_DIR,COMPARE_DIR,"test2",RESULT_FILE_EXT,CMP_FILE_EXT)
+ )
+ {
+ fail("Output is incorrect!");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/CommentsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/CommentsTestCase.java
new file mode 100644
index 00000000..39724a64
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/CommentsTestCase.java
@@ -0,0 +1,108 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+
+import java.io.StringWriter;
+
+/**
+ * Test comments
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class CommentsTestCase extends BaseTestCase
+{
+
+ public static Test suite()
+ {
+ return new TestSuite(CommentsTestCase.class);
+ }
+
+ /**
+ * Default constructor.
+ * @param name
+ */
+ public CommentsTestCase(String name)
+ {
+ super(name);
+ }
+
+
+ /**
+ * Test multiline comments
+ * @throws Exception
+ */
+ public void testMultiLine()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.init();
+
+ Context context = new VelocityContext();
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context, writer, "test","abc #* test\r\ntest2*#\r\ndef");
+ assertEquals("abc \r\ndef", writer.toString());
+ }
+
+ /**
+ * Test single line comments
+ * @throws Exception
+ */
+ public void testSingleLine()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.init();
+
+ Context context = new VelocityContext();
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context, writer, "test","123 ## test test\r\nabc");
+ assertEquals("123 abc", writer.toString());
+
+ context = new VelocityContext();
+ writer = new StringWriter();
+ ve.evaluate(context, writer, "test","123 \r\n## test test\r\nabc");
+ assertEquals("123 \r\nabc", writer.toString());
+
+ }
+
+ /**
+ * Test combined comments
+ * @throws Exception
+ */
+ public void testCombined()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.init();
+
+ Context context = new VelocityContext();
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context, writer, "test","test\r\n## #* *# ${user \r\nabc");
+ assertEquals("test\r\nabc", writer.toString());
+
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/CommonsExtPropTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/CommonsExtPropTestCase.java
new file mode 100644
index 00000000..200150f6
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/CommonsExtPropTestCase.java
@@ -0,0 +1,178 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestSuite;
+import org.apache.velocity.util.ExtProperties;
+
+import java.io.FileWriter;
+import java.util.Iterator;
+import java.util.Vector;
+
+
+/**
+ * Tests for the ExtProperties class. This is an identical
+ * copy of the ConfigurationTestCase, which will disappear when
+ * the Configuration class does
+ *
+ * @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 CommonsExtPropTestCase extends BaseTestCase
+{
+ /**
+ * Comparison directory.
+ */
+ private static final String COMPARE_DIR =
+ TEST_COMPARE_DIR + "/configuration/compare";
+
+ /**
+ * Results directory.
+ */
+ private static final String RESULTS_DIR =
+ TEST_RESULT_DIR + "/configuration";
+
+ /**
+ * Test configuration
+ */
+ private static final String TEST_CONFIG =
+ TEST_COMPARE_DIR + "/configuration/test-config.properties";
+
+ /**
+ * Creates a new instance.
+ *
+ */
+ public CommonsExtPropTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new TestSuite(CommonsExtPropTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testExtendedProperties ()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ ExtProperties c = new ExtProperties(TEST_CONFIG);
+
+ FileWriter result = new FileWriter(
+ getFileName(RESULTS_DIR, "output", "res"));
+
+ message(result, "Testing order of keys ...");
+ showIterator(result, c.getKeys());
+
+ message(result, "Testing retrieval of CSV values ...");
+ showVector(result, c.getVector("resource.loaders"));
+
+ message(result, "Testing subset(prefix).getKeys() ...");
+ ExtProperties subset = c.subset("resource.loader.file");
+ showIterator(result, subset.getKeys());
+
+ message(result, "Testing getVector(prefix) ...");
+ showVector(result, subset.getVector("path"));
+
+ message(result, "Testing getString(key) ...");
+ result.write(c.getString("config.string.value"));
+ result.write("\n\n");
+
+ message(result, "Testing getBoolean(key) ...");
+ result.write(Boolean.valueOf(c.getBoolean("config.boolean.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing getByte(key) ...");
+ result.write(new Byte(c.getByte("config.byte.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing getShort(key) ...");
+ result.write(new Short(c.getShort("config.short.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing getInt(key) ...");
+ result.write(new Integer(c.getInt("config.int.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing getLong(key) ...");
+ result.write(new Long(c.getLong("config.long.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing getFloat(key) ...");
+ result.write(new Float(c.getFloat("config.float.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing getDouble(key) ...");
+ result.write(new Double(c.getDouble("config.double.value")).toString());
+ result.write("\n\n");
+
+ message(result, "Testing escaped-comma scalar...");
+ result.write( c.getString("escape.comma1"));
+ result.write("\n\n");
+
+ message(result, "Testing escaped-comma vector...");
+ showVector(result, c.getVector("escape.comma2"));
+ result.write("\n\n");
+
+ result.flush();
+ result.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "output","res","cmp"))
+ {
+ fail("Output incorrect.");
+ }
+ }
+
+ private void showIterator(FileWriter result, Iterator i)
+ throws Exception
+ {
+ while(i.hasNext())
+ {
+ result.write((String) i.next());
+ result.write("\n");
+ }
+ result.write("\n");
+ }
+
+ private void showVector(FileWriter result, Vector v)
+ throws Exception
+ {
+ for (Object aV : v)
+ {
+ result.write((String) aV);
+ result.write("\n");
+ }
+ result.write("\n");
+ }
+
+ private void message(FileWriter result, String message)
+ throws Exception
+ {
+ result.write("--------------------------------------------------\n");
+ result.write(message + "\n");
+ result.write("--------------------------------------------------\n");
+ result.write("\n");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ContextAutoreferenceKeyTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ContextAutoreferenceKeyTestCase.java
new file mode 100644
index 00000000..f427f4f7
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ContextAutoreferenceKeyTestCase.java
@@ -0,0 +1,58 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+
+/**
+ * This class tests passing expressions as method arguments
+ */
+
+public class ContextAutoreferenceKeyTestCase extends BaseTestCase
+{
+ public ContextAutoreferenceKeyTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty(VelocityEngine.CONTEXT_AUTOREFERENCE_KEY, "self");
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("foo", "bar");
+ }
+
+ public void testAutoreference()
+ {
+ assertEvalEquals("bar", "$foo");
+ assertEvalEquals("bar", "$self.foo");
+ assertEvalEquals("bar", "$self.self.foo");
+ assertEvalEquals("true", "$self.containsKey('foo')");
+ assertEvalEquals("false", "$self.containsKey('bar')");
+ assertEvalEquals("bar", "$self.put('foo', 'baz')");
+ assertEvalEquals("baz", "$foo");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ContextSafetyTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ContextSafetyTestCase.java
new file mode 100644
index 00000000..a30c2940
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ContextSafetyTestCase.java
@@ -0,0 +1,145 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Vector;
+
+/**
+ * Tests if we are context safe : can we switch objects in the context
+ * and re-merge the template safely.
+ *
+ * NOTE:
+ * This class should not extend RuntimeTestCase because this test
+ * is run from the VelocityTestSuite which in effect a runtime
+ * test suite and the test suite initializes the Runtime. Extending
+ * RuntimeTestCase causes the Runtime to be initialized twice.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ContextSafetyTestCase extends BaseTestCase implements TemplateTestBase
+{
+ public ContextSafetyTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ Velocity.reset();
+ Velocity.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(ContextSafetyTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testContextSafety ()
+ throws Exception
+ {
+ /*
+ * make a Vector and String array because
+ * they are treated differently in Foreach()
+ */
+ Vector<String> v = new Vector();
+
+ v.addElement("vector hello 1");
+ v.addElement( "vector hello 2");
+ v.addElement( "vector hello 3");
+
+ String strArray[] = new String[3];
+
+ strArray[0] = "array hello 1";
+ strArray[1] = "array hello 2";
+ strArray[2] = "array hello 3";
+
+ VelocityContext context = new VelocityContext();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /*
+ * get the template and the output
+ */
+
+ Template template = RuntimeSingleton.getTemplate(
+ getFileName(null, "context_safety", TMPL_FILE_EXT));
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "context_safety1", RESULT_FILE_EXT));
+
+ FileOutputStream fos2 =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "context_safety2", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+ Writer writer2 = new BufferedWriter(new OutputStreamWriter(fos2));
+
+ /*
+ * put the Vector into the context, and merge
+ */
+
+ context.put("vector", v);
+ template.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ /*
+ * now put the string array into the context, and merge
+ */
+
+ context.put("vector", strArray);
+ template.merge(context, writer2);
+ writer2.flush();
+ writer2.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,"context_safety1",
+ RESULT_FILE_EXT,CMP_FILE_EXT) ||
+ !isMatch(RESULT_DIR,COMPARE_DIR,"context_safety2",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/DefineTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/DefineTestCase.java
new file mode 100755
index 00000000..1c66ced8
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/DefineTestCase.java
@@ -0,0 +1,120 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 tests the #define directive
+ */
+public class DefineTestCase extends BaseTestCase
+{
+ public DefineTestCase(String name)
+ {
+ super(name);
+ }
+
+ protected String defAndEval(String block)
+ {
+ return defAndEval("def", block);
+ }
+
+ protected String defAndEval(String key, String block)
+ {
+ return evaluate("#define( $"+key+" )"+block+"#end$"+key);
+ }
+
+ public void testSimple()
+ {
+ assertEquals("abc", defAndEval("abc"));
+ assertEvalEquals("abc abc abc", "#define( $a )abc#end$a $a $a");
+ }
+
+ public void testNotSimple()
+ {
+ assertEquals("true", defAndEval("#if( $def )true#end"));
+ assertEquals("123", defAndEval("#foreach( $i in [1..3] )$i#end"));
+ assertEquals("hello world", defAndEval("#macro( test )hello world#end#test()"));
+ }
+
+ public void testOverridingDefinitionInternally()
+ {
+ assertEvalEquals("truefalse", "#define( $or )true#set( $or = false )#end$or$or");
+ }
+
+ public void testLateBinding()
+ {
+ context.put("baz", "foo");
+ assertEvalEquals("foobar", "#define( $lb )$baz#end${lb}#set( $baz = 'bar' )${lb}");
+ }
+
+ public void testRerendering()
+ {
+ context.put("inc", new Inc());
+ assertEvalEquals("1 2 3", "#define( $i )$inc#end$i $i $i");
+ }
+
+ public void testAssignation()
+ {
+ assertEvalEquals("[][hello]","#define( $orig )hello#end[#set( $assig = $orig )][$assig]");
+ }
+
+ public void testNonRenderingUsage()
+ {
+ String template = "#define($foo)\n" +
+ " foo_contents\n" +
+ "#end\n" +
+ "#if ($foo)\n" +
+ " found foo\n" +
+ "#end";
+ assertEvalEquals(" found foo\n", template);
+ }
+
+ public void testRecursionLimit()
+ {
+ try
+ {
+ assertEvalEquals("$r", "#define( $r )$r#end$r");
+ }
+ catch (Exception t)
+ {
+ fail("Recursion should not have thrown an exception");
+ }
+ catch (Error e)
+ {
+ fail("Infinite recursion should not be possible.");
+ }
+ }
+
+ public void testThingsOfQuestionableMorality()
+ {
+ // redefining $foo within $foo
+ assertEquals("foobar", defAndEval("foo", "foo#define( $foo )bar#end$foo"));
+ }
+
+
+ public static class Inc
+ {
+ int foo = 1;
+ public String toString()
+ {
+ return String.valueOf(foo++);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/EncodingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/EncodingTestCase.java
new file mode 100644
index 00000000..bc056222
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/EncodingTestCase.java
@@ -0,0 +1,208 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Tests input encoding handling. The input target is UTF-8, having
+ * chinese and and a spanish enyay (n-twiddle)
+ *
+ * Thanks to Kent Johnson for the example input file.
+ *
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class EncodingTestCase extends BaseTestCase implements TemplateTestBase
+{
+ public EncodingTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ Velocity.reset();
+
+ Velocity.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ Velocity.setProperty( Velocity.INPUT_ENCODING, "UTF-8" );
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(EncodingTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testChineseEncoding ()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /*
+ * get the template and the output
+ */
+
+ /*
+ * Chinese and spanish
+ */
+
+ Template template = Velocity.getTemplate(
+ getFileName(null, "encodingtest", TMPL_FILE_EXT), "UTF-8");
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "encodingtest", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8));
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,"encodingtest",
+ RESULT_FILE_EXT,CMP_FILE_EXT) )
+ {
+ fail("Output 1 incorrect.");
+ }
+ }
+
+ public void testHighByteChinese()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /*
+ * a 'high-byte' chinese example from Michael Zhou
+ */
+
+ Template template = Velocity.getTemplate(
+ getFileName( null, "encodingtest2", TMPL_FILE_EXT), "UTF-8");
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "encodingtest2", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos, StandardCharsets.UTF_8));
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,"encodingtest2",
+ RESULT_FILE_EXT,CMP_FILE_EXT) )
+ {
+ fail("Output 2 incorrect.");
+ }
+
+ }
+
+ public void testHighByteChinese2()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /*
+ * a 'high-byte' chinese from Ilkka
+ */
+
+ Template template = Velocity.getTemplate(
+ getFileName( null, "encodingtest3", TMPL_FILE_EXT), "GBK");
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "encodingtest3", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos, "GBK"));
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,"encodingtest3",
+ RESULT_FILE_EXT,CMP_FILE_EXT) )
+ {
+ fail("Output 3 incorrect.");
+ }
+ }
+
+ public void testRussian()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /*
+ * Russian example from Vitaly Repetenko
+ */
+
+ Template template = Velocity.getTemplate(
+ getFileName( null, "encodingtest_KOI8-R", TMPL_FILE_EXT), "KOI8-R");
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "encodingtest_KOI8-R", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos, "KOI8-R"));
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,"encodingtest_KOI8-R",
+ RESULT_FILE_EXT,CMP_FILE_EXT) )
+ {
+ fail("Output 4 incorrect.");
+ }
+ }
+}
+
+
+
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/EvaluateTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/EvaluateTestCase.java
new file mode 100644
index 00000000..fc5c1d1b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/EvaluateTestCase.java
@@ -0,0 +1,299 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.app.event.implement.EscapeHtmlReference;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test #evaluate directive.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class EvaluateTestCase extends BaseTestCase
+{
+
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/evaluate";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/evaluate";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/evaluate/compare";
+
+ /**
+ * Default constructor.
+ * @param name
+ */
+ public EvaluateTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ assureResultsDirectoryExists(RESULTS_DIR);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ // extension hook
+ }
+
+
+ /**
+ * Test basic functionality.
+ * @throws Exception
+ */
+ public void testEvaluate()
+ throws Exception
+ {
+ Map props = new HashMap();
+ testFile("eval1", props);
+ }
+
+ /**
+ * Test evaluate directive preserves macros (VELOCITY-591)
+ * @throws Exception
+ */
+ public void testEvaluateMacroPreserve()
+ throws Exception
+ {
+ Map properties = new HashMap();
+ testFile("eval2", properties);
+
+ properties.put(RuntimeConstants.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL,"false");
+ testFile("eval2", properties);
+ }
+
+ /**
+ * Test in a macro context.
+ * @throws Exception
+ */
+ public void testEvaluateVMContext()
+ throws Exception
+ {
+ testFile("evalvmcontext", new HashMap());
+ }
+
+ /**
+ * Test #stop and #break
+ * @throws Exception
+ */
+ public void testStopAndBreak()
+ {
+ engine.setProperty("evaluate.provide.scope.control", "true");
+ assertEvalEquals("t ", "t #stop t2 #evaluate('t3')");
+ assertEvalEquals("t ", "t #break t2 #evaluate('t3')");
+ assertEvalEquals("t t2 t3 ", "t t2 #evaluate('t3 #stop t4') t5");
+ assertEvalEquals("t t2 t3 t5", "t t2 #evaluate('t3 #break t4') t5");
+ assertEvalEquals("t t2 t3 ", "t t2 #evaluate('t3 #break($evaluate.topmost) t4') t5");
+ }
+
+ /**
+ * Test that the event handlers work in #evaluate (since they are
+ * attached to the context). Only need to check one - they all
+ * work the same.
+ * @throws Exception
+ */
+ public void testEventHandler()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, EscapeHtmlReference.class.getName());
+ ve.init();
+
+ Context context = new VelocityContext();
+ context.put("lt","<");
+ context.put("gt",">");
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context, writer, "test","${lt}test${gt} #evaluate('${lt}test2${gt}')");
+ assertEquals("&lt;test&gt; &lt;test2&gt;", writer.toString());
+
+ }
+
+
+ /**
+ * Test errors are thrown
+ * @throws Exception
+ */
+ public void testErrors()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.init();
+
+ Context context = new VelocityContext();
+
+ // no arguments
+ StringWriter writer = new StringWriter();
+ try
+ {
+ ve.evaluate(context, writer, "test",
+ "#evaluate()");
+ fail("Expected exception");
+ }
+ catch (ParseErrorException e)
+ {
+ assertEquals("test",e.getTemplateName());
+ assertEquals(1,e.getLineNumber());
+ assertEquals(1,e.getColumnNumber());
+ }
+
+ // too many arguments
+ writer = new StringWriter();
+ try
+ {
+ ve.evaluate(context, writer, "test",
+ "#evaluate('aaa' 'bbb')");
+ fail("Expected exception");
+ }
+ catch (ParseErrorException e)
+ {
+ assertEquals("test",e.getTemplateName());
+ assertEquals(1,e.getLineNumber());
+ assertEquals(17,e.getColumnNumber());
+ }
+
+ // argument not a string or reference
+ writer = new StringWriter();
+ try
+ {
+ ve.evaluate(context, writer, "test",
+ "#evaluate(10)");
+ fail("Expected exception");
+ }
+ catch (ParseErrorException e)
+ {
+ assertEquals("test",e.getTemplateName());
+ assertEquals(1,e.getLineNumber());
+ assertEquals(11,e.getColumnNumber());
+ }
+
+ // checking line/col for parse error
+ writer = new StringWriter();
+ try
+ {
+ String eval = "this is a multiline\n\n\n\n\n test #foreach() with an error";
+ context.put("eval",eval);
+ ve.evaluate(context, writer, "test",
+ "first line\n second line: #evaluate($eval)");
+ fail("Expected exception");
+ }
+ catch (ParseErrorException e)
+ {
+ // should be start of #evaluate
+ assertEquals("test",e.getTemplateName());
+ assertEquals(2,e.getLineNumber());
+ assertEquals(15,e.getColumnNumber());
+ }
+ }
+
+ /**
+ * Test a file parses with no errors and compare to existing file.
+ * @param basefilename
+ * @throws Exception
+ */
+ private void testFile(String basefilename, Map<String, Object> properties)
+ throws Exception
+ {
+ info("Test file: "+basefilename);
+ VelocityEngine ve = engine;
+ ve.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ for (String key : properties.keySet())
+ {
+ String value = (String) properties.get(key);
+ ve.addProperty(key, value);
+ info("Add property: " + key + " = " + value);
+ }
+
+ ve.init();
+
+ Template template;
+ FileOutputStream fos;
+ Writer fwriter;
+
+ template = ve.getTemplate( getFileName(null, basefilename, TMPL_FILE_EXT) );
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, basefilename, RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, basefilename, RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ String result = getFileContents(RESULTS_DIR, basefilename, RESULT_FILE_EXT);
+ String compare = getFileContents(COMPARE_DIR, basefilename, CMP_FILE_EXT);
+
+ String msg = "Output was incorrect\n"+
+ "-----Result-----\n"+ result +
+ "----Expected----\n"+ compare +
+ "----------------";
+
+ fail(msg);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/EventHandlingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/EventHandlingTestCase.java
new file mode 100644
index 00000000..ef3bfcdf
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/EventHandlingTestCase.java
@@ -0,0 +1,271 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.event.EventCartridge;
+import org.apache.velocity.app.event.MethodExceptionEventHandler;
+import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.ContextAware;
+import org.apache.velocity.util.RuntimeServicesAware;
+import org.apache.velocity.util.introspection.Info;
+
+/**
+ * Tests event handling for all event handlers except IncludeEventHandler. This is tested
+ * separately due to its complexity.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class EventHandlingTestCase extends BaseTestCase
+{
+ private static String NO_REFERENCE_VALUE = "<no reference value>";
+ private static String REFERENCE_VALUE = "<reference value>";
+
+ public EventHandlingTestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testManualEventHandlers()
+ throws Exception
+ {
+ TestEventCartridge te = new TestEventCartridge();
+ /*
+ * Test attaching the event cartridge to the context.
+ * Make an event cartridge, register all the
+ * event handlers (at once) and attach it to the
+ * Context
+ */
+
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(te);
+ ec.attachToContext(context);
+
+ /*
+ * now wrap the event cartridge - we want to make sure that
+ * we can do this w/o harm
+ */
+ doTestReferenceInsertionEventHandler1();
+ doTestReferenceInsertionEventHandler2();
+ doTestMethodExceptionEventHandler1();
+ doTestMethodExceptionEventHandler2();
+ }
+
+ /**
+ * Test assigning the event handlers via properties
+ */
+ public void testConfigurationEventHandlers()
+ throws Exception
+ {
+ engine.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, TestEventCartridge.class.getName());
+ engine.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, TestEventCartridge.class.getName());
+
+ doTestReferenceInsertionEventHandler1();
+ doTestReferenceInsertionEventHandler2();
+ doTestMethodExceptionEventHandler1();
+ doTestMethodExceptionEventHandler2();
+ }
+
+ /**
+ * Test all the event handlers using the given engine.
+ */
+ private void doTestReferenceInsertionEventHandler1()
+ throws Exception
+ {
+ VelocityContext outer = context;
+ context = new VelocityContext(context);
+ context.put("name", "Velocity");
+
+ /*
+ * First, the reference insertion handler
+ */
+ String expected = REFERENCE_VALUE + REFERENCE_VALUE + REFERENCE_VALUE;
+ assertEvalEquals(expected, "$name$name$name");
+
+ context = outer;
+ }
+
+ private void doTestReferenceInsertionEventHandler2()
+ throws Exception
+ {
+ VelocityContext outer = context;
+ context = new VelocityContext(context);
+ context.put("name", "Velocity");
+
+ /*
+ * using the same handler, we can deal with
+ * null references as well
+ */
+ assertEvalEquals(NO_REFERENCE_VALUE, "$floobie");
+
+ context = outer;
+ }
+
+ private void doTestMethodExceptionEventHandler1()
+ throws Exception
+ {
+ VelocityContext outer = context;
+ context = new VelocityContext(context);
+
+ /*
+ * finally, we test a method exception event - we do this
+ * by putting this class in the context, and calling
+ * a method that does nothing but throw an exception.
+ * we use flag in the context to turn the event handling
+ * on and off
+ *
+ * Note also how the reference insertion process
+ * happens as well
+ */
+ context.put("allow_exception",Boolean.TRUE);
+ context.put("this", this );
+
+ evaluate(" $this.throwException()");
+
+ context = outer;
+ }
+
+ private void doTestMethodExceptionEventHandler2()
+ throws Exception
+ {
+ VelocityContext outer = context;
+ context = new VelocityContext(context);
+ context.put("this", this );
+
+ /*
+ * now, we remove the exception flag, and we can see that the
+ * exception will propgate all the way up here, and
+ * wil be caught by the catch() block below
+ */
+ assertEvalException("$this.throwException()", MethodInvocationException.class);
+
+ context = outer;
+ }
+
+ /**
+ * silly method to throw an exception to test
+ * the method invocation exception event handling
+ */
+ public void throwException()
+ throws Exception
+ {
+ throw new Exception("Hello from throwException()");
+ }
+
+
+
+ public static class TestEventCartridge
+ implements ReferenceInsertionEventHandler,
+ MethodExceptionEventHandler,
+ RuntimeServicesAware,ContextAware
+ {
+ private RuntimeServices rs;
+
+ /**
+ * Required by EventHandler
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs )
+ {
+ // make sure this is only called once
+ if (this.rs == null)
+ this.rs = rs;
+
+ else
+ fail("initialize called more than once.");
+ }
+
+ /**
+ * Event handler for when a reference is inserted into the output stream.
+ */
+ @Override
+ public Object referenceInsert(Context context, String reference, Object value )
+ {
+ // as a test, make sure this EventHandler is initialized
+ if (rs == null)
+ fail ("Event handler not initialized!");
+
+
+ /*
+ * if we have a value
+ * return a known value
+ */
+ String s = null;
+
+ if( value != null )
+ {
+ s = REFERENCE_VALUE;
+ }
+ else
+ {
+ /*
+ * we only want to deal with $floobie - anything
+ * else we let go
+ */
+ if ( reference.equals("$floobie") )
+ {
+ s = NO_REFERENCE_VALUE;
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Handles exceptions thrown during in-template method access
+ */
+ @Override
+ public Object methodException(Context context, Class claz, String method, Exception e, Info info )
+ {
+ // as a test, make sure this EventHandler is initialized
+ if (rs == null)
+ fail ("Event handler not initialized!");
+
+ // only do processing if the switch is on
+ if (context != null)
+ {
+ boolean exceptionSwitch = context.containsKey("allow_exception");
+
+ if( exceptionSwitch && method.equals("throwException"))
+ {
+ return "handler";
+ }
+ else
+ throw new RuntimeException(e);
+
+ } else
+
+ throw new RuntimeException(e);
+ }
+
+ Context context;
+
+
+ @Override
+ public void setContext(Context context)
+ {
+ this.context = context;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ExceptionTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ExceptionTestCase.java
new file mode 100644
index 00000000..b555e1bc
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ExceptionTestCase.java
@@ -0,0 +1,171 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.ExceptionGeneratingDirective;
+import org.apache.velocity.test.misc.ExceptionGeneratingEventHandler;
+import org.apache.velocity.test.misc.ExceptionGeneratingResourceLoader;
+import org.apache.velocity.test.provider.TestProvider;
+
+import java.io.StringWriter;
+
+/**
+ * Test case for miscellaneous Exception related issues.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ExceptionTestCase extends BaseTestCase implements TemplateTestBase
+{
+ VelocityEngine ve;
+
+ /**
+ * Default constructor.
+ */
+ public ExceptionTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(ExceptionTestCase.class);
+ }
+
+
+ public void testReferenceInsertionEventHandlerException()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION,ExceptionGeneratingEventHandler.class.getName());
+ ve.init();
+ assertException(ve);
+ }
+
+ /**
+ * Note - this is the one case where RuntimeExceptions *are not* passed through
+ * verbatim.
+ * @throws Exception
+ */
+ public void testMethodExceptionEventHandlerException()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION,ExceptionGeneratingEventHandler.class.getName());
+ ve.init();
+ Context context = new VelocityContext();
+ context.put ("test",new TestProvider());
+ assertMethodInvocationException(ve,context,"$test.getThrow()");
+ assertMethodInvocationException(ve,context,"$test.throw");
+ }
+
+ public void testIncludeEventHandlerException()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE,ExceptionGeneratingEventHandler.class.getName());
+ ve.init();
+ assertException(ve,"#include('dummy')");
+ }
+
+ public void testResourceLoaderException()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADERS,"except");
+ ve.setProperty("except.resource.loader.class",ExceptionGeneratingResourceLoader.class.getName());
+ try
+ {
+ ve.init(); // tries to get the macro file
+ ve.getTemplate("test.txt");
+ fail("Should have thrown RuntimeException");
+ }
+ catch (RuntimeException E)
+ {
+ // do nothing
+ }
+ }
+
+
+ public void testDirectiveException()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty("userdirective",ExceptionGeneratingDirective.class.getName());
+ ve.init();
+ assertException(ve,"#Exception() test #end");
+ }
+
+
+
+ public void assertException(VelocityEngine ve)
+ throws Exception
+ {
+ Context context = new VelocityContext();
+ context.put ("test","test");
+ assertException(ve,context,"this is a $test");
+ }
+
+ public void assertException(VelocityEngine ve, String input)
+ throws Exception
+ {
+ Context context = new VelocityContext();
+ context.put ("test","test");
+ assertException(ve,context,input);
+ }
+
+ public void assertException(VelocityEngine ve, Context context, String input)
+ throws Exception
+ {
+ try
+ {
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context,writer,"test",input);
+ fail("Expected RuntimeException");
+ }
+ catch (RuntimeException E)
+ {
+ // do nothing
+ }
+ }
+ public void assertMethodInvocationException(VelocityEngine ve, Context context, String input)
+ throws Exception
+ {
+ try
+ {
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context,writer,"test",input);
+ fail("Expected MethodInvocationException");
+ }
+ catch (MethodInvocationException E)
+ {
+ // do nothing
+ }
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ExpressionAsMethodArgumentTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ExpressionAsMethodArgumentTestCase.java
new file mode 100644
index 00000000..2816799a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ExpressionAsMethodArgumentTestCase.java
@@ -0,0 +1,56 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+
+/**
+ * This class tests passing expressions as method arguments
+ */
+
+public class ExpressionAsMethodArgumentTestCase extends BaseTestCase
+{
+ public ExpressionAsMethodArgumentTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("tool",new MyAbsTool());
+ context.put("foo",2);
+ context.put("bar",-3);
+ }
+
+ public void testExpressionAsMethod()
+ {
+ assertEvalEquals("6","$tool.abs( $foo * $bar )");
+ assertEvalEquals("12","$tool.abs( $foo * $tool.abs( $foo * $bar ) )");
+ }
+
+ public static class MyAbsTool
+ {
+ public int abs(int num)
+ {
+ return Math.abs(num);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/FilteredEventHandlingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/FilteredEventHandlingTestCase.java
new file mode 100644
index 00000000..e56fa733
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/FilteredEventHandlingTestCase.java
@@ -0,0 +1,238 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+
+/**
+ * Tests event handling for all event handlers when multiple event handlers are
+ * assigned for each type.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class FilteredEventHandlingTestCase extends BaseTestCase
+{
+
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/includeevent";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/includeevent";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/includeevent/compare";
+
+
+ private TestLogger logger = new TestLogger(false, false);
+
+ /**
+ * Default constructor.
+ */
+ public FilteredEventHandlingTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(FilteredEventHandlingTestCase.class);
+ }
+
+ public void testFilteredEventHandling() throws Exception
+ {
+ String handler1 = "org.apache.velocity.test.eventhandler.Handler1";
+ String handler2 = "org.apache.velocity.test.eventhandler.Handler2";
+ String sequence1 = handler1 + "," + handler2;
+ String sequence2 = handler2 + "," + handler1;
+
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ /*
+ * Set up two VelocityEngines that will apply the handlers in both orders
+ */
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, sequence1);
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, sequence1);
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, sequence1);
+ ve.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ ve.init();
+
+ VelocityEngine ve2 = new VelocityEngine();
+ ve2.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ ve2.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, sequence2);
+ ve2.setProperty(RuntimeConstants.EVENTHANDLER_REFERENCEINSERTION, sequence2);
+ ve2.setProperty(RuntimeConstants.EVENTHANDLER_INCLUDE, sequence2);
+ ve2.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ ve2.init();
+
+ VelocityContext context;
+ StringWriter w;
+
+
+ // check reference insertion with both sequences
+ context = new VelocityContext();
+ w = new StringWriter();
+ context.put("test","abc");
+ ve.evaluate( context, w, "test", "$test" );
+ if ( !w.toString().equals( "ABCABC" ))
+ {
+ fail( "Reference insertion test 1");
+ }
+
+ context = new VelocityContext();
+ w = new StringWriter();
+ context.put("test","abc");
+ ve2.evaluate( context, w, "test", "$test" );
+ if ( !w.toString().equals( "ABCabc" ))
+ {
+ fail( "Reference insertion test 2");
+ }
+
+ // check method exception with both sequences
+
+ // sequence 1
+ context = new VelocityContext();
+ w = new StringWriter();
+ context.put("test",new ArrayList());
+
+ try
+ {
+ ve.evaluate( context, w, "test", "$test.get(0)");
+ fail ( "Method exception event test 1" );
+ }
+ catch( MethodInvocationException mee )
+ {
+ // do nothing
+ }
+
+ // sequence2
+ context = new VelocityContext();
+ w = new StringWriter();
+ context.put("test",new ArrayList());
+
+ ve2.evaluate( context, w, "test", "$test.get(0)");
+
+ // check log on null set with both sequences
+ // sequence 1
+ context = new VelocityContext();
+ w = new StringWriter();
+ logger.startCapture();
+ ve.evaluate( context, w, "test", "#set($test1 = $test2)" );
+ String log = logger.getLog();
+ if ( log != null && log.length() > 0)
+ {
+ fail( "log null set test 1");
+ }
+
+ // sequence 2
+ context = new VelocityContext();
+ w = new StringWriter();
+ logger.startCapture();
+ ve2.evaluate( context, w, "test", "#set($test1 = $test2)" );
+ log = logger.getLog();
+ if ( log != null && log.length() > 0)
+ {
+ fail( "log null set test 2");
+ }
+
+ logger.stopCapture();
+
+ // check include event handler with both sequences
+
+ // sequence 1
+ Template template;
+ FileOutputStream fos;
+ Writer fwriter;
+
+ template = ve.getTemplate( getFileName(null, "test4", TMPL_FILE_EXT) );
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, "test4", RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ context = new VelocityContext();
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "test4", RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+
+ // sequence 2
+ template = ve2.getTemplate( getFileName(null, "test5", TMPL_FILE_EXT) );
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, "test5", RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ context = new VelocityContext();
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "test5", RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ForeachTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ForeachTestCase.java
new file mode 100644
index 00000000..2e495843
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ForeachTestCase.java
@@ -0,0 +1,147 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.test.provider.ForeachMethodCallHelper;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class tests the Foreach loop.
+ *
+ * @author Daniel Rall
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ */
+public class ForeachTestCase extends BaseTestCase
+{
+ public ForeachTestCase(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Tests limiting of the number of loop iterations.
+ */
+ public void testMaxNbrLoopsConstraint()
+ throws Exception
+ {
+ // Limit the loop to three iterations.
+ engine.setProperty(RuntimeConstants.MAX_NUMBER_LOOPS,
+ 3);
+
+ assertEvalEquals("1 2 3 ", "#foreach ($item in [1..10])$item #end");
+ }
+
+ /**
+ * Tests proper method execution during a Foreach loop over a Collection
+ * with items of varying classes.
+ */
+ public void testCollectionAndMethodCall()
+ throws Exception
+ {
+ List col = new ArrayList();
+ col.add(100);
+ col.add("STRVALUE");
+ context.put("helper", new ForeachMethodCallHelper());
+ context.put("col", col);
+
+ assertEvalEquals("int 100 str STRVALUE ", "#foreach ( $item in $col )$helper.getFoo($item) #end");
+ }
+
+ /**
+ * Tests that #foreach will be able to retrieve an iterator from
+ * an arbitrary object that happens to have an iterator() method.
+ * (With the side effect of supporting the new Java 5 Iterable interface)
+ */
+ public void testObjectWithIteratorMethod()
+ throws Exception
+ {
+ context.put("iterable", new MyIterable());
+
+ assertEvalEquals("1 2 3 ", "#foreach ($i in $iterable)$i #end");
+ }
+
+ public void testNotReallyIterableIteratorMethod()
+ throws Exception
+ {
+ context.put("nri", new NotReallyIterable());
+
+ assertEvalEquals("", "#foreach ($i in $nri)$i #end");
+ }
+
+ public void testVelocityHasNextProperty()
+ throws Exception
+ {
+ List list = new ArrayList();
+ list.add("test1");
+ list.add("test2");
+ list.add("test3");
+ context.put("list", list);
+ assertEvalEquals("test1 SEPARATOR test2 SEPARATOR test3 ", "#foreach ($value in $list)$value #if( $foreach.hasNext )SEPARATOR #end#end");
+ }
+
+ public void testNestedVelocityHasNextProperty()
+ throws Exception
+ {
+ List list = new ArrayList();
+ list.add("test1");
+ list.add("test2");
+ list.add("test3");
+ list.add("test4");
+ context.put("list", list);
+ List list2 = new ArrayList();
+ list2.add("a1");
+ list2.add("a2");
+ list2.add("a3");
+ context.put("list2", list2);
+
+ assertEvalEquals("test1 (a1;a2;a3)-test2 (a1;a2;a3)-test3 (a1;a2;a3)-test4 (a1;a2;a3)", "#foreach ($value in $list)$value (#foreach ($val in $list2)$val#if( $foreach.hasNext );#end#end)#if( $foreach.hasNext )-#end#end");
+ }
+
+ public static class MyIterable
+ {
+ private List foo;
+
+ public MyIterable()
+ {
+ foo = new ArrayList();
+ foo.add(1);
+ foo.add(2L);
+ foo.add("3");
+ }
+
+ public Iterator iterator()
+ {
+ return foo.iterator();
+ }
+ }
+
+ public static class NotReallyIterable
+ {
+ public Object iterator()
+ {
+ return new Object();
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java
new file mode 100644
index 00000000..776f0b43
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/GetAsTestCase.java
@@ -0,0 +1,145 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.util.TemplateBoolean;
+import org.apache.velocity.util.TemplateNumber;
+import org.apache.velocity.util.TemplateString;
+
+/**
+ * Test objects with getAs<Type>() methods.
+ */
+public class GetAsTestCase extends BaseTestCase
+{
+ public GetAsTestCase(final String name)
+ {
+ super(name);
+ }
+
+ public void testCustomString()
+ {
+ // render
+ context.put("foo", new CustomString("getAsString"));
+ assertEvalEquals("getAsString", "$foo");
+ // aborted value
+ context.put("bar", new CustomString(null));
+ assertEvalEquals("", "$!bar");
+ // concatenation
+ context.put("half", new CustomString("half"));
+ assertEvalEquals("1half", "#set( $out = 1 + $half )$out");
+ }
+
+ public void testCustomBoolean()
+ {
+ context.put("foo", new CustomBoolean(false));
+ assertEvalEquals("right", "#if( !$foo )right#end");
+ }
+
+ public void testCustomNumber()
+ {
+ context.put("foo", new CustomNumber(7));
+ assertEvalEquals("14", "#set( $bar = $foo * 2 )$bar");
+ }
+
+
+ public void testTemplateString()
+ {
+ context.put("foo", new CustomTemplateString("getAsString"));
+ assertEvalEquals("getAsString", "$foo");
+ }
+
+ public void testTemplateBoolean()
+ {
+ context.put("foo", new CustomTemplateBoolean(false));
+ assertEvalEquals("right", "#if( !$foo )right#end");
+ }
+
+ public void testTemplateNumber()
+ {
+ context.put("foo", new CustomTemplateNumber(5));
+ assertEvalEquals("25", "#set( $foo = $foo * $foo )$foo");
+ }
+
+
+
+ public static class CustomString
+ {
+ String string;
+ public CustomString(String string)
+ {
+ this.string = string;
+ }
+ public String getAsString()
+ {
+ return string;
+ }
+ }
+
+ public static class CustomBoolean
+ {
+ boolean bool;
+ public CustomBoolean(boolean bool)
+ {
+ this.bool = bool;
+ }
+ public boolean getAsBoolean()
+ {
+ return bool;
+ }
+ }
+
+ public static class CustomNumber
+ {
+ Number num;
+ public CustomNumber(Number num)
+ {
+ this.num = num;
+ }
+ public Number getAsNumber()
+ {
+ return num;
+ }
+ }
+
+ public static class CustomTemplateString extends CustomString implements TemplateString
+ {
+ public CustomTemplateString(String string)
+ {
+ super(string);
+ }
+ }
+
+ public static class CustomTemplateBoolean extends CustomBoolean implements TemplateBoolean
+ {
+ public CustomTemplateBoolean(Boolean bool)
+ {
+ super(bool);
+ }
+ }
+
+ public static class CustomTemplateNumber extends CustomNumber implements TemplateNumber
+ {
+ public CustomTemplateNumber(Number num)
+ {
+ super(num);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/HyphenInIdentifiersTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/HyphenInIdentifiersTestCase.java
new file mode 100644
index 00000000..e4ccc7f9
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/HyphenInIdentifiersTestCase.java
@@ -0,0 +1,50 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityEngine;
+
+/**
+ * This class tests passing expressions as method arguments
+ */
+
+public class HyphenInIdentifiersTestCase extends BaseTestCase
+{
+ public HyphenInIdentifiersTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.addProperty("parser.allow_hyphen_in_identifiers", true);
+ }
+
+ public void testHyphen()
+ {
+ assertEvalEquals("6","#set($var-1 = 7)#set($var-2 = $var-1 - 1)$var-2");
+ }
+
+ public void testHyphenInCollection()
+ {
+ assertEvalEquals("foofoofoo","#set($map = {'name-with-hyphen':'foo'})$map.name-with-hyphen${map.name-with-hyphen}${map.get('name-with-hyphen')}");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyNoEmptyCheckTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyNoEmptyCheckTestCase.java
new file mode 100644
index 00000000..2935100c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyNoEmptyCheckTestCase.java
@@ -0,0 +1,120 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import java.util.Collections;
+
+/**
+ * Used to check that empty values are properly handled in #if statements
+ */
+public class IfEmptyNoEmptyCheckTestCase extends BaseTestCase
+{
+ public IfEmptyNoEmptyCheckTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty(RuntimeConstants.CHECK_EMPTY_OBJECTS, "false");
+ }
+
+ protected void assertEmpty(Object obj)
+ {
+ context.put("obj", obj);
+ assertEvalEquals("", "#if( $obj )fail#end");
+ }
+
+ protected void assertNotEmpty(Object obj)
+ {
+ context.put("obj", obj);
+ assertEvalEquals("", "#if( !$obj )fail#end");
+ }
+
+ public void testNull()
+ {
+ assertEmpty(null);
+ assertNotEmpty(new NullAsString());
+ assertNotEmpty(new NullAsNumber());
+ }
+
+ public void testDataStructures()
+ {
+ assertNotEmpty(Collections.emptyMap());
+ assertNotEmpty(Collections.emptyList());
+ assertNotEmpty(new Object[]{});
+ }
+
+ public void testString()
+ {
+ assertNotEmpty("");
+ assertNotEmpty(new EmptyAsString());
+ }
+
+ public void testNumber()
+ {
+ assertNotEmpty(0);
+ }
+
+ public void testStringBuilder()
+ {
+ StringBuilder builder = new StringBuilder();
+ assertNotEmpty(builder);
+ }
+
+ public void testLiterals()
+ {
+ assertEvalEquals("", "#if( !0 )fail#end");
+ assertEvalEquals("", "#if( !0.0 )fail#end");
+ assertEvalEquals("", "#if( !'' )fail#end");
+ assertEvalEquals("", "#if( !\"\" )fail#end");
+ assertEvalEquals("", "#if( ![] )fail#end");
+ assertEvalEquals("", "#if( !{} )fail#end");
+ }
+
+ public static class NullAsString
+ {
+ public String getAsString()
+ {
+ return null;
+ }
+ }
+
+ public static class EmptyAsString
+ {
+ public String getAsString()
+ {
+ return "";
+ }
+ }
+
+ public static class NullAsNumber
+ {
+ public String getAsNumber()
+ {
+ return null;
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java
new file mode 100644
index 00000000..52631a19
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IfEmptyTestCase.java
@@ -0,0 +1,144 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.Collections;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Used to check that empty values are properly handled in #if statements
+ */
+public class IfEmptyTestCase extends BaseTestCase
+{
+ public IfEmptyTestCase(final String name)
+ {
+ super(name);
+ }
+
+ protected void assertEmpty(Object obj)
+ {
+ context.put("obj", obj);
+ assertEvalEquals("", "#if( $obj )fail#end");
+ }
+
+ protected void assertNotEmpty(Object obj)
+ {
+ context.put("obj", obj);
+ assertEvalEquals("", "#if( !$obj )fail#end");
+ }
+
+ public void testNull()
+ {
+ assertEmpty(null);
+ assertEmpty(new NullAsString());
+ assertEmpty(new NullAsNumber());
+ }
+
+ public void testDataStructures()
+ {
+ assertEmpty(Collections.emptyMap());
+ assertEmpty(Collections.emptyList());
+ assertEmpty(new Object[]{});
+ List list = new ArrayList();
+ list.add(1);
+ assertNotEmpty(list);
+ Map map = new TreeMap();
+ map.put("foo", 1);
+ assertNotEmpty(map);
+ }
+
+ public void testString()
+ {
+ assertEmpty("");
+ assertEmpty(new EmptyAsString());
+ assertNotEmpty("hello");
+ }
+
+ public void testNumber()
+ {
+ assertEmpty(0);
+ assertEmpty(0L);
+ assertEmpty(0.0f);
+ assertEmpty(0.0);
+ assertEmpty(BigInteger.ZERO);
+ assertEmpty(BigDecimal.ZERO);
+ assertNotEmpty(1);
+ assertNotEmpty(1L);
+ assertNotEmpty(1.0f);
+ assertNotEmpty(1.0);
+ assertNotEmpty(BigInteger.ONE);
+ assertNotEmpty(BigDecimal.ONE);
+ }
+
+ public void testStringBuilder()
+ {
+ StringBuilder builder = new StringBuilder();
+ assertEmpty(builder);
+ builder.append("yo");
+ assertNotEmpty(builder);
+ }
+
+ public void testLiterals()
+ {
+ assertEvalEquals("", "#if( 0 )fail#end");
+ assertEvalEquals("", "#if( 0.0 )fail#end");
+ assertEvalEquals("", "#if( '' )fail#end");
+ assertEvalEquals("", "#if( \"\" )fail#end");
+ assertEvalEquals("", "#if( [] )fail#end");
+ assertEvalEquals("", "#if( {} )fail#end");
+
+ assertEvalEquals("", "#if( !1 )fail#end");
+ assertEvalEquals("", "#if( !1.0 )fail#end");
+ assertEvalEquals("", "#if( !'foo' )fail#end");
+ assertEvalEquals("", "#if( !\"foo\" )fail#end");
+ assertEvalEquals("", "#if( ![ 'foo' ] )fail#end");
+ assertEvalEquals("", "#if( !{ 'foo':'bar' } )fail#end");
+ }
+
+ public static class NullAsString
+ {
+ public String getAsString()
+ {
+ return null;
+ }
+ }
+
+ public static class EmptyAsString
+ {
+ public String getAsString()
+ {
+ return "";
+ }
+ }
+
+ public static class NullAsNumber
+ {
+ public String getAsNumber()
+ {
+ return null;
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java
new file mode 100755
index 00000000..f1ce938d
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IfNullTestCase.java
@@ -0,0 +1,101 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+
+/**
+ * Used to check that nulls are properly handled in #if statements
+ */
+public class IfNullTestCase extends BaseTestCase
+{
+ public IfNullTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("nullToString", new NullToString());
+ context.put("notnull", new Object());
+ }
+
+ public void testIfEquals()
+ {
+ // both null
+ assertEvalEquals("foo", "#if( $null == $otherNull )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( $null == $nullToString )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( $nullToString == $null )foo#{else}bar#end");
+ // left null, right not
+ assertEvalEquals("bar", "#if( $nullToString == $notnull )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $null == $notnull )foo#{else}bar#end");
+ // right null, left not
+ assertEvalEquals("bar", "#if( $notnull == $nullToString )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $notnull == $null )foo#{else}bar#end");
+ }
+
+ public void testIfNotEquals()
+ {
+ // both null
+ assertEvalEquals("bar", "#if( $null != $otherNull )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $null != $nullToString )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $nullToString != $null )foo#{else}bar#end");
+ // left null, right not
+ assertEvalEquals("foo", "#if( $nullToString != $notnull )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( $null != $notnull )foo#{else}bar#end");
+ // right null, left not
+ assertEvalEquals("foo", "#if( $notnull != $nullToString )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( $notnull != $null )foo#{else}bar#end");
+ }
+
+ public void testIfValue()
+ {
+ assertEvalEquals("bar", "#if( $null )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $nullToString )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( !$null )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( !$nullToString )foo#{else}bar#end");
+ }
+
+ public void testIfAnd()
+ {
+ assertEvalEquals("bar", "#if( $null && $nullToString )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $nullToString && $null )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $null && $notnull )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $notnull && $nullToString )foo#{else}bar#end");
+ }
+
+ public void testIfOr()
+ {
+ assertEvalEquals("bar", "#if( $null || $nullToString )foo#{else}bar#end");
+ assertEvalEquals("bar", "#if( $nullToString || $null )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( $null || $notnull )foo#{else}bar#end");
+ assertEvalEquals("foo", "#if( $notnull || $nullToString )foo#{else}bar#end");
+ }
+
+ public static class NullToString
+ {
+ public String getAsString()
+ {
+ return null;
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeErrorTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeErrorTestCase.java
new file mode 100644
index 00000000..a242d05f
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeErrorTestCase.java
@@ -0,0 +1,119 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+
+import java.io.StringWriter;
+
+
+
+
+/**
+ * Test that #parse and #include pass errors to calling code.
+ * Specifically checking against VELOCITY-95 and VELOCITY-96.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class IncludeErrorTestCase extends BaseTestCase implements TemplateTestBase
+{
+ VelocityEngine ve;
+
+ /**
+ * Default constructor.
+ */
+ public IncludeErrorTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(IncludeErrorTestCase.class);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, TemplateTestBase.TEST_COMPARE_DIR + "/includeerror");
+
+ ve.init();
+ }
+
+
+
+ public void testMissingParseError() throws Exception
+ {
+ checkException("missingparse.vm",ResourceNotFoundException.class);
+ }
+
+ public void testMissingIncludeError() throws Exception
+ {
+ checkException("missinginclude.vm",ResourceNotFoundException.class);
+ }
+
+ public void testParseError() throws Exception
+ {
+ checkException("parsemain.vm",ParseErrorException.class);
+ }
+
+ public void testParseError2() throws Exception
+ {
+ checkException("parsemain2.vm",ParseErrorException.class);
+ }
+
+
+ /**
+ * Check that an exception is thrown for the given template
+ * @param templateName
+ * @param exceptionClass
+ * @throws Exception
+ */
+ private void checkException(String templateName,Class exceptionClass)
+ throws Exception
+ {
+ Context context = new VelocityContext();
+ Template template = ve.getTemplate(templateName, "UTF-8");
+
+ try (StringWriter writer = new StringWriter())
+ {
+ template.merge(context, writer);
+ writer.flush();
+ fail("File should have thrown an exception");
+ }
+ catch (Exception E)
+ {
+ assertTrue(exceptionClass.isAssignableFrom(E.getClass()));
+ }
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeEventHandlingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeEventHandlingTestCase.java
new file mode 100644
index 00000000..720ffc5c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IncludeEventHandlingTestCase.java
@@ -0,0 +1,250 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.event.EventCartridge;
+import org.apache.velocity.app.event.IncludeEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.RuntimeServicesAware;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Tests event handling
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class IncludeEventHandlingTestCase extends BaseTestCase implements IncludeEventHandler,RuntimeServicesAware
+{
+
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/includeevent";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/includeevent";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/includeevent/compare";
+
+
+ private static final int PASS_THROUGH=0;
+ private static final int RELATIVE_PATH=1;
+ private static final int BLOCK=2;
+
+ private int EventHandlerBehavior = PASS_THROUGH;
+
+ /**
+ * Default constructor.
+ */
+ public IncludeEventHandlingTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Velocity.reset();
+ Velocity.addProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+
+
+ }
+
+
+ public static Test suite ()
+ {
+ return new TestSuite(IncludeEventHandlingTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testIncludeEventHandling ()
+ throws Exception
+ {
+ Template template1 = RuntimeSingleton.getTemplate(
+ getFileName(null, "test1", TMPL_FILE_EXT));
+
+ Template template2 = RuntimeSingleton.getTemplate(
+ getFileName(null, "subdir/test2", TMPL_FILE_EXT));
+
+ Template template3 = RuntimeSingleton.getTemplate(
+ getFileName(null, "test3", TMPL_FILE_EXT));
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "test1", RESULT_FILE_EXT));
+
+ FileOutputStream fos2 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "test2", RESULT_FILE_EXT));
+
+ FileOutputStream fos3 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "test3", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+ Writer writer2 = new BufferedWriter(new OutputStreamWriter(fos2));
+ Writer writer3 = new BufferedWriter(new OutputStreamWriter(fos3));
+
+ /*
+ * lets make a Context and add the event cartridge
+ */
+
+ Context context = new VelocityContext();
+
+ /*
+ * Now make an event cartridge, register the
+ * input event handler and attach it to the
+ * Context
+ */
+
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(this);
+ ec.attachToContext( context );
+
+
+ // BEHAVIOR A: pass through #input and #parse with no change
+ EventHandlerBehavior = PASS_THROUGH;
+
+ template1.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ // BEHAVIOR B: pass through #input and #parse with using a relative path
+ EventHandlerBehavior = RELATIVE_PATH;
+
+ template2.merge(context, writer2);
+ writer2.flush();
+ writer2.close();
+
+ // BEHAVIOR C: refuse to pass through #input and #parse
+ EventHandlerBehavior = BLOCK;
+
+ template3.merge(context, writer3);
+ writer3.flush();
+ writer3.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "test1",
+ RESULT_FILE_EXT, CMP_FILE_EXT) ||
+ !isMatch(RESULTS_DIR, COMPARE_DIR, "test2",
+ RESULT_FILE_EXT, CMP_FILE_EXT) ||
+ !isMatch(RESULTS_DIR, COMPARE_DIR, "test3",
+ RESULT_FILE_EXT, CMP_FILE_EXT)
+ )
+ {
+ fail("Output incorrect.");
+ }
+ }
+
+
+ @Override
+ public void setRuntimeServices(RuntimeServices rs )
+ {
+ }
+
+ /**
+ * Sample handler with different behaviors for the different tests.
+ */
+ @Override
+ public String includeEvent(Context context, String includeResourcePath, String currentResourcePath, String directiveName)
+ {
+ if (EventHandlerBehavior == PASS_THROUGH)
+ return includeResourcePath;
+
+
+ // treat as relative path
+ else if (EventHandlerBehavior == RELATIVE_PATH)
+ {
+ // if the resource name starts with a slash, it's not a relative path
+ if (includeResourcePath.startsWith("/") || includeResourcePath.startsWith("\\") ) {
+ return includeResourcePath;
+ }
+
+ int lastslashpos = Math.max(
+ currentResourcePath.lastIndexOf("/"),
+ currentResourcePath.lastIndexOf("\\")
+ );
+
+ // root of resource tree
+ if ( (lastslashpos == -1))
+ return includeResourcePath;
+
+ // prepend path to the input path
+ else
+ return currentResourcePath.substring(0,lastslashpos) + "/" + includeResourcePath;
+
+
+
+ } else if (EventHandlerBehavior == BLOCK)
+ return null;
+
+ // should never happen
+ else
+ return null;
+
+
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IndexTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IndexTestCase.java
new file mode 100644
index 00000000..a585de7e
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IndexTestCase.java
@@ -0,0 +1,171 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 java.util.ArrayList;
+/**
+ * Test index syntax e.g, $foo[1]
+ */
+public class IndexTestCase extends BaseTestCase
+{
+ public IndexTestCase(String name)
+ {
+ super(name);
+ //DEBUG = true;
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+
+ context.put("NULL", null);
+ context.put("red", "blue");
+
+ int[] a = {1, 2, 3};
+ context.put("a", a);
+ String[] str = {"a", "ab", "abc"};
+ context.put("str", str);
+
+ ArrayList alist = new ArrayList();
+ alist.add(1);
+ alist.add(2);
+ alist.add(3);
+ alist.add(a);
+ alist.add(null);
+ context.put("alist", alist);
+
+ Foo foo = new Foo();
+ foo.bar = alist;
+ context.put("foo", foo);
+
+ Boo boo = new Boo();
+ context.put("boo", boo);
+ }
+
+ public void testCallingIndex()
+ {
+ assertEvalEquals("1 -3-", "$a[0] -$a[ 2 ]-");
+ assertEvalEquals("x21", "#set($i = 1)x$a[$i]1");
+ assertEvalEquals("3", "$a[ $a[ $a[0] ] ]");
+ assertEvalEquals("ab", "$str[1]");
+ assertEvalEquals("123", "$alist[0]$alist[1]$alist[2]");
+ assertEvalEquals("1][2-[3]", "$alist[0]][$alist[$a[0]]-[$alist[2]]");
+
+ assertEvalEquals("2", "$alist[3][1]");
+ assertEvalEquals("3 [1]", "$alist[2] [1]");
+
+ assertEvalEquals("4", "#set($bar = [3, 4, 5])$bar[1]");
+ assertEvalEquals("21", "#set($i = 1)#set($bar = [3, $a[$i], $a[0]])$bar[1]$bar[2]");
+
+ assertEvalEquals("2", "$foo.bar[1]");
+ assertEvalEquals("2", "$foo.getBar()[1]");
+ assertEvalEquals("2", "$foo.getBar()[3][1]");
+
+ assertEvalEquals(" a ab abc ", "#foreach($i in $foo.bar[3]) $str[$foreach.index] #end");
+
+ assertEvalEquals("apple", "#set($hash = {'a':'apple', 'b':'orange'})$hash['a']");
+
+ assertEvalEquals("xx ", "#if($alist[4] == $NULL)xx#end #if($alist[4])yy#end");
+
+ assertEvalEquals("BIG TRUEaBIG FALSE", "$foo[true]a$foo[false]");
+ assertEvalEquals("junk foobar ", "$foo[\"junk\"]");
+ assertEvalEquals("GOT NULL", "#set($i=$NULL)$boo[$i]");
+
+ assertEvalEquals("321", "$a[-1]$a[ -2]$a[-3 ]");
+ assertEvalEquals("67xx", "#set($hash={1:11, 5:67, 23:2})$hash[5]$!hash[6]#if(!$hash[1000])xx#end");
+
+ // Some cases that should be evaluated as text
+ assertEvalEquals("[]", "[]");
+ assertEvalEquals("$[]", "$[]");
+ assertEvalEquals("$.[]", "$.[]");
+ assertEvalEquals("$[1]", "$[1]");
+ assertEvalEquals("$[1]1", "$[1]1");
+ assertEvalEquals("$1[1]1", "$1[1]1");
+ assertEvalEquals("blue.[]", "$red.[]");
+ assertEvalEquals("blue[]", "${red}[]");
+ assertEvalEquals("blue][", "$red][");
+ assertEvalEquals("1[]", "${a[0]}[]");
+ assertEvalEquals("1a$[]", "$a[0]a$[]");
+ assertEvalEquals("$![]", "$![]");
+ assertEvalEquals("$\\![]", "$\\![]");
+ }
+
+ public void testIndexSetting()
+ {
+ assertEvalEquals("foo", "#set($str[1] = \"foo\")$str[1]");
+ assertEvalEquals("5150", "#set($alist[1] = 5150)$alist[1]");
+ assertEvalEquals("15", "$alist[3][0]#set($alist[3][0] = 5)$alist[3][0]");
+ assertEvalEquals("orange","#set($blaa = {\"apple\":\"grape\"})#set($blaa[\"apple\"] = \"orange\")$blaa[\"apple\"]");
+ assertEvalEquals("null","#set($str[0] = $NULL)#if($str[0] == $NULL)null#end");
+ assertEvalEquals("null","#set($blaa = {\"apple\":\"grape\"})#set($blaa[\"apple\"] = $NULL)#if($blaa[\"apple\"] == $NULL)null#end");
+ assertEvalEquals("2112", "#set($a[-1] = 2112)$a[2]");
+ assertEvalEquals("3344","#set($hash = {1:11, 2:22, 5:66})#set($hash[2]=33)#set($hash[3]=44)$hash[2]$hash[3]");
+ }
+
+
+ public void testErrorHandling()
+ {
+ assertEvalExceptionAt("$boo['throwex']", 1, 5);
+ assertEvalExceptionAt("$boo[]", 1, 6);
+ assertEvalExceptionAt("$boo[blaa]", 1, 6);
+ assertEvalExceptionAt("#set($foo[1] = 3)", 1, 10);
+ assertEvalExceptionAt("$a[500]", 1, 3);
+ }
+
+
+ public static class Foo
+ {
+ public Object bar = null;
+ public Object getBar()
+ {
+ return bar;
+ }
+
+ public String get(Boolean bool)
+ {
+ if (bool)
+ return "BIG TRUE";
+ else
+ return "BIG FALSE";
+ }
+
+ public String get(String str)
+ {
+ return str + " foobar ";
+ }
+ }
+
+ public static class Boo
+ {
+ public Object get(Object obj)
+ {
+ if (obj == null)
+ return "GOT NULL";
+ else if (obj.equals("throwex"))
+ throw new RuntimeException("Generated Exception");
+
+ return obj;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/InfoTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/InfoTestCase.java
new file mode 100644
index 00000000..0ca5a150
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/InfoTestCase.java
@@ -0,0 +1,118 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.test.misc.UberspectTestException;
+import org.apache.velocity.util.introspection.Info;
+
+import java.io.StringWriter;
+
+
+
+
+/**
+ * Test that the Info class in the Introspector holds the correct information.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:isidore@setgame.com">Llewellyn Falco</a>
+ * @version $Id$
+ */
+public class InfoTestCase extends BaseTestCase implements TemplateTestBase
+{
+ VelocityEngine ve;
+
+ /**
+ * Default constructor.
+ */
+ public InfoTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(InfoTestCase.class);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.setProperty(
+ "runtime.introspector.uberspect", "org.apache.velocity.test.misc.UberspectTestImpl");
+
+ ve.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, TemplateTestBase.TEST_COMPARE_DIR + "/info");
+
+ ve.init();
+ }
+
+
+
+ public void testInfoProperty() throws Exception
+ {
+ // check property
+ checkInfo("info1.vm", 1, 7);
+ }
+
+ public void testInfoMethod() throws Exception
+ {
+ // check method
+ checkInfo("info2.vm", 1, 7);
+ }
+
+ public void checkInfo(String templateName,
+ int expectedLine, int expectedCol) throws Exception
+ {
+ Context context = new VelocityContext();
+ Template template = ve.getTemplate(templateName, "UTF-8");
+ Info info = null;
+
+ context.put("main", this);
+
+ try (StringWriter writer = new StringWriter())
+ {
+ template.merge(context, writer);
+ writer.flush();
+ fail("Uberspect should have thrown an exception");
+ }
+ catch (UberspectTestException E)
+ {
+ info = E.getInfo();
+ }
+ assertInfoEqual(info, templateName, expectedLine, expectedCol);
+
+ }
+
+ private void assertInfoEqual(Info i, String name, int line, int column)
+ {
+ assertEquals("Template Name", name, i.getTemplateName());
+ assertEquals("Template Line", line, i.getLine());
+ assertEquals("Template Column", column, i.getColumn());
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java
new file mode 100644
index 00000000..acaafba9
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/InlineScopeVMTestCase.java
@@ -0,0 +1,134 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Tests if the VM template-locality is working.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
+ * @version $Id$
+ */
+public class InlineScopeVMTestCase extends BaseTestCase implements TemplateTestBase
+{
+ public InlineScopeVMTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ /*
+ * do our properties locally, and just override the ones we want
+ * changed
+ */
+
+ Velocity.reset();
+
+ Velocity.setProperty(
+ Velocity.VM_PERM_ALLOW_INLINE_REPLACE_GLOBAL, "true");
+
+ Velocity.setProperty(
+ Velocity.VM_PERM_INLINE_LOCAL, "true");
+
+ Velocity.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.setProperty("space.gobbling", "bc");
+
+ Velocity.init();
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(InlineScopeVMTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testInlineScopeVM ()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /*
+ * Get the template and the output. Do them backwards.
+ * vm_test2 uses a local VM and vm_test1 doesn't
+ */
+
+ Template template2 = RuntimeSingleton.getTemplate(
+ getFileName(null, "vm_test2", TMPL_FILE_EXT));
+
+ Template template1 = RuntimeSingleton.getTemplate(
+ getFileName(null, "vm_test1", TMPL_FILE_EXT));
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "vm_test1", RESULT_FILE_EXT));
+
+ FileOutputStream fos2 =
+ new FileOutputStream (
+ getFileName(RESULT_DIR, "vm_test2", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+ Writer writer2 = new BufferedWriter(new OutputStreamWriter(fos2));
+
+ /*
+ * put the Vector into the context, and merge both
+ */
+
+ VelocityContext context = new VelocityContext();
+
+ template1.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ template2.merge(context, writer2);
+ writer2.flush();
+ writer2.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,"vm_test1",
+ RESULT_FILE_EXT,CMP_FILE_EXT) ||
+ !isMatch(RESULT_DIR,COMPARE_DIR,"vm_test2",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectionCacheDataTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectionCacheDataTestCase.java
new file mode 100644
index 00000000..bbd4bfab
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectionCacheDataTestCase.java
@@ -0,0 +1,81 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.util.introspection.IntrospectionCacheData;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+/**
+ * Checks that arrays are cached correctly in the Introspector.
+ *
+ * @author <a href="Alexey Pachenko">alex+news@olmisoft.com</a>
+ * @version $Id$
+ */
+public class IntrospectionCacheDataTestCase extends TestCase
+{
+
+ private static class CacheHitCountingVelocityContext extends VelocityContext
+ {
+ public int cacheHit = 0;
+
+ @Override
+ public IntrospectionCacheData icacheGet(Object key)
+ {
+ final IntrospectionCacheData result = super.icacheGet(key);
+ if (result != null) {
+ ++cacheHit;
+ }
+ return result;
+ }
+
+ }
+
+ public void testCache() throws ParseErrorException, MethodInvocationException,
+ ResourceNotFoundException, IOException
+ {
+ CacheHitCountingVelocityContext context = new CacheHitCountingVelocityContext();
+ context.put("this", this);
+ StringWriter w = new StringWriter();
+ Velocity.evaluate(context, w, "test", "$this.exec('a')$this.exec('b')");
+ assertEquals("[a][b]", w.toString());
+ assertTrue(context.cacheHit > 0);
+ }
+
+
+ /**
+ * For use when acting as a context reference.
+ *
+ * @param value
+ * @return
+ */
+ public String exec(String value)
+ {
+ return "[" + value + "]";
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java
new file mode 100644
index 00000000..f97f1c49
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector2TestCase.java
@@ -0,0 +1,168 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.Introspector;
+
+import java.lang.reflect.Method;
+
+/**
+ * Test case for the Velocity Introspector which
+ * tests the ability to find a 'best match'
+ *
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class Introspector2TestCase extends BaseTestCase
+{
+
+ /**
+ * Creates a new instance.
+ */
+ public Introspector2TestCase(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Get the containing <code>TestSuite</code>.
+ *
+ * @return The <code>TestSuite</code> to run.
+ */
+ public static Test suite ()
+ {
+ return new TestSuite(Introspector2TestCase.class);
+ }
+
+ public void testIntrospector()
+ throws Exception
+ {
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+
+ Method method;
+ String result;
+ Tester t = new Tester();
+
+ Object[] params = { new Foo(), new Foo() };
+
+ Introspector introspector = new Introspector(log);
+
+ method = introspector
+ .getMethod( Tester.class, "find", params );
+
+ if ( method == null)
+ fail("Returned method was null");
+
+ result = (String) method.invoke( t, params);
+
+ if ( !result.equals( "Bar-Bar" ) )
+ {
+ fail("Should have gotten 'Bar-Bar' : received '" + result + "'");
+ }
+
+ /*
+ * now test for failure due to ambiguity
+ */
+
+ method = introspector
+ .getMethod( Tester2.class, "find", params );
+
+ if ( method != null)
+ fail("Introspector shouldn't have found a method as it's ambiguous.");
+ }
+
+ public interface Woogie
+ {
+ }
+
+ public static class Bar implements Woogie
+ {
+ int i;
+ }
+
+ public static class Foo extends Bar
+ {
+ int j;
+ }
+
+ public static class Tester
+ {
+ public static String find(Woogie w, Object o )
+ {
+ return "Woogie-Object";
+ }
+
+ public static String find(Object w, Bar o )
+ {
+ return "Object-Bar";
+ }
+
+ public static String find(Bar w, Bar o )
+ {
+ return "Bar-Bar";
+ }
+
+ public static String find( Object o )
+ {
+ return "Object";
+ }
+
+ public static String find( Woogie o )
+ {
+ return "Woogie";
+ }
+ }
+
+ public static class Tester2
+ {
+ public static String find(Woogie w, Object o )
+ {
+ return "Woogie-Object";
+ }
+
+ public static String find(Object w, Bar o )
+ {
+ return "Object-Bar";
+ }
+
+ public static String find(Bar w, Object o )
+ {
+ return "Bar-Object";
+ }
+
+ public static String find( Object o )
+ {
+ return "Object";
+ }
+
+ public static String find( Woogie o )
+ {
+ return "Woogie";
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java
new file mode 100644
index 00000000..aef11f2c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/Introspector3TestCase.java
@@ -0,0 +1,151 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.util.introspection.Introspector;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple introspector test case for primitive problem found in 1.3
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class Introspector3TestCase extends BaseTestCase
+{
+ /**
+ * Creates a new instance.
+ */
+ public Introspector3TestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(Introspector3TestCase.class);
+ }
+
+ public void testSimple()
+ throws Exception
+ {
+ Method method;
+ String result;
+
+ MethodProvider mp = new MethodProvider();
+
+ /*
+ * string integer
+ */
+
+ Object[] listIntInt = { new ArrayList(), 1, 2};
+ Object[] listLongList = { new ArrayList(), 1L, new ArrayList() };
+ Object[] intInt = {1, 2};
+ Object[] longInt = {1L, 2};
+ Object[] longLong = {1L, 2L};
+
+ Introspector introspector = new Introspector(log);
+ method = introspector.getMethod(
+ MethodProvider.class, "lii", listIntInt);
+ result = (String) method.invoke(mp, listIntInt);
+
+ assertTrue(result.equals("lii"));
+
+ method = introspector.getMethod(
+ MethodProvider.class, "ii", intInt);
+ result = (String) method.invoke(mp, intInt);
+
+ assertTrue(result.equals("ii"));
+
+ method = introspector.getMethod(
+ MethodProvider.class, "ll", longInt);
+ result = (String) method.invoke(mp, longInt);
+
+ assertTrue(result.equals("ll"));
+
+ /*
+ * test overloading with primitives
+ */
+
+ method = introspector.getMethod(
+ MethodProvider.class, "ll", longLong);
+ result = (String) method.invoke(mp, longLong);
+
+ assertTrue(result.equals("ll"));
+
+ method = introspector.getMethod(
+ MethodProvider.class, "lll", listLongList);
+ result = (String) method.invoke(mp, listLongList);
+
+ assertTrue(result.equals("lll"));
+
+ /*
+ * test invocation with nulls
+ */
+
+ Object [] oa = {null, 0};
+ method = introspector.getMethod(
+ MethodProvider.class, "lll", oa );
+ result = (String) method.invoke(mp, oa);
+
+ assertTrue(result.equals("Listl"));
+
+ }
+
+ public static class MethodProvider
+ {
+ public String ii(int p, int d)
+ {
+ return "ii";
+ }
+
+ public String lii(List s, int p, int d)
+ {
+ return "lii";
+ }
+
+ public String lll(List s, long p, List d)
+ {
+ return "lll";
+ }
+
+
+ public String lll(List s, long p, int d)
+ {
+ return "lli";
+ }
+
+ public String lll(List s, long p)
+ {
+ return "Listl";
+ }
+
+ public String ll(long p, long d)
+ {
+ return "ll";
+ }
+
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java
new file mode 100644
index 00000000..ec2fec2b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/IntrospectorTestCase.java
@@ -0,0 +1,226 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.Introspector;
+
+import java.lang.reflect.Method;
+
+/**
+ * Test case for the Velocity Introspector which uses
+ * the Java Reflection API to determine the correct
+ * signature of the methods used in VTL templates.
+ *
+ * This should be split into separate tests for each
+ * of the methods searched for but this is a start
+ * for now.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class IntrospectorTestCase extends BaseTestCase
+{
+ private static MethodProvider mp;
+
+ private Introspector introspector;
+
+ @Override
+ public void setUp()
+ {
+ mp = new MethodProvider();
+ log = new TestLogger();
+ introspector = new Introspector(log);
+ }
+
+ /**
+ * Creates a new instance.
+ */
+ public IntrospectorTestCase (String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Get the containing <code>TestSuite</code>. This is always
+ * <code>VelocityTestSuite</code>.
+ *
+ * @return The <code>TestSuite</code> to run.
+ */
+ public static Test suite ()
+ {
+ return new TestSuite(IntrospectorTestCase.class);
+ }
+
+ public void testIntrospectorBoolean()
+ throws Exception
+ {
+ // Test boolean primitive.
+ Object[] booleanParams = {Boolean.TRUE};
+ String type = "boolean";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", booleanParams);
+ String result = (String) method.invoke(mp, booleanParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorByte()
+ throws Exception
+ {
+ // Test byte primitive.
+ Object[] byteParams = { new Byte("1") };
+ String type = "byte";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", byteParams);
+ String result = (String) method.invoke(mp, byteParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorChar()
+ throws Exception
+ {
+ // Test char primitive.
+ Object[] characterParams = {'a'};
+ String type = "character";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", characterParams);
+ String result = (String) method.invoke(mp, characterParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorDouble()
+ throws Exception
+ {
+
+ // Test double primitive.
+ Object[] doubleParams = { 1.0 };
+ String type = "double";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", doubleParams);
+ String result = (String) method.invoke(mp, doubleParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorFloat()
+ throws Exception
+ {
+
+ // Test float primitive.
+ Object[] floatParams = { 1.0f };
+ String type = "float";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", floatParams);
+ String result = (String) method.invoke(mp, floatParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorInteger()
+ throws Exception
+ {
+
+ // Test integer primitive.
+ Object[] integerParams = { 1 };
+ String type = "integer";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", integerParams);
+ String result = (String) method.invoke(mp, integerParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorPrimitiveLong()
+ throws Exception
+ {
+
+ // Test long primitive.
+ Object[] longParams = { 1L };
+ String type = "long";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", longParams);
+ String result = (String) method.invoke(mp, longParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorPrimitiveShort()
+ throws Exception
+ {
+ // Test short primitive.
+ Object[] shortParams = {(short) 1};
+ String type = "short";
+ Method method = introspector.getMethod(
+ MethodProvider.class, type + "Method", shortParams);
+ String result = (String) method.invoke(mp, shortParams);
+
+ assertEquals("Method could not be found", type, result);
+ }
+
+ public void testIntrospectorUntouchable()
+ throws Exception
+ {
+ // Test untouchable
+
+ Object[] params = {};
+
+ Method method = introspector.getMethod(
+ MethodProvider.class, "untouchable", params);
+
+ assertNull("able to access a private-access method.", method);
+ }
+
+ public void testIntrospectorReallyUntouchable()
+ throws Exception
+ {
+ // Test really untouchable
+ Object[] params = {};
+
+ Method method = introspector.getMethod(
+ MethodProvider.class, "reallyuntouchable", params);
+
+ assertNull("able to access a private-access method.", method);
+ }
+
+ public static class MethodProvider
+ {
+ /*
+ * Methods with native parameter types.
+ */
+ public String booleanMethod (boolean p) { return "boolean"; }
+ public String byteMethod (byte p) { return "byte"; }
+ public String characterMethod (char p) { return "character"; }
+ public String doubleMethod (double p) { return "double"; }
+ public String floatMethod (float p) { return "float"; }
+ public String integerMethod (int p) { return "integer"; }
+ public String longMethod (long p) { return "long"; }
+ public String shortMethod (short p) { return "short"; }
+
+ String untouchable() { return "yech";}
+ // don't remove! Used through introspection for testing!
+ private String reallyuntouchable() { return "yech!"; }
+
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/InvalidEventHandlerTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/InvalidEventHandlerTestCase.java
new file mode 100644
index 00000000..48ba8e61
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/InvalidEventHandlerTestCase.java
@@ -0,0 +1,646 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.app.event.EventCartridge;
+import org.apache.velocity.app.event.InvalidReferenceEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.util.RuntimeServicesAware;
+import org.apache.velocity.util.introspection.Info;
+
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Tests event handling for all event handlers except IncludeEventHandler. This is tested
+ * separately due to its complexity.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class InvalidEventHandlerTestCase
+extends TestCase
+{
+ // @@ VELOCITY-553
+ public class TestObject {
+ private String nullValueAttribute = null;
+
+ public String getNullValueAttribute() {
+ return nullValueAttribute;
+ }
+
+ public String getRealString() {
+ return new String("helloFooRealStr");
+ }
+
+ public String getString() {
+ return new String("helloFoo");
+ }
+
+ public String getNullString() {
+ return null;
+ }
+
+ public java.util.Date getNullDate() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("TestObject [nullValueAttribute=");
+ builder.append(nullValueAttribute);
+ builder.append("]");
+ return builder.toString();
+ }
+ }
+ // @@ VELOCITY-553
+
+
+ /**
+ * Default constructor.
+ */
+ public InvalidEventHandlerTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(InvalidEventHandlerTestCase.class);
+ }
+
+ public void testManualEventHandlers()
+ throws Exception
+ {
+ TestEventCartridge te = new TestEventCartridge();
+
+ /*
+ * Test attaching the event cartridge to the context
+ */
+ VelocityEngine ve = new VelocityEngine();
+ ve.init();
+
+ /*
+ * lets make a Context and add the event cartridge
+ */
+
+ VelocityContext inner = new VelocityContext();
+
+ /*
+ * Now make an event cartridge, register all the
+ * event handlers (at once) and attach it to the
+ * Context
+ */
+
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(te);
+ ec.attachToContext( inner );
+
+ doTestInvalidReferenceEventHandler0(ve, inner);
+ doTestInvalidReferenceEventHandler1(ve, inner);
+ doTestInvalidReferenceEventHandler2(ve, inner);
+ doTestInvalidReferenceEventHandler3(ve, inner);
+ doTestInvalidReferenceEventHandler4(ve, inner);
+ }
+
+ /**
+ * Test assigning the event handlers via properties
+ */
+ public void testConfigurationEventHandlers()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_INVALIDREFERENCES, TestEventCartridge.class.getName());
+
+ ve.init();
+ doTestInvalidReferenceEventHandler0(ve, null);
+ doTestInvalidReferenceEventHandler1(ve, null);
+ doTestInvalidReferenceEventHandler2(ve, null);
+ doTestInvalidReferenceEventHandler3(ve, null);
+ doTestInvalidReferenceEventHandler4(ve, null);
+ }
+
+ /**
+ * Test deeper structures
+ * @param ve
+ * @param vc
+ * @throws Exception
+ */
+ private void doTestInvalidReferenceEventHandler4(VelocityEngine ve, VelocityContext vc)
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext(vc);
+
+ Tree test = new Tree();
+ test.setField("10");
+ Tree test2 = new Tree();
+ test2.setField("12");
+ test.setChild(test2);
+
+ context.put("tree",test);
+ String s;
+ Writer w;
+
+ // show work fine
+ s = "$tree.Field $tree.field $tree.child.Field";
+ w = new StringWriter();
+ ve.evaluate(context, w, "mystring", s);
+
+ s = "$tree.x $tree.field.x $tree.child.y $tree.child.Field.y";
+ w = new StringWriter();
+ ve.evaluate(context, w, "mystring", s);
+
+ }
+
+ /**
+ * Test invalid #set
+ * @param ve
+ * @param vc
+ * @throws Exception
+ */
+ private void doTestInvalidReferenceEventHandler3(VelocityEngine ve, VelocityContext vc)
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext(vc);
+ context.put("a1", 5);
+ context.put("a4", 5);
+ context.put("b1","abc");
+
+ String s;
+ Writer w;
+
+ // good object, bad right hand side
+ s = "#set($xx = $a1.afternoon())";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // good object, bad right hand reference
+ s = "#set($yy = $q1)";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ }
+
+ /**
+ * Test invalid method calls
+ * @param ve
+ * @param vc
+ * @throws Exception
+ */
+ private void doTestInvalidReferenceEventHandler2(VelocityEngine ve, VelocityContext vc)
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext(vc);
+ context.put("a1", 5);
+ context.put("a4", 5);
+ context.put("b1","abc");
+
+ String s;
+ Writer w;
+
+ // good object, bad method
+ s = "$a1.afternoon()";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // good object, bad method, quiet reference
+ s = "$!a1.afternoon()";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("", w.toString());
+
+ // bad object, bad method -- fails on get
+ s = "$zz.daylight()";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // bad object, bad method, quiet reference
+ s = "$!zz.daylight()";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("", w.toString());
+
+ // change result
+ s = "$b1.baby()";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("www",w.toString());
+ }
+
+ /**
+ * Test invalid gets/references
+ * @param ve
+ * @param vc
+ * @throws Exception
+ */
+ private void doTestInvalidReferenceEventHandler1(VelocityEngine ve, VelocityContext vc)
+ throws Exception
+ {
+ String result;
+
+ VelocityContext context = new VelocityContext(vc);
+ context.put("a1", 5);
+ context.put("a4", 5);
+ context.put("b1","abc");
+
+ // normal - should be no calls to handler
+ String s = "$a1 $a1.intValue() $b1 $b1.length() #set($c1 = '5')";
+ Writer w = new StringWriter();
+ ve.evaluate(context, w, "mystring", s);
+
+ // good object, bad property
+ s = "$a1.foobar";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // same one as a quiet reference should not fail
+ s = "$!a1.foobar";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("",w.toString());
+
+ // same one inside an #if statement should not fail
+ s = "#if($a1.foobar)yes#{else}no#end";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("no",w.toString());
+
+
+ // bad object, bad property
+ s = "$a2.foobar";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // same one as a quiet reference should not fail
+ s = "$!a2.foobar";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("",w.toString());
+
+ // same one inside an #if statement should still fail
+ s = "#if($a2.foobar)yes#{else}no#end";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // except if object is tested first
+ s = "#if($a2 and $a2.foobar)yes#{else}no#end";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ assertEquals("no", w.toString());
+
+ // bad object, no property
+ s = "$a3";
+ w = new StringWriter();
+ try {
+ ve.evaluate( context, w, "mystring", s );
+ fail("Expected exception.");
+ } catch (RuntimeException e) {}
+
+ // bad object, no property as quiet reference should not fail
+ s = "$!a3";
+ w = new StringWriter();
+ ve.evaluate(context, w, "mystring", s);
+ result = w.toString();
+ assertEquals("", result);
+
+ // bad object, no property as #if condition should not fail
+ s = "#if($a3)yes#{else}no#end";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ result = w.toString();
+ assertEquals("no", result);
+
+ // good object, bad property; change the value
+ s = "$a4.foobar";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+ result = w.toString();
+ assertEquals("zzz", result);
+
+ }
+
+ /**
+ * Test invalidGetMethod
+ *
+ * Test behaviour (which should be the same) of
+ * $objRef.myAttribute and $objRef.getMyAttribute()
+ *
+ * @param ve
+ * @param vc
+ * @throws Exception
+ */
+ private void doTestInvalidReferenceEventHandler0(VelocityEngine ve, VelocityContext vc)
+ throws Exception
+ {
+ String result;
+ Writer w;
+ String s;
+ boolean rc;
+
+ VelocityContext context = new VelocityContext(vc);
+ context.put("propertyAccess", new String("lorem ipsum"));
+ context.put("objRef", new TestObject());
+ java.util.ArrayList arrayList = new java.util.ArrayList();
+ arrayList.add("firstOne");
+ arrayList.add(null);
+ java.util.HashMap hashMap = new java.util.HashMap();
+ hashMap.put(41, "41 is not 42");
+
+ context.put("objRefArrayList", arrayList);
+ context.put("objRefHashMap", hashMap);
+
+ // good object, good property (returns non null value)
+ s = "#set($resultVar = $propertyAccess.bytes)"; // -> getBytes()
+ w = new StringWriter();
+ rc = ve.evaluate( context, w, "mystring", s );
+
+ // good object, good property accessor method (returns non null value)
+ s = "#set($resultVar = $propertyAccess.getBytes())"; // -> getBytes()
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+ // good object, good property (returns non null value)
+ s = "$objRef.getRealString()";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+ // good object, good property accessor method (returns null value)
+ // No exception shall be thrown, as returning null should be valid
+ s = "$objRef.getNullValueAttribute()";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+ // good object, good property (returns null value)
+ // No exception shall be thrown, as returning null should be valid
+ s = "$objRef.nullValueAttribute"; // -> getNullValueAttribute()
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+ // good object, good accessor method which returns a non-null object reference
+ // Test removing a hashmap element which exists
+ s = "$objRefHashMap.remove(41)";
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+
+ // good object, good accessor method which returns null
+ // Test removing a hashmap element which DOES NOT exist
+ // Expected behaviour: Returning null as a value should be
+ // OK and not result in an exception
+ s = "$objRefHashMap.remove(42)"; // will return null, as the key does not exist
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+ // good object, good method invocation (returns non-null object reference)
+ s = "$objRefArrayList.get(0)"; // element 0 is NOT NULL
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+
+ // good object, good method invocation (returns null value)
+ // Expected behaviour: Returning null as a value should be
+ // OK and not result in an exception
+ s = "$objRefArrayList.get(1)"; // element 1 is null
+ w = new StringWriter();
+ ve.evaluate( context, w, "mystring", s );
+
+ }
+
+
+
+ /**
+ * Test assigning the event handlers via properties
+ */
+
+ public static class TestEventCartridge
+ implements InvalidReferenceEventHandler,
+ RuntimeServicesAware
+ {
+ private RuntimeServices rs;
+
+ public TestEventCartridge()
+ {
+ }
+
+ /**
+ * Required by EventHandler
+ */
+ @Override
+ public void setRuntimeServices(RuntimeServices rs )
+ {
+ // make sure this is only called once
+ if (this.rs == null)
+ this.rs = rs;
+
+ else
+ fail("initialize called more than once.");
+ }
+
+
+ @Override
+ public Object invalidGetMethod(Context context, String reference, Object object, String property, Info info)
+ {
+ // as a test, make sure this EventHandler is initialized
+ if (rs == null)
+ fail ("Event handler not initialized!");
+
+ switch (reference)
+ {
+ // good object, bad property
+ case "$a1.foobar":
+ assertEquals(Integer.valueOf(5), object);
+ assertEquals("foobar", property);
+ throw new RuntimeException("expected exception");
+
+ // bad object, bad property
+ case "$a2":
+ assertNull(object);
+ assertNull(property);
+ throw new RuntimeException("expected exception");
+
+
+ // bad object, no property
+ case "$a3":
+ assertNull(object);
+ assertNull(property);
+ throw new RuntimeException("expected exception");
+
+
+ // good object, bad property; change the value
+ case "$a4.foobar":
+ assertEquals(Integer.valueOf(5), object);
+ assertEquals("foobar", property);
+ return "zzz";
+
+
+ // bad object, bad method -- fail on the object
+ case "$zz":
+ assertNull(object);
+ assertNull(property);
+ throw new RuntimeException("expected exception");
+
+
+ // pass q1 through
+ case "$q1":
+
+ break;
+ case "$tree.x":
+ assertEquals("x", property);
+ break;
+ case "$tree.field.x":
+ assertEquals("x", property);
+ break;
+ case "$tree.child.y":
+ assertEquals("y", property);
+ break;
+ case "$tree.child.Field.y":
+ assertEquals("y", property);
+ break;
+ default:
+ fail("invalidGetMethod: unexpected reference: " + reference);
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ public Object invalidMethod(Context context, String reference, Object object, String method, Info info)
+ {
+ // as a test, make sure this EventHandler is initialized
+ if (rs == null)
+ fail ("Event handler not initialized!");
+
+ // good reference, bad method
+ if (object.getClass().equals(Integer.class))
+ {
+ assertEquals("$a1.afternoon()",reference);
+ assertEquals("afternoon",method);
+ throw new RuntimeException("expected exception");
+ }
+
+ else if (object.getClass().equals(String.class) && "baby".equals(method))
+ {
+ return "www";
+ }
+
+ else
+ {
+ fail("Unexpected invalid method. " + method);
+ }
+
+ return null;
+ }
+
+
+ @Override
+ public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info)
+ {
+
+ // as a test, make sure this EventHandler is initialized
+ if (rs == null)
+ fail ("Event handler not initialized!");
+
+ // good object, bad method
+ if (leftreference.equals("xx"))
+ {
+ assertEquals("q1.afternoon()",rightreference);
+ throw new RuntimeException("expected exception");
+ }
+ if (leftreference.equals("yy"))
+ {
+ assertEquals("$q1",rightreference);
+ throw new RuntimeException("expected exception");
+ }
+ else
+ {
+ fail("Unexpected left hand side. " + leftreference);
+ }
+
+ return false;
+ }
+
+ }
+
+ public static class Tree
+ {
+ String field;
+ Tree child;
+
+ public Tree()
+ {
+
+ }
+
+ public String getField()
+ {
+ return field;
+ }
+
+ public void setField(String field)
+ {
+ this.field = field;
+ }
+
+ public Tree getChild()
+ {
+ return child;
+ }
+
+ public void setChild(Tree child)
+ {
+ this.child = child;
+ }
+
+ public String testMethod()
+ {
+ return "123";
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroAutoReloadTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroAutoReloadTestCase.java
new file mode 100644
index 00000000..d8f12dde
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroAutoReloadTestCase.java
@@ -0,0 +1,103 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+
+/**
+ * This class tests the velocimacro.library.autoreload functionality, and issue VELOCITY-
+ */
+public class MacroAutoReloadTestCase extends BaseTestCase
+{
+ private final String RELOAD_TEMPLATE_PATH = TEST_COMPARE_DIR + "/reload";
+
+ public MacroAutoReloadTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ // always copy macros library before modifying it, to ensure successive tests will pass
+ Files.copy(FileSystems.getDefault().getPath(RELOAD_TEMPLATE_PATH + "/macros.vtl"), FileSystems.getDefault().getPath(RELOAD_TEMPLATE_PATH + "/macros2.vtl"), StandardCopyOption.REPLACE_EXISTING);
+
+ engine = new VelocityEngine();
+
+ //by default, make the engine's log output go to the test-report
+ log = new TestLogger(false, false);
+ engine.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, log);
+
+ // use file resource loader
+ engine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file,string");
+ engine.addProperty("file.resource.loader.path", RELOAD_TEMPLATE_PATH);
+ engine.addProperty("velocimacro.library", "macros2.vtl");
+ engine.addProperty("velocimacro.library.autoreload", "true");
+ engine.addProperty("file.resource.loader.cache", "false");
+ engine.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+ engine.addProperty("string.resource.loader.repository.name", "stringRepo");
+ engine.addProperty("string.resource.loader.repository.static", "false");
+ context = new VelocityContext();
+ }
+
+
+ public void testChangedMacro() throws Exception
+ {
+ String template = "#foo('hip')";
+ assertEvalEquals("hop_hip", template);
+
+ FileWriter writer = new FileWriter(RELOAD_TEMPLATE_PATH + "/macros2.vtl");
+ writer.write("#macro(foo $txt)hip_$txt#{end}");
+ writer.close();
+ // last modified timestamps resolution is one second before Java 8,
+ // so force reloading by setting a file date in the future
+ File macros2 = new File(TEST_COMPARE_DIR + "/reload/macros2.vtl");
+ macros2.setLastModified(macros2.lastModified() + 1000);
+
+ assertEvalEquals("hip_hip", template);
+ }
+
+ public void testNewMacro() throws Exception
+ {
+ String template = "#foo('hip')";
+ assertEvalEquals("hop_hip", template);
+
+ FileWriter writer = new FileWriter(TEST_COMPARE_DIR + "/reload/macros2.vtl", true);
+ writer.write("\n#macro(bar $txt)hep_$txt#{end}");
+ writer.close();
+ // last modified timestamps resolution is one second before Java 8,
+ // so force reloading by setting a file date in the future
+ File macros2 = new File(TEST_COMPARE_DIR + "/reload/macros2.vtl");
+ macros2.setLastModified(macros2.lastModified() + 1000);
+
+ template = "#foo('hip') #bar('hip')";
+ assertEvalEquals("hop_hip hep_hip", template);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroCommentsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroCommentsTestCase.java
new file mode 100644
index 00000000..3a38f9e4
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroCommentsTestCase.java
@@ -0,0 +1,56 @@
+package org.apache.velocity.test;
+
+/*
+ * 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;
+
+/**
+ * Test Macro comment functionality
+ */
+public class MacroCommentsTestCase extends BaseTestCase
+{
+ public MacroCommentsTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ public void testComments()
+ {
+ assertEvalEquals("ab","#macro(foo ##\n $bar \n ## blaa\n $bar2##\n)$bar$bar2#end#foo(\"a\" \"b\")");
+ assertEvalEquals("","#macro(foo1##\n)#end#foo1()");
+ assertEvalEquals("ab","#macro(foo2##\n\t ####\r $bar \n ##\n## Testing blaa\n $bar2##\n)$bar$bar2#end#foo2(\"a\" \"b\")");
+ assertEvalEquals("","#macro(foo4 ## test\n ## test2 ## test3 \n)#end#foo4()");
+ assertEvalEquals("","#macro(foo4 ## test\n $x = 5 ## test2 ## test3 \n)#end#foo4()");
+ }
+
+ public void testErrors()
+ {
+ // We only allow comment lines in macro definitions
+ assertEvalException("#foo1(## test)", ParseErrorException.class);
+ assertEvalException("#foo1($test ## test)", ParseErrorException.class);
+ assertEvalException("#break(## test)", ParseErrorException.class);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroDefaultArgTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroDefaultArgTestCase.java
new file mode 100644
index 00000000..368991f4
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroDefaultArgTestCase.java
@@ -0,0 +1,73 @@
+package org.apache.velocity.test;
+
+/*
+ * 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;
+
+/**
+ * Test macro default parameters.
+ */
+public class MacroDefaultArgTestCase extends BaseTestCase
+{
+ public MacroDefaultArgTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+ engine.setProperty(RuntimeConstants.VM_ARGUMENTS_STRICT, Boolean.TRUE);
+ engine.setProperty(RuntimeConstants.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, Boolean.TRUE);
+ }
+
+ public void testCompare()
+ {
+ assertEvalEquals("121", "#macro(foo $a=1)$a#end#foo()#foo(2)#foo");
+ assertEvalEquals("12", "#macro(foo $a = 1)$a#end#foo()#foo(2)");
+ assertEvalEquals("12", "#macro(foo $a= 1 )$a#end#foo()#foo(2)");
+ assertEvalEquals("1x2x", "#macro(foo $a= 1 $b = \"x\")$a$b#end#foo()#foo(2)");
+ assertEvalEquals("1 2 5 2 5 [1, 2] ", "#macro(foo $a=1 $b=2)$a $b #end#foo()#foo(5)#foo(5 [1,2])");
+ assertEvalEquals("1 2 5 2 5 [1, 2] ", "#macro(foo $a=1 , $b=2)$a $b #end#foo()#foo(5)#foo(5 [1,2])");
+ assertEvalEquals("1 2 5 2 5 [1, 2] ", "#macro(foo, $a=1\n $b =2 )$a $b #end#foo()#foo(5)#foo(5 [1,2])");
+
+ assertEvalEquals("3 2 5 2 5 [1, 2] ", "#macro(foo, $a=$x $b =2 )$a $b #end#set($x=3)#foo()#foo(5)#foo(5 [1,2])");
+ assertEvalEquals("{a=3} 2 5 2 5 [1, 2] ", "#macro(foo, $a = {\"a\":$x} $b =2 )$a $b #end#set($x=3)#foo()#foo(5)#foo(5 [1,2])");
+ assertEvalEquals("3 2 5 2 5 [1, 2] ", "#macro(foo, $a = \"$x\" $b =2 )$a $b #end#set($x=3)#foo()#foo(5)#foo(5 [1,2])");
+ assertEvalEquals("3$y 2 5 2 5 [1, 2] ", "#macro(foo, $a = \"$x\\$y\" $b =2 )$a $b #end#set($x=3)#foo()#foo(5)#foo(5 [1,2])");
+ assertEvalEquals("5 3 2 5 [1, 2] 2 ", "#macro(foo, $c $a = \"$x\" $b =2 )$c $a $b #end#set($x=3)#foo(5)#foo(5 [1,2])");
+
+ assertEvalEquals("1xno2xyes", "#macro(foo $a= 1 $b = \"x\")$a$b$bodyContent#end#@foo()no#end#@foo(2)yes#end");
+
+ assertEvalEquals("xy", "#macro(foo $a=\"$b$c\"##\n)$a#end#set($b=\"x\")#set($c=\"y\")#foo()");
+ }
+
+ public void testErrors()
+ {
+ assertEvalException("#macro(foo $a = 1 $b)#end");
+ assertEvalException("#macro(foo $c $a = 3 $b)#end");
+ assertEvalException("#macro(foo $a $b = 1)#end#foo()"); // Too few arguments
+ assertEvalException("#macro(foo $a $b $c = 4)#end#foo(1)"); // Too few arguments
+ assertEvalException("#macro(foo $a = 3)#end#foo(2 3)"); // Too many arguments
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroForwardDefineTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroForwardDefineTestCase.java
new file mode 100644
index 00000000..5a9fb8da
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MacroForwardDefineTestCase.java
@@ -0,0 +1,119 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.StringWriter;
+
+/**
+ * Make sure that a forward referenced macro inside another macro definition does
+ * not report an error in the log.
+ * (VELOCITY-71).
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+public class MacroForwardDefineTestCase
+ extends BaseTestCase
+{
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/macroforwarddefine";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/macroforwarddefine";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/macroforwarddefine/compare";
+
+ /**
+ * Collects the log messages.
+ */
+ private TestLogger logger = new TestLogger(false, true);
+
+ /**
+ * Default constructor.
+ */
+ public MacroForwardDefineTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ // use Velocity.setProperty (instead of properties file) so that we can use actual instance of log
+ Velocity.reset();
+ Velocity.setProperty(RuntimeConstants.RESOURCE_LOADERS,"file");
+ Velocity.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH );
+ Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID,"true");
+
+ // actual instance of logger
+ logger.setEnabledLevel(TestLogger.LOG_LEVEL_DEBUG);
+ Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ Velocity.init();
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(MacroForwardDefineTestCase.class);
+ }
+
+ public void testLogResult()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+ Template template = Velocity.getTemplate("macros.vm");
+
+ // try to get only messages during merge
+ logger.startCapture();
+ template.merge(context, new StringWriter());
+ logger.stopCapture();
+
+ String resultLog = logger.getLog();
+ if ( !isMatch(resultLog, COMPARE_DIR, "velocity.log", "cmp"))
+ {
+ String compare = getFileContents(COMPARE_DIR, "velocity.log", CMP_FILE_EXT);
+
+ String msg = "Log output was incorrect\n"+
+ "-----Result-----\n"+ resultLog +
+ "----Expected----\n"+ compare +
+ "----------------";
+
+ fail(msg);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodCacheKeyTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodCacheKeyTestCase.java
new file mode 100644
index 00000000..43436f0b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodCacheKeyTestCase.java
@@ -0,0 +1,88 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.velocity.runtime.parser.node.ASTMethod;
+
+/**
+ * Checks that the equals method works correctly when caching method keys.
+ *
+ * @author <a href="Will Glass-Husain">wglass@forio.com</a>
+ * @version $Id$
+ */
+public class MethodCacheKeyTestCase extends TestCase
+{
+
+ public void testMethodKeyCacheEquals()
+ {
+ Class [] elements1 = new Class [] { Object.class };
+ ASTMethod.MethodCacheKey mck1 = new ASTMethod.MethodCacheKey("test",elements1, false);
+
+ selfEqualsAssertions(mck1);
+
+ Class [] elements2 = new Class [] { Object.class };
+ ASTMethod.MethodCacheKey mck2 = new ASTMethod.MethodCacheKey("test",elements2, false);
+
+ assertTrue(mck1.equals(mck2));
+
+ Class [] elements3 = new Class [] { String.class };
+ ASTMethod.MethodCacheKey mck3 = new ASTMethod.MethodCacheKey("test",elements3, false);
+
+ assertFalse(mck1.equals(mck3));
+
+ Class [] elements4 = new Class [] { Object.class };
+ ASTMethod.MethodCacheKey mck4 = new ASTMethod.MethodCacheKey("boo",elements4, false);
+
+ assertFalse(mck1.equals(mck4));
+
+ /* check for potential NPE's */
+ Class [] elements5 = ArrayUtils.EMPTY_CLASS_ARRAY;
+ ASTMethod.MethodCacheKey mck5 = new ASTMethod.MethodCacheKey("boo",elements5, false);
+ selfEqualsAssertions(mck5);
+
+ Class [] elements6 = null;
+ ASTMethod.MethodCacheKey mck6 = new ASTMethod.MethodCacheKey("boo",elements6, false);
+ selfEqualsAssertions(mck6);
+
+ Class [] elements7 = new Class [] {};
+ ASTMethod.MethodCacheKey mck7 = new ASTMethod.MethodCacheKey("boo",elements7, false);
+ selfEqualsAssertions(mck7);
+
+ Class [] elements8 = new Class [] {null};
+ ASTMethod.MethodCacheKey mck8 = new ASTMethod.MethodCacheKey("boo",elements8, false);
+ selfEqualsAssertions(mck8);
+
+ Class [] elements9 = new Class [] { Object.class };
+ ASTMethod.MethodCacheKey mck9 = new ASTMethod.MethodCacheKey("boo",elements9, false);
+ selfEqualsAssertions(mck9);
+
+ }
+
+ private void selfEqualsAssertions(ASTMethod.MethodCacheKey mck)
+ {
+ assertTrue(mck.equals(mck));
+ assertTrue(!mck.equals(null));
+ assertTrue(!mck.equals(null));
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodInvocationExceptionTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodInvocationExceptionTestCase.java
new file mode 100644
index 00000000..34f4d813
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodInvocationExceptionTestCase.java
@@ -0,0 +1,278 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.StringWriter;
+
+/**
+ * Tests if we can hand Velocity an arbitrary class for logging.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class MethodInvocationExceptionTestCase extends TestCase
+{
+ protected boolean DEBUG = false;
+
+ /**
+ * Default constructor.
+ * @param name
+ */
+ public MethodInvocationExceptionTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ /*
+ * init() Runtime with defaults
+ */
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(MethodInvocationExceptionTestCase.class);
+ }
+
+ protected void log(String out)
+ {
+ Velocity.getLog().debug(out);
+ if (DEBUG)
+ {
+ System.out.println(out);
+ }
+ }
+
+ /**
+ * Runs the test :
+ *
+ * uses the Velocity class to eval a string
+ * which accesses a method that throws an
+ * exception.
+ * @throws Exception
+ */
+ public void testNormalMethodInvocationException ()
+ throws Exception
+ {
+ String template = "$woogie.doException() boing!";
+
+ VelocityContext vc = new VelocityContext();
+
+ vc.put("woogie", this );
+
+ StringWriter w = new StringWriter();
+
+ try
+ {
+ Velocity.evaluate( vc, w, "test", template );
+ fail("No exception thrown");
+ }
+ catch( MethodInvocationException mie )
+ {
+ log("Caught MIE (good!) :" );
+ log(" reference = " + mie.getReferenceName() );
+ log(" method = " + mie.getMethodName() );
+
+ Throwable t = mie.getCause();
+ log(" throwable = " + t );
+
+ if( t instanceof Exception)
+ {
+ log(" exception = " + t.getMessage() );
+ }
+ }
+ }
+
+
+ public void testGetterMethodInvocationException ()
+ throws Exception
+ {
+ VelocityContext vc = new VelocityContext();
+ vc.put("woogie", this );
+
+ StringWriter w = new StringWriter();
+
+ /*
+ * second test - to ensure that methods accessed via get+ construction
+ * also work
+ */
+
+ String template = "$woogie.foo boing!";
+
+ try
+ {
+ Velocity. evaluate( vc, w, "test", template );
+ fail("No exception thrown, second test.");
+ }
+ catch( MethodInvocationException mie )
+ {
+ log("Caught MIE (good!) :" );
+ log(" reference = " + mie.getReferenceName() );
+ log(" method = " + mie.getMethodName() );
+
+ Throwable t = mie.getCause();
+ log(" throwable = " + t );
+
+ if( t instanceof Exception)
+ {
+ log(" exception = " + t.getMessage() );
+ }
+ }
+ }
+
+
+ public void testCapitalizedGetterMethodInvocationException ()
+ throws Exception
+ {
+ VelocityContext vc = new VelocityContext();
+ vc.put("woogie", this );
+
+ StringWriter w = new StringWriter();
+
+ String template = "$woogie.Foo boing!";
+
+ try
+ {
+ Velocity. evaluate( vc, w, "test", template );
+ fail("No exception thrown, third test.");
+ }
+ catch( MethodInvocationException mie )
+ {
+ log("Caught MIE (good!) :" );
+ log(" reference = " + mie.getReferenceName() );
+ log(" method = " + mie.getMethodName() );
+
+ Throwable t = mie.getCause();
+ log(" throwable = " + t );
+
+ if( t instanceof Exception)
+ {
+ log(" exception = " + t.getMessage() );
+ }
+ }
+ }
+
+ public void testSetterMethodInvocationException ()
+ throws Exception
+ {
+ VelocityContext vc = new VelocityContext();
+ vc.put("woogie", this );
+
+ StringWriter w = new StringWriter();
+
+ String template = "#set($woogie.foo = 'lala') boing!";
+
+ try
+ {
+ Velocity. evaluate( vc, w, "test", template );
+ fail("No exception thrown, set test.");
+ }
+ catch( MethodInvocationException mie )
+ {
+ log("Caught MIE (good!) :" );
+ log(" reference = " + mie.getReferenceName() );
+ log(" method = " + mie.getMethodName() );
+
+ Throwable t = mie.getCause();
+ log(" throwable = " + t );
+
+ if( t instanceof Exception)
+ {
+ log(" exception = " + t.getMessage() );
+ }
+ }
+ }
+
+
+ /**
+ * test that no exception is thrown when in parameter to macro.
+ * This is the way we expect the system to work, but it would be better
+ * to throw an exception.
+ * @throws Exception
+ */
+ public void testMacroInvocationException ()
+ throws Exception
+ {
+ VelocityContext vc = new VelocityContext();
+ vc.put("woogie", this );
+
+ StringWriter w = new StringWriter();
+
+ String template = "#macro (macro1 $param) $param #end #macro1($woogie.getFoo())";
+
+ try
+ {
+ Velocity. evaluate( vc, w, "test", template );
+ fail("No exception thrown, macro invocation test.");
+ }
+ catch( MethodInvocationException mie )
+ {
+ log("Caught MIE (good!) :" );
+ log(" reference = " + mie.getReferenceName() );
+ log(" method = " + mie.getMethodName() );
+
+ Throwable t = mie.getCause();
+ log(" throwable = " + t );
+
+ if( t instanceof Exception)
+ {
+ log(" exception = " + t.getMessage() );
+ }
+ }
+ catch( Exception e)
+ {
+ fail("Wrong exception thrown, test of exception within macro parameter");
+ }
+ }
+
+ public void doException()
+ throws Exception
+ {
+ throw new NullPointerException();
+ }
+
+ public void getFoo()
+ throws Exception
+ {
+ throw new Exception("Hello from getFoo()" );
+ }
+
+ public void setFoo( String foo )
+ throws Exception
+ {
+ throw new Exception("Hello from setFoo()");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodOverloadingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodOverloadingTestCase.java
new file mode 100644
index 00000000..711481c8
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MethodOverloadingTestCase.java
@@ -0,0 +1,184 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Test a reported bug in which method overloading throws IllegalArgumentException
+ * after a null return value.
+ * (VELOCITY-132).
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class MethodOverloadingTestCase extends BaseTestCase
+{
+ String logData;
+
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/methodoverloading";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/methodoverloading";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/methodoverloading/compare";
+
+ /**
+ * Default constructor.
+ */
+ public MethodOverloadingTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(MethodOverloadingTestCase.class);
+ }
+
+ public void testMethodOverloading()
+ throws Exception
+ {
+ /*
+ * test overloading in a single template
+ */
+ testFile("single");
+
+ assertTrue(!logData.contains("IllegalArgumentException"));
+ }
+
+ public void testParsedMethodOverloading()
+ throws Exception
+ {
+ /*
+ * test overloading in a file included with #parse
+ */
+ testFile("main");
+
+ assertTrue(!logData.contains("IllegalArgumentException"));
+
+ }
+
+ public void testFile(String basefilename)
+ throws Exception
+ {
+ TestLogger logger = new TestLogger();
+ VelocityEngine ve = new VelocityEngine();
+ ve.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ ve.setProperty(VelocityEngine.RUNTIME_LOG_INSTANCE, logger );
+ ve.init();
+
+ Template template;
+ FileOutputStream fos;
+ Writer fwriter;
+ Context context;
+
+ template = ve.getTemplate( getFileName(null, basefilename, TMPL_FILE_EXT) );
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, basefilename, RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ context = new VelocityContext();
+ setupContext(context);
+ logger.on();
+ template.merge(context, fwriter);
+ logger.off();
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, basefilename, RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ logData = logger.getLog();
+ }
+
+ public void setupContext(Context context)
+ {
+ context.put("test", this);
+ context.put("nullValue", null);
+ }
+
+
+ public String overloadedMethod ( Integer s )
+ {
+ return "Integer";
+ }
+
+ public String overloadedMethod ( String s )
+ {
+ return "String";
+ }
+
+
+ public String overloadedMethod2 ( Integer s )
+ {
+ return "Integer";
+ }
+
+ public String overloadedMethod2 ( String i )
+ {
+ return "String";
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MiscTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MiscTestCase.java
new file mode 100644
index 00000000..a1a284a7
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MiscTestCase.java
@@ -0,0 +1,74 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.runtime.RuntimeInstance;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Test case for any miscellaneous stuff. If it isn't big, and doesn't fit
+ * anywhere else, it goes here
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class MiscTestCase extends BaseTestCase
+{
+ public MiscTestCase (String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(MiscTestCase.class);
+ }
+
+ public void testRuntimeInstanceProperties()
+ {
+ // check that runtime instance properties can be set and retrieved
+ RuntimeInstance ri = new RuntimeInstance();
+ ri.setProperty("baabaa.test","the answer");
+ assertEquals("the answer",ri.getProperty("baabaa.test"));
+ }
+
+ public void testStringUtils()
+ {
+ /*
+ * some StringUtils tests
+ */
+
+ String arg = null;
+ String res = StringUtils.trim(arg);
+ assertNull(arg);
+
+ arg = " test ";
+ res = StringUtils.trim(arg);
+ assertEquals("test",res);
+
+ arg = "test";
+ res = StringUtils.trim(arg);
+ assertEquals("test",res);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MultiLoaderTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MultiLoaderTestCase.java
new file mode 100644
index 00000000..dc7d67aa
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MultiLoaderTestCase.java
@@ -0,0 +1,223 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Load templates from the Classpath.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a>
+ * @version $Id$
+ */
+public class MultiLoaderTestCase extends BaseTestCase
+{
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/multiloader";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/multiloader";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/multiloader/compare";
+
+ /**
+ * Default constructor.
+ */
+ public MultiLoaderTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ /*
+ * Set up the file loader.
+ */
+
+ Velocity.reset();
+
+ Velocity.setProperty(Velocity.RESOURCE_LOADERS, "file");
+
+ Velocity.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ Velocity.addProperty(Velocity.RESOURCE_LOADERS, "classpath");
+
+ Velocity.addProperty(Velocity.RESOURCE_LOADERS, "jar");
+
+ /*
+ * Set up the classpath loader.
+ */
+
+ Velocity.setProperty(
+ Velocity.RESOURCE_LOADER + ".classpath.class",
+ "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+
+ Velocity.setProperty(
+ Velocity.RESOURCE_LOADER + ".classpath.cache",
+ "false");
+
+ Velocity.setProperty(
+ Velocity.RESOURCE_LOADER + ".classpath.modification_check_interval",
+ "2");
+
+ /*
+ * setup the Jar loader
+ */
+
+ Velocity.setProperty(
+ Velocity.RESOURCE_LOADER + ".jar.class",
+ "org.apache.velocity.runtime.resource.loader.JarResourceLoader");
+
+ Velocity.setProperty(
+ Velocity.RESOURCE_LOADER + ".jar.path",
+ "jar:file:" + FILE_RESOURCE_LOADER_PATH + "/test2.jar" );
+
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(MultiLoaderTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testMultiLoader ()
+ throws Exception
+ {
+ /*
+ * lets ensure the results directory exists
+ */
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ /*
+ * Template to find with the file loader.
+ */
+ Template template1 = Velocity.getTemplate(
+ getFileName(null, "path1", TMPL_FILE_EXT));
+
+ /*
+ * Template to find with the classpath loader.
+ */
+ Template template2 = Velocity.getTemplate("includeevent/test1-cp." + TMPL_FILE_EXT);
+
+ /*
+ * Template to find with the jar loader
+ */
+ Template template3 = Velocity.getTemplate("template/test2." + TMPL_FILE_EXT);
+
+ /*
+ * and the results files
+ */
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "path1", RESULT_FILE_EXT));
+
+ FileOutputStream fos2 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "test2", RESULT_FILE_EXT));
+
+ FileOutputStream fos3 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "test3", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+ Writer writer2 = new BufferedWriter(new OutputStreamWriter(fos2));
+ Writer writer3 = new BufferedWriter(new OutputStreamWriter(fos3));
+
+ /*
+ * put the Vector into the context, and merge both
+ */
+
+ VelocityContext context = new VelocityContext();
+
+ template1.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ template2.merge(context, writer2);
+ writer2.flush();
+ writer2.close();
+
+ template3.merge(context, writer3);
+ writer3.flush();
+ writer3.close();
+
+ if (!isMatch(RESULTS_DIR,COMPARE_DIR,"path1",RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Output incorrect for FileResourceLoader test.");
+ }
+
+ if (!isMatch(RESULTS_DIR,COMPARE_DIR,"test2",RESULT_FILE_EXT,CMP_FILE_EXT) )
+ {
+ fail("Output incorrect for ClasspathResourceLoader test.");
+ }
+
+ if( !isMatch(RESULTS_DIR,COMPARE_DIR,"test3",RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Output incorrect for JarResourceLoader test.");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/MultipleFileResourcePathTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/MultipleFileResourcePathTestCase.java
new file mode 100644
index 00000000..c3cd32f3
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/MultipleFileResourcePathTestCase.java
@@ -0,0 +1,144 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Multiple paths in the file resource loader.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class MultipleFileResourcePathTestCase extends BaseTestCase
+{
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH1 = TEST_COMPARE_DIR + "/multi/path1";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH2 = TEST_COMPARE_DIR + "/multi/path2";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/multi";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/multi/compare";
+
+ /**
+ * Default constructor.
+ */
+ public MultipleFileResourcePathTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(MultipleFileResourcePathTestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Velocity.reset();
+
+ Velocity.addProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH1);
+
+ Velocity.addProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH2);
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testMultipleFileResources ()
+ throws Exception
+ {
+ Template template1 = RuntimeSingleton.getTemplate(
+ getFileName(null, "path1", TMPL_FILE_EXT));
+
+ Template template2 = RuntimeSingleton.getTemplate(
+ getFileName(null, "path2", TMPL_FILE_EXT));
+
+ FileOutputStream fos1 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "path1", RESULT_FILE_EXT));
+
+ FileOutputStream fos2 =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "path2", RESULT_FILE_EXT));
+
+ Writer writer1 = new BufferedWriter(new OutputStreamWriter(fos1));
+ Writer writer2 = new BufferedWriter(new OutputStreamWriter(fos2));
+
+ /*
+ * put the Vector into the context, and merge both
+ */
+
+ VelocityContext context = new VelocityContext();
+
+ template1.merge(context, writer1);
+ writer1.flush();
+ writer1.close();
+
+ template2.merge(context, writer2);
+ writer2.flush();
+ writer2.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "path1",
+ RESULT_FILE_EXT, CMP_FILE_EXT) ||
+ !isMatch(RESULTS_DIR, COMPARE_DIR, "path2",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/NumberMethodCallsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/NumberMethodCallsTestCase.java
new file mode 100644
index 00000000..2a24d39a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/NumberMethodCallsTestCase.java
@@ -0,0 +1,142 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeServices;
+import org.apache.velocity.test.provider.NumberMethods;
+
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+
+/**
+ * Used to check that method calls with number parameters are executed correctly.
+ *
+ * @author <a href="mailto:wglass@forio.com">Peter Romianowski</a>
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ */
+public class NumberMethodCallsTestCase extends TestCase
+{
+ private VelocityEngine ve = null;
+
+ private final static boolean PRINT_RESULTS = false;
+
+ /**
+ * Default constructor.
+ */
+ public NumberMethodCallsTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.init();
+ }
+
+ public void init( RuntimeServices rs )
+ {
+ // do nothing with it
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(NumberMethodCallsTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testNumberMethodCalls ()
+ throws Exception
+ {
+ VelocityContext vc = new VelocityContext();
+
+ // context object with overloaded methods with number arguments
+ vc.put("Test",new NumberMethods());
+
+ // numbers for context
+ vc.put("AByte",new Byte("10"));
+ vc.put("AShort",new Short("10"));
+ vc.put("AInteger", 10);
+ vc.put("ALong", 10L);
+ vc.put("ADouble", 10.0);
+ vc.put("AFloat", 10f);
+ vc.put("ABigDecimal",new BigDecimal(10));
+ vc.put("ABigInteger",new BigInteger("10"));
+
+ // check context objects
+ System.out.println("Testing: method calls with arguments as context objects");
+ checkResults(vc,"$Test.numMethod($AByte)","byte (10)");
+ checkResults(vc,"$Test.numMethod($AShort)","short (10)");
+ checkResults(vc,"$Test.numMethod($AInteger)","int (10)");
+ checkResults(vc,"$Test.numMethod($ADouble)","double (10.0)");
+ checkResults(vc,"$Test.numMethod($AFloat)","double (10.0)");
+ checkResults(vc,"$Test.numMethod($ALong)","long (10)");
+ checkResults(vc,"$Test.numMethod($ABigDecimal)","BigDecimal (10)");
+ checkResults(vc,"$Test.numMethod($ABigInteger)","BigInteger (10)");
+
+ // check literals
+ // -- will cast floating point literal to smallest possible of Double, BigDecimal
+ // -- will cast integer literal to smallest possible of Integer, Long, BigInteger
+ System.out.println("Testing: method calls with arguments as literals");
+ checkResults(vc,"$Test.numMethod(10.0)","double (10.0)");
+ checkResults(vc,"$Test.numMethod(10)","int (10)");
+ checkResults(vc,"$Test.numMethod(10000000000)","long (10000000000)");
+ checkResults(vc,"$Test.numMethod(10000000000000000000000)","BigInteger (10000000000000000000000)");
+
+ // check calculated results
+ // -- note calculated value is cast to smallest possible type
+ // -- method invoked is smallest relevant method
+ // -- it's an unusual case here of both byte and int methods, but this works as expected
+ System.out.println("Testing: method calls with arguments as calculated values");
+ checkResults(vc,"#set($val = 10.0 + 1.5)$Test.numMethod($val)","double (11.5)");
+ checkResults(vc,"#set($val = 100 + 1)$Test.numMethod($val)","int (101)");
+ checkResults(vc,"#set($val = 100 * 1000)$Test.numMethod($val)","int (100000)");
+ checkResults(vc,"#set($val = 100 + 1.5)$Test.numMethod($val)","double (101.5)");
+ checkResults(vc,"#set($val = $ALong + $AInteger)$Test.numMethod($val)","long (20)");
+ checkResults(vc,"#set($val = $ABigInteger + $AInteger)$Test.numMethod($val)","BigInteger (20)");
+ }
+
+
+ private void checkResults(Context vc, String template, String compare) throws Exception
+ {
+
+ StringWriter writer = new StringWriter();
+ ve.evaluate( vc, writer, "test", template);
+ assertEquals("Incorrect results for template '" + template + "'.",compare,writer.toString());
+
+ if (PRINT_RESULTS)
+ System.out.println ("Method call successful: " + template);
+
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/OldPropertiesTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/OldPropertiesTestCase.java
new file mode 100644
index 00000000..46c3b06d
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/OldPropertiesTestCase.java
@@ -0,0 +1,170 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.DeprecationAwareExtProperties;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tests if we can hand Velocity an arbitrary class for logging.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class OldPropertiesTestCase extends TestCase implements TemplateTestBase
+{
+ private VelocityEngine ve = null;
+ private TestLogger logger = null;
+
+ /**
+ * Default constructor.
+ */
+ public OldPropertiesTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(OldPropertiesTestCase.class);
+ }
+
+ static Pattern propPattern = Pattern.compile("^([a-z._]+)\\s*=\\s*[^#]+.*$", Pattern.CASE_INSENSITIVE);
+ static Pattern warnPattern = Pattern.compile("^\\s*\\[warn\\]\\s*configuration key '([a-z._]+)' has been deprecated in favor of '([a-z._]+)'$", Pattern.CASE_INSENSITIVE);
+
+ static class Translator extends DeprecationAwareExtProperties
+ {
+ @Override
+ public String translateKey(String oldName) { return super.translateKey(oldName); }
+ }
+
+ /**
+ * Check old properties setting and retrieval
+ */
+ public void testOldProperties()
+ throws Exception
+ {
+ String oldProperties = TEST_COMPARE_DIR + "/oldproperties/velocity.properties";
+ ve = new VelocityEngine();
+ logger = new TestLogger(false, true);
+ logger.setEnabledLevel(TestLogger.LOG_LEVEL_WARN);
+
+ // put our test logger where it belongs for this test
+ Field loggerField = DeprecationAwareExtProperties.class.getDeclaredField("logger");
+ loggerField.setAccessible(true);
+ loggerField.set(null, logger);
+
+ logger.on();
+ ve.setProperties(oldProperties);
+ logger.off();
+
+ Translator translator = new Translator();
+
+ // check getting old/new values
+ List<String> oldPropSettings = FileUtils.readLines(new File(oldProperties));
+ Set<String> oldKeys = new HashSet<>();
+ for (String oldProp : oldPropSettings)
+ {
+ Matcher matcher = propPattern.matcher(oldProp);
+ if (matcher.matches())
+ {
+ String propName = matcher.group(1);
+ String translated = translator.translateKey(propName);
+ if (!translated.equals(propName))
+ {
+ Object oldKeyValue = ve.getProperty(propName);
+ Object newKeyValue = ve.getProperty(translated);
+ assertEquals(oldKeyValue, newKeyValue);
+ oldKeys.add(propName);
+ }
+ }
+ }
+
+ // check warnings in the logs
+ String log = logger.getLog();
+ String logLines[] = log.split("\\r?\\n");
+ for (String logLine : logLines)
+ {
+ Matcher matcher = warnPattern.matcher(logLine);
+ if (matcher.matches() && matcher.groupCount() == 2)
+ {
+ String oldName = matcher.group(1);
+ assertTrue(oldKeys.remove(oldName));
+ }
+ }
+ if (oldKeys.size() > 0)
+ {
+ fail("No warning detected for the following properties: " + StringUtils.join(oldKeys, ", "));
+ }
+ }
+
+ /**
+ * Check default properties
+ */
+ public void testNewProperties()
+ throws Exception
+ {
+ ve = new VelocityEngine();
+ logger = new TestLogger(false, true);
+ logger.setEnabledLevel(TestLogger.LOG_LEVEL_WARN);
+
+ // put our test logger where it belongs for this test
+ Field loggerField = DeprecationAwareExtProperties.class.getDeclaredField("logger");
+ loggerField.setAccessible(true);
+ loggerField.set(null, logger);
+
+ logger.on();
+ ve.init();
+ logger.off();
+
+ // check warnings in the logs
+ String log = logger.getLog();
+ String logLines[] = log.split("\\r?\\n");
+ for (String logLine : logLines)
+ {
+ Matcher matcher = warnPattern.matcher(logLine);
+ if (matcher.matches() && matcher.groupCount() == 2)
+ {
+ fail("Default properties contain deprecated property '" + matcher.group(1) + "', deprecated in favor of '" + matcher.group(2) + "'");
+ }
+ }
+
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseExceptionTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseExceptionTestCase.java
new file mode 100644
index 00000000..5dd2bd24
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseExceptionTestCase.java
@@ -0,0 +1,179 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Test parser exception is generated with appropriate info.
+ *
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ParseExceptionTestCase extends BaseTestCase
+{
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = "parseexception";
+
+
+ /**
+ * Default constructor.
+ * @param name name of test
+ */
+ public ParseExceptionTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ Velocity.reset();
+ super.setUp();
+ }
+
+ /**
+ * Tests that parseException has useful info when called by template.marge()
+ * @throws Exception
+ */
+ public void testParseExceptionFromTemplate ()
+ throws Exception
+ {
+
+ VelocityEngine ve = new VelocityEngine();
+
+ ve.setProperty("file.resource.loader.cache", "true");
+ ve.setProperty("file.resource.loader.path", TemplateTestBase.TEST_COMPARE_DIR + "/" + FILE_RESOURCE_LOADER_PATH);
+ ve.init();
+
+
+ Writer writer = new StringWriter();
+
+ VelocityContext context = new VelocityContext();
+
+ try
+ {
+ Template template = ve.getTemplate("badtemplate.vm");
+ template.merge(context, writer);
+ fail("Should have thown a ParseErrorException");
+ }
+ catch (ParseErrorException e)
+ {
+ assertEquals("badtemplate.vm",e.getTemplateName());
+ assertEquals(5,e.getLineNumber());
+ assertEquals(9,e.getColumnNumber());
+ }
+ finally
+ {
+ if (writer != null)
+ {
+ writer.close();
+ }
+ }
+ }
+
+ /**
+ * Tests that parseException has useful info when thrown in VelocityEngine.evaluate()
+ * @throws Exception
+ */
+ public void testParseExceptionFromEval ()
+ throws Exception
+ {
+ assertEvalExceptionAt(" #set($abc) ", 1, 13);
+ }
+
+ /**
+ * Tests that parseException has useful info when thrown in VelocityEngine.evaluate()
+ * and the problem comes from a macro definition
+ * @throws Exception
+ */
+ public void testParseExceptionFromMacroDef ()
+ throws Exception
+ {
+ assertEvalExceptionAt("#macro($blarg) foo #end", 1, 7);
+ }
+
+ /**
+ * Tests that parseException has useful info when thrown in VelocityEngine.evaluate()
+ * and the problem comes from a macro definition
+ * @throws Exception
+ */
+ public void testParseExceptionFromMacroDefBody ()
+ throws Exception
+ {
+ assertEvalExceptionAt("#macro(aa $blarg) #set(!! = bb) #end #aa('aa')", 1, 24);
+ }
+
+ /**
+ * Tests that parseException has useful info when thrown in VelocityEngine.evaluate()
+ * and the problem comes from a macro invocation
+ * @throws Exception
+ */
+ public void testParseExceptionFromMacroInvoke ()
+ {
+ assertEvalExceptionAt("#macro( foo $a) $a #end #foo(woogie)", 1, 32);
+ }
+
+
+ /**
+ * Tests that parseException has useful info with macro calls with
+ * invalid number of arguments
+ * @throws Exception
+ */
+ public void testParseExceptionMacroInvalidArgumentCount ()
+ throws Exception
+ {
+ engine.setProperty(RuntimeConstants.VM_ARGUMENTS_STRICT,"true");
+ assertEvalExceptionAt("#macro(foo $a) $a #end #foo('test1' 'test2')", 1, 24);
+ }
+
+
+ /**
+ * Tests that parseException has useful info with macro calls with
+ * invalid number of arguments
+ * @throws Exception
+ */
+ public void testParseExceptionMacroInvalidArgumentCountNoException ()
+ throws Exception
+ {
+ assertEvalEquals("test1", "#macro(foo $a)$a#end#foo('test1' 'test2')");
+ }
+
+ /**
+ * Minus is not any more allowed inside a symbol (reference, property or method).
+ * @throws Exception
+ */
+ public void testParseExceptionMinusSignDissalowed()
+ throws Exception
+ {
+ assertEvalExceptionAt("${foo-bar}", 1, 6);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseWithMacroLibsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseWithMacroLibsTestCase.java
new file mode 100644
index 00000000..1b83d5f4
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ParseWithMacroLibsTestCase.java
@@ -0,0 +1,309 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Test case for including macro libraries via the #parse method.
+ */
+public class ParseWithMacroLibsTestCase extends BaseTestCase
+{
+ private static final String RESULT_DIR = TEST_RESULT_DIR + "/parsemacros";
+
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/parsemacros/compare";
+
+ public ParseWithMacroLibsTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ }
+
+ /**
+ * Test suite
+ * @return test suite
+ */
+ public static junit.framework.Test suite()
+ {
+ return new TestSuite(ParseWithMacroLibsTestCase.class);
+ }
+
+ public void testParseMacroLocalCacheOn()
+ throws Exception
+ {
+ /*
+ * local scope, cache on
+ */
+ VelocityEngine ve = createEngine(true, true);
+
+ // render twice to make sure there is no difference with cached templates
+ testParseMacro(ve, "vm_library1.vm", "parseMacro1_1", false);
+ testParseMacro(ve, "vm_library1.vm", "parseMacro1_1", false);
+
+ // run again with different macro library
+ testParseMacro(ve, "vm_library2.vm", "parseMacro1_1b", false);
+ testParseMacro(ve, "vm_library2.vm", "parseMacro1_1b", false);
+ }
+
+ /**
+ * Runs the tests with global namespace.
+ */
+ public void testParseMacroLocalCacheOff()
+ throws Exception
+ {
+ /*
+ * local scope, cache off
+ */
+ VelocityEngine ve = createEngine(false, true);
+
+ testParseMacro(ve, "vm_library1.vm", "parseMacro1_2", true);
+
+ // run again with different macro library
+ testParseMacro(ve, "vm_library2.vm", "parseMacro1_2b", true);
+ }
+
+ public void testParseMacroGlobalCacheOn()
+ throws Exception
+ {
+ /*
+ * global scope, cache on
+ */
+ VelocityEngine ve = createEngine(true, false);
+
+ // render twice to make sure there is no difference with cached templates
+ testParseMacro(ve, "vm_library1.vm", "parseMacro1_3", false);
+ testParseMacro(ve, "vm_library1.vm", "parseMacro1_3", false);
+
+ // run again with different macro library
+ testParseMacro(ve, "vm_library2.vm", "parseMacro1_3b", false);
+ testParseMacro(ve, "vm_library2.vm", "parseMacro1_3b", false);
+ }
+
+ public void testParseMacroGlobalCacheOff()
+ throws Exception
+ {
+ /*
+ * global scope, cache off
+ */
+ VelocityEngine ve = createEngine(false, false);
+
+ testParseMacro(ve, "vm_library1.vm", "parseMacro1_4", true);
+
+ // run again with different macro library
+ testParseMacro(ve, "vm_library2.vm", "parseMacro1_4b", true);
+
+ }
+
+ /**
+ * Test #parse with macros. Can be used to test different engine configurations
+ * @param ve
+ * @param outputBaseFileName
+ * @param testCachingOff
+ * @throws Exception
+ */
+ private void testParseMacro(VelocityEngine ve, String includeFile, String outputBaseFileName, boolean testCachingOff)
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, outputBaseFileName, RESULT_FILE_EXT));
+
+ VelocityContext context = new VelocityContext();
+ context.put("includefile", includeFile);
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ Template template = ve.getTemplate("parseMacro1.vm");
+ template.merge(context, writer);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, outputBaseFileName,
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ String result = getFileContents(RESULT_DIR, outputBaseFileName, RESULT_FILE_EXT);
+ String compare = getFileContents(COMPARE_DIR, outputBaseFileName, CMP_FILE_EXT);
+
+ String msg = "Processed template did not match expected output\n"+
+ "-----Result-----\n"+ result +
+ "----Expected----\n"+ compare +
+ "----------------";
+
+ fail(msg);
+ }
+
+ /*
+ * Show that caching is turned off
+ */
+ if (testCachingOff)
+ {
+ Template t1 = ve.getTemplate("parseMacro1.vm");
+ Template t2 = ve.getTemplate("parseMacro1.vm");
+
+ assertNotSame("Different objects", t1, t2);
+ }
+ }
+
+ /**
+ * Return and initialize engine
+ * @return
+ */
+ private VelocityEngine createEngine(boolean cache, boolean local)
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve.setProperty("velocimacro.permissions.allow.inline.to.replace.global",
+ local);
+ ve.setProperty("file.resource.loader.cache", cache);
+ ve.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ TEST_COMPARE_DIR + "/parsemacros");
+ ve.init();
+
+ return ve;
+ }
+
+
+ /**
+ * Test whether the literal text is given if a definition cannot be
+ * found for a macro.
+ *
+ * @throws Exception
+ */
+ public void testParseMacrosWithNoDefinition()
+ throws Exception
+ {
+ /*
+ * ve1: local scope, cache on
+ */
+ VelocityEngine ve1 = new VelocityEngine();
+
+ ve1.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve1.setProperty("velocimacro.permissions.allow.inline.to.replace.global",
+ Boolean.FALSE);
+ ve1.setProperty("file.resource.loader.cache", Boolean.TRUE);
+ ve1.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ ve1.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve1.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ TEST_COMPARE_DIR + "/parsemacros");
+ ve1.init();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, "parseMacro2", RESULT_FILE_EXT));
+
+ VelocityContext context = new VelocityContext();
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ Template template = ve1.getTemplate("parseMacro2.vm");
+ template.merge(context, writer);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, "parseMacro2",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template did not match expected output");
+ }
+ }
+
+
+ /**
+ * Test that if a macro is duplicated, the second one takes precendence
+ *
+ * @throws Exception
+ */
+ public void testDuplicateDefinitions()
+ throws Exception
+ {
+ /*
+ * ve1: local scope, cache on
+ */
+ VelocityEngine ve1 = new VelocityEngine();
+
+ ve1.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve1.setProperty("velocimacro.permissions.allow.inline.to.replace.global",
+ Boolean.FALSE);
+ ve1.setProperty("file.resource.loader.cache", Boolean.TRUE);
+ ve1.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ ve1.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve1.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ TEST_COMPARE_DIR + "/parsemacros");
+ ve1.init();
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, "parseMacro3", RESULT_FILE_EXT));
+
+ VelocityContext context = new VelocityContext();
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ Template template = ve1.getTemplate("parseMacro3.vm");
+ template.merge(context, writer);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, "parseMacro3",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template did not match expected output");
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ParserTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ParserTestCase.java
new file mode 100644
index 00000000..f3e4af7b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ParserTestCase.java
@@ -0,0 +1,201 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * More specific parser tests where just templating
+ * isn't enough.
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class ParserTestCase extends TestCase
+{
+ public ParserTestCase(String testName)
+ {
+ super(testName);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(ParserTestCase.class);
+ }
+
+ /**
+ * Test to make sure that using '=' in #if() throws a PEE
+ */
+ public void testEquals()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ TestLogger logger = new TestLogger();
+ ve.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ ve.init();
+
+ /*
+ * this should parse fine -> uses ==
+ */
+
+ String template = "#if($a == $b) foo #end";
+
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+
+ /*
+ * this should throw an exception
+ */
+
+ template = "#if($a = $b) foo #end";
+
+ try
+ {
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+ fail("Could evaluate template with errors!");
+ }
+ catch(ParseErrorException pe)
+ {
+ // Do nothing
+ }
+ }
+
+ /**
+ * Test to see if we force the first arg to #macro() to be a word
+ */
+ public void testMacro()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ TestLogger logger = new TestLogger();
+ ve.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ ve.init();
+
+ /*
+ * this should work
+ */
+
+ String template = "#macro(foo) foo #end";
+
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+
+ /*
+ * this should throw an exception
+ */
+
+ template = "#macro($x) foo #end";
+
+ try
+ {
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+ fail("Could evaluate macro with errors!");
+ }
+ catch(ParseErrorException pe)
+ {
+ // Do nothing
+ }
+ }
+
+ /**
+ * Test to see if don't tolerate passing word tokens in anything but the
+ * 0th arg to #macro() and the 1th arg to foreach()
+ */
+ public void testArgs()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ TestLogger logger = new TestLogger();
+ ve.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ ve.init();
+
+ /*
+ * this should work
+ */
+
+ String template = "#macro(foo) foo #end";
+
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+
+ /*
+ * this should work - spaces intentional
+ */
+
+ template = "#foreach( $i in $woogie ) end #end";
+
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+
+ /*
+ * this should bomb
+ */
+
+ template = "#macro( foo $a) $a #end #foo(woogie)";
+
+ try
+ {
+ ve.evaluate(new VelocityContext(), new StringWriter(), "foo", template);
+ fail("Evaluation of macro with errors succeeded!");
+ }
+ catch(ParseErrorException pe)
+ {
+ // Do nothing
+ }
+ }
+
+ /**
+ * Test to see if we toString is called multiple times on references.
+ */
+ public void testASTReferenceToStringOnlyCalledOnce()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ TestLogger logger = new TestLogger();
+ ve.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ ve.init();
+
+ String template = "$counter";
+
+ ToStringCounter counter = new ToStringCounter();
+ Map m = new HashMap();
+ m.put("counter", counter);
+
+ ve.evaluate(new VelocityContext(m), new StringWriter(), "foo", template);
+
+ assertEquals(1, counter.timesCalled);
+ }
+
+ public static class ToStringCounter {
+ public int timesCalled = 0;
+ public String toString() {
+ this.timesCalled++;
+ return "foo";
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/PropertyMethodPrecedenceTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/PropertyMethodPrecedenceTestCase.java
new file mode 100755
index 00000000..13f2266a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/PropertyMethodPrecedenceTestCase.java
@@ -0,0 +1,103 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+
+/**
+ * Used to check that vararg method calls on references work properly
+ */
+public class PropertyMethodPrecedenceTestCase extends BaseTestCase
+{
+ public PropertyMethodPrecedenceTestCase(final String name)
+ {
+ super(name);
+ // DEBUG = true;
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("geta", new getGetgetisTool());
+ context.put("getA", new GetgetisTool());
+ context.put("geta2", new get2getisTool());
+ context.put("get_a", new getisTool());
+ context.put("isA", new isTool());
+ }
+
+ public void testLowercasePropertyMethods()
+ {
+ assertEvalEquals("getfoo", "$geta.foo");
+ assertEvalEquals("getFoo", "$getA.foo");
+ assertEvalEquals("get(foo)", "$get_a.foo");
+ assertEvalEquals("true", "$isA.foo");
+ }
+
+ public void testUppercasePropertyMethods()
+ {
+ assertEvalEquals("getFoo", "$geta.Foo");
+ assertEvalEquals("getfoo", "$geta2.Foo");
+ assertEvalEquals("getFoo", "$getA.Foo");
+ assertEvalEquals("get(Foo)", "$get_a.Foo");
+ assertEvalEquals("true", "$isA.Foo");
+ }
+
+
+ public static class isTool
+ {
+ public boolean isFoo()
+ {
+ return true;
+ }
+ }
+
+ public static class getisTool extends isTool
+ {
+ public String get(String s)
+ {
+ return "get("+s+")";
+ }
+ }
+
+ public static class GetgetisTool extends getisTool
+ {
+ public String getFoo()
+ {
+ return "getFoo";
+ }
+ }
+
+ public static class getGetgetisTool extends GetgetisTool
+ {
+ public String getfoo()
+ {
+ return "getfoo";
+ }
+ }
+
+ public static class get2getisTool extends getisTool
+ {
+ public String getfoo()
+ {
+ return "getfoo";
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/RenderVelocityTemplateTest.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/RenderVelocityTemplateTest.java
new file mode 100644
index 00000000..74288393
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/RenderVelocityTemplateTest.java
@@ -0,0 +1,155 @@
+package org.apache.velocity.test;
+
+import junit.framework.Assert;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+public class RenderVelocityTemplateTest
+{
+
+ static class RenderVelocityTemplate
+ {
+ static
+ {
+ try
+ {
+ Properties p = new Properties();
+ p.put("velocimacro.permissions.allow.inline.local.scope", "true");
+ Velocity.init(p);
+ }
+ catch (Exception e)
+ {
+ throw new AssertionError("Failed to init Velocity");
+ }
+ }
+
+ private final VelocityContext velocityContext = new VelocityContext();
+
+ private final String template;
+
+ public RenderVelocityTemplate(String template)
+ {
+ this.template = template;
+ }
+
+ public String getContent()
+ throws Exception
+ {
+ StringWriter stringWriter = new StringWriter();
+ Velocity.evaluate(velocityContext, stringWriter, "", template);
+ return stringWriter.toString();
+ }
+ }
+
+
+ private static final String templateString = "" + //
+ "#macro (showhelloworld $foo)\n" + //
+ "Hello, World\n" + //
+ "#end\n" + //
+ "\n" + //
+ "<html>\n" + //
+ "<head>\n" + //
+ "<title>page title</title>\n" + //
+ "</head>\n" + //
+ "<body>\n" + //
+ "<p>This is a test</p>\n" + //
+ "<p>#showhelloworld ($foo)</p>\n" + //
+ "</body>\n" + //
+ "</html>";
+
+ public void testMultipleEvals()
+ throws Exception
+ {
+ RenderVelocityTemplate template = new RenderVelocityTemplate(templateString);
+
+ String result = null;
+ for (int i = 0; i < 1000; ++i)
+ {
+ result = template.getContent();
+
+ // Verify that the original macro invocation has been replaced with its result.
+ int index = result.indexOf("#showhelloworld");
+ if (index != -1)
+ {
+ throw new AssertionError("Failed to substitute macro:\n" + result);
+ }
+
+ // Verify that the macro did indeed expand.
+ int indexHW = result.indexOf("<p>Hello, World");
+ Assert.assertTrue(indexHW >= 0);
+
+ // Assert.assertEquals("", result); // enable to show what we really get
+ }
+ }
+
+ /** Helper class for testMultiThreadMultipleEvals(). */
+ static class ExceptionHandler
+ implements Thread.UncaughtExceptionHandler
+ {
+ List<Throwable> errors = new ArrayList<>();
+
+ @Override
+ public void uncaughtException(Thread t, Throwable e)
+ {
+ errors.add(e);
+ }
+ }
+
+ /** Helper class for testMultiThreadMultipleEvals(). */
+ class RunMultipleEvals
+ extends Thread
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ testMultipleEvals();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /**
+ * Spawn multiple threads that concurrently call testMultipleEvals.
+ */
+ public void testMultiThreadMultipleEvals()
+ throws Throwable
+ {
+ int nthreads = 4;
+ ExceptionHandler eh = new ExceptionHandler();
+
+ List<Thread> threads = new ArrayList<>(nthreads);
+ for (int i = 0; i < nthreads; ++i)
+ {
+ Thread t = new RunMultipleEvals();
+ t.setUncaughtExceptionHandler(eh);
+ threads.add(t);
+ }
+
+ for (Thread t : threads)
+ {
+ t.start();
+ }
+
+ for (Thread t : threads)
+ {
+ t.join();
+ }
+
+ if (eh.errors.size() > 0)
+ {
+ // Rethrow the first failing exception.
+ System.out.println("Failed " + eh.errors.size() + " out of " + nthreads + " template evaluations");
+ throw eh.errors.get(0);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceCachingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceCachingTestCase.java
new file mode 100644
index 00000000..4ca9a5a6
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceCachingTestCase.java
@@ -0,0 +1,94 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Test resource caching related issues.
+ *
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ResourceCachingTestCase extends BaseTestCase
+{
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = "/resourcecaching";
+
+
+ /**
+ * Default constructor.
+ */
+ public ResourceCachingTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(ResourceCachingTestCase.class);
+ }
+
+ /**
+ * Tests for fix of bug VELOCITY-98 where a #include followed by #parse
+ * of the same file throws ClassCastException when caching is on.
+ * @throws Exception
+ */
+ public void testIncludeParseCaching ()
+ throws Exception
+ {
+
+ VelocityEngine ve = new VelocityEngine();
+
+ ve.setProperty("file.resource.loader.cache", "true");
+ ve.setProperty("file.resource.loader.path", TemplateTestBase.TEST_COMPARE_DIR + FILE_RESOURCE_LOADER_PATH);
+ ve.init();
+
+ Template template = ve.getTemplate("testincludeparse.vm");
+
+ Writer writer = new StringWriter();
+
+ VelocityContext context = new VelocityContext();
+
+ // will produce a ClassCastException if Velocity-98 is not solved
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceExistsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceExistsTestCase.java
new file mode 100755
index 00000000..0c50219d
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceExistsTestCase.java
@@ -0,0 +1,105 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
+
+/**
+ * Test the resource exists method
+ *
+ * @version $Id: ResourceExistsTestCase.java 687191 2008-08-19 23:02:41Z nbubna $
+ */
+public class ResourceExistsTestCase extends BaseTestCase
+{
+ private VelocityEngine velocity;
+ private String path = TEST_COMPARE_DIR + "/resourceexists";
+ private TestLogger logger = new TestLogger();
+
+ public ResourceExistsTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+try {
+ velocity = new VelocityEngine();
+ velocity.setProperty("resource.loader", "file,string");
+ velocity.setProperty("file.resource.loader.path", path);
+ velocity.setProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+
+ // actual instance of logger
+ logger.on();
+ velocity.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ velocity.setProperty("runtime.log.logsystem.test.level", "debug");
+} catch (Exception e) {
+ System.out.println("exception via gump: "+e);
+ e.printStackTrace();
+ System.out.println("log: "+logger.getLog());
+}
+ }
+
+ public void testFileResourceExists() throws Exception
+ {
+try {
+ if (!velocity.resourceExists("testfile.vm"))
+ {
+ String msg = "testfile.vm was not found in path "+path;
+ System.out.println(msg);
+ System.out.println("Log was: "+logger.getLog());
+ path = path+"/testfile.vm";
+ java.io.File file = new java.io.File(path);
+ if (file.exists()) {
+ System.out.println("file system found "+path);
+ } else {
+ System.out.println(file+" could not be found as a file");
+ }
+ fail(msg);
+ }
+ if (velocity.resourceExists("nosuchfile.vm"))
+ {
+ String msg = "nosuchfile.vm should not have been found in path "+path;
+ System.out.println(msg);
+ fail(msg);
+ }
+} catch (Exception e) {
+ System.out.println("exception via gump: "+e);
+ e.printStackTrace();
+ System.out.println("log: "+logger.getLog());
+}
+ }
+
+ public void testStringResourceExists() throws Exception
+ {
+try {
+ assertFalse(velocity.resourceExists("foo.vm"));
+ StringResourceLoader.getRepository().putStringResource("foo.vm", "Make it so!");
+ assertTrue(velocity.resourceExists("foo.vm"));
+} catch (Exception e) {
+ System.out.println("exception via gump: "+e);
+ e.printStackTrace();
+ System.out.println("log: "+logger.getLog());
+}
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceLoaderInstanceTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceLoaderInstanceTestCase.java
new file mode 100644
index 00000000..e6217b0c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ResourceLoaderInstanceTestCase.java
@@ -0,0 +1,154 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Test that an instance of a ResourceLoader can be successfully passed in.
+ *
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ResourceLoaderInstanceTestCase extends BaseTestCase
+{
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/resourceinstance";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/resourceinstance";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/resourceinstance/compare";
+
+ private TestLogger logger = new TestLogger();
+
+ /**
+ * Default constructor.
+ */
+ public ResourceLoaderInstanceTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+
+ ResourceLoader rl = new FileResourceLoader();
+
+ // pass in an instance to Velocity
+ Velocity.reset();
+ Velocity.setProperty( "resource.loader", "testrl" );
+ Velocity.setProperty( "testrl.resource.loader.instance", rl );
+ Velocity.setProperty( "testrl.resource.loader.path", FILE_RESOURCE_LOADER_PATH );
+
+ // actual instance of logger
+ logger.on();
+ Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ Velocity.setProperty("runtime.log.logsystem.test.level", "debug");
+
+ Velocity.init();
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(ResourceLoaderInstanceTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testResourceLoaderInstance ()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Template template = RuntimeSingleton.getTemplate(
+ getFileName(null, "testfile", TMPL_FILE_EXT));
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "testfile", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ /*
+ * put the Vector into the context, and merge both
+ */
+
+ VelocityContext context = new VelocityContext();
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if ( !isMatch(RESULTS_DIR, COMPARE_DIR, "testfile",
+ RESULT_FILE_EXT, CMP_FILE_EXT) )
+ {
+ String result = getFileContents(RESULT_DIR, "testfile", RESULT_FILE_EXT);
+ String compare = getFileContents(COMPARE_DIR, "testfile", CMP_FILE_EXT);
+
+ String msg = "Processed template did not match expected output\n"+
+ "-----Result-----\n"+ result +
+ "----Expected----\n"+ compare +
+ "----------------";
+
+ fail(msg);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java
new file mode 100755
index 00000000..21a6490d
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/ScopeTestCase.java
@@ -0,0 +1,369 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import java.util.HashMap;
+
+/**
+ * This class tests the directive scope controls
+ */
+public class ScopeTestCase extends BaseTestCase
+{
+ public ScopeTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty("a.provide.scope.control", "true");
+ engine.setProperty("define.provide.scope.control", "true");
+ engine.setProperty("evaluate.provide.scope.control", "true");
+ engine.setProperty("foo.provide.scope.control", "true");
+ engine.setProperty("macro.provide.scope.control", "true");
+ engine.setProperty("template.provide.scope.control", "true");
+ engine.setProperty("vm.provide.scope.control", "true");
+ engine.setProperty("space.gobbling", "bc");
+ }
+
+ public void testScopeGetLeakIntoInner()
+ {
+ addTemplate("foo", "#foreach($i in [1..1])#set($foreach.a=$i)"+
+ "#foreach($j in [2..2])$foreach.a#set($foreach.a=$j)"+
+ "#foreach($k in [3..3])$foreach.a#end#end$foreach.a#end");
+ assertTmplEquals("121", "foo");
+ }
+
+ public void testScopeGetLeakDoesntHideNullset()
+ {
+ addTemplate("a", "#macro(a)#set($macro.a='a')#b()$macro.a#end"+
+ "#macro(b)$macro.a#set($macro.a=$null)$!macro.a#end"+
+ "#a()");
+ assertTmplEquals("aa", "a");
+ }
+
+ public void testRootTemplateMergeScope()
+ {
+ addTemplate("foo", "foo#break($template)bar");
+ assertTmplEquals("foo", "foo");
+ assertNull(context.get("template"));
+ }
+
+ public void testParseScope()
+ {
+ addTemplate("test", "$template.info.depth"+
+ "$!parse.parent.info.depth"+
+ "#set( $template.foo = 'bar' )"+
+ "$template.foo"+
+ "#break($template)"+
+ "woogie");
+ assertEvalEquals("1bar", "#parse( 'test' )");
+ assertNull(context.get("template"));
+ }
+
+ public void testNestedParseScope()
+ {
+ HashMap grab = new HashMap();
+ context.put("grab", grab);
+
+ addTemplate("inner", "Inner depth: $template.info.depth"+
+ "#set( $template.foo = '?' )"+
+ "$!grab.put('inner',$template)"+
+ "#break($template)$template.foo");
+ addTemplate("outer", "#set( $template.foo = '!' )"+
+ "Outer depth: $template.info.depth "+
+ "#parse('inner')"+
+ "$!grab.put('outer', $template)"+
+ "$template.foo");
+ assertEvalEquals("Outer depth: 1 Inner depth: 2!", "#parse('outer')");
+ // make extra sure that the outer control was restored after the stop
+ assertFalse(grab.get("inner") == grab.get("outer"));
+ // make sure the outer control was cleaned up
+ assertNull(context.get("template"));
+
+ addTemplate("3", "$template.topmost.foo#set( $template.topmost.foo = 'bar' )");
+ addTemplate("2", "#parse( '3' )$!parse.foo");
+ addTemplate("1", "#set( $template.foo = 'foo' )#parse('2')$template.foo");
+ assertEvalEquals("foobar", "#parse('1')$!parse");
+ // make sure the top control was cleaned up
+ assertNull(context.get("template"));
+ }
+
+ public void testForeachScope()
+ {
+ String template = "#foreach( $i in [0..2] )"+
+ "#if( $i > 1 )#break($foreach)#end"+
+ "$foreach.index:$foreach.count:$foreach.hasNext,"+
+ "#end";
+ assertEvalEquals("0:1:true,1:2:true,", template);
+ assertNull(context.get("foreach"));
+ }
+
+ public void testNestedForeachScope()
+ {
+ String template = "#foreach( $i in [1..5] )"+
+ "#foreach( $j in [1..2] )"+
+ "#if ( $i > $foreach.count + $foreach.index + $foreach.info.depth )#break($foreach.topmost)#end"+
+ "#end"+
+ "$i"+
+ "#end";
+ assertEvalEquals("123", template);
+ assertNull(context.get("foreach"));
+ }
+
+ public void testMacroScope()
+ {
+ String template = "#macro( foo $i )"+
+ "#if($i > 2 )#break($macro)#end"+
+ "$i#end"+
+ "#foo( 0 )#foo( 1 )#foo( 2 )";
+ assertEvalEquals("012", template);
+ assertNull(context.get("macro"));
+ }
+
+ public void testRecursiveMacroScope()
+ {
+ String template = "#macro( foo )$macro.info.depth"+
+ "#if($macro.info.depth > 2 )#break($macro.topmost)#end"+
+ "#foo()#end#foo()";
+ assertEvalEquals("123", template);
+ assertNull(context.get("macro"));
+ }
+
+ public void testNestedMacroScope()
+ {
+ String template = "#macro( a )$macro.info.depth#set($macro.c = 'a')$macro.c#end"+
+ "#macro( b )#set($macro.c = 'b' )#a()$macro.c#end"+
+ "#b()";
+ assertEvalEquals("2ab", template);
+ assertNull(context.get("macro"));
+ }
+
+ public void testBodyMacroScope()
+ {
+ String template = "#macro( foo $bar )$bodyContent$macro.bar#end"+
+ "#@foo( 'bar' )#set( $macro.bar = 'foo'+$bar )"+
+ "#set( $foo.d = $foo.info.depth )$foo.d #end";
+ assertEvalEquals("1 foobar", template);
+ assertNull(context.get("foo"));
+ assertNull(context.get("macro"));
+ }
+
+ public void testRecursiveBodyMacroScope()
+ {
+ engine.setProperty(RuntimeConstants.VM_MAX_DEPTH, "5");
+ String template = "#macro( foo )$bodyContent$macro.i#end"+
+ "#@foo()#set( $macro.i = \"$!macro.i$foo.info.depth,\" )"+
+ "$!bodyContent#end";
+ assertEvalEquals("1,2,3,4,5,", template);
+ assertNull(context.get("foo"));
+ assertNull(context.get("macro"));
+ }
+
+ public void testDefineScope()
+ {
+ String template = "#define( $foo )#set( $define.bar = 'bar'+$define.info.depth )$define.bar#end$foo";
+ assertEvalEquals("bar1", template);
+ assertNull(context.get("define"));
+ }
+
+ public void testNestedDefineScope()
+ {
+ String template = "#define($a)$b c#end"+
+ "#define($b)$define.info.depth#break($define.topmost)#end"+
+ "$a";
+ assertEvalEquals("2", template);
+ assertNull(context.get("define"));
+ }
+
+ public void testRecursiveDefineScope()
+ {
+ engine.setProperty(RuntimeConstants.DEFINE_DIRECTIVE_MAXDEPTH, "10");
+ String template = "#define($a)$define.info.depth"+
+ "#if($define.info.depth == 5)#break($define)#end,$a#end$a";
+ assertEvalEquals("1,2,3,4,5", template);
+ assertNull(context.get("define"));
+ }
+
+ public void testRootEvaluateScope()
+ {
+ assertEvalEquals("1", "$evaluate.info.depth");
+ assertEvalEquals("foo", "foo#break($evaluate)bar");
+ assertNull(context.get("evaluate"));
+ }
+
+ public void testEvaluateScope()
+ {
+ context.put("h", "#");
+ context.put("d", "$");
+ String template = "${h}set( ${d}evaluate.foo = 'bar' )"+
+ "${d}evaluate.foo ${d}evaluate.info.depth";
+ addTemplate("eval", "#evaluate(\""+template+"\")");
+ assertTmplEquals("bar 1", "eval");
+ assertNull(context.get("evaluate"));
+ assertNull(context.get("template"));
+ }
+
+ public void testNestedEvaluateScope()
+ {
+ context.put("h", "#");
+ context.put("d", "$");
+ addTemplate("e", "#evaluate(\"${h}evaluate( '${d}evaluate.info.depth${h}stop(${d}evaluate) blah' )\")");
+ assertTmplEquals("2", "e");
+ assertNull(context.get("evaluate"));
+ assertNull(context.get("template"));
+ }
+
+ public void testTurningOffTemplateScope()
+ {
+ engine.setProperty("template."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
+ // root
+ addTemplate("test", "$template.info.depth");
+ assertTmplEquals("$template.info.depth", "test");
+ // #parse
+ assertEvalEquals("$template.info.depth", "#parse('test')");
+ }
+
+ public void testTurningOffEvaluateScope()
+ {
+ engine.setProperty("evaluate."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
+ // root
+ assertSchmoo("$evaluate.info.depth");
+ // #evaluate
+ assertEvalEquals("$evaluate.info.depth", "#evaluate( '$evaluate.info.depth' )");
+ }
+
+ public void testTurningOffMacroScope()
+ {
+ engine.setProperty("macro."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
+ engine.setProperty("foo."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
+ // macro definition
+ assertEvalEquals("$macro", "#macro(a)$macro#end#a()");
+ // macro body
+ assertEvalEquals("$macro $foo", "#macro(foo)$bodyContent#end#@foo()$macro $foo#end");
+ }
+
+ public void testTurningOffDefineScope()
+ {
+ engine.setProperty("define."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
+ assertEvalEquals("$define", "#define($a)$define#end$a");
+ }
+
+ public void testTurningOffForeachScope()
+ {
+ engine.setProperty("foreach."+RuntimeConstants.PROVIDE_SCOPE_CONTROL, "false");
+ assertEvalEquals("$foreach$foreach", "#foreach($i in [0..1])$foreach#end");
+ }
+
+ public void testTemplateReplaced()
+ {
+ context.put("template", "foo");
+ addTemplate("test", "$template.replaced");
+ assertTmplEquals("foo", "test");
+ assertEvalEquals("foo", "#parse('test')");
+ assertContextValue("template", "foo");
+ }
+
+ public void testEvaluateReplaced()
+ {
+ context.put("evaluate","foo");
+ assertEvalEquals("foo", "$evaluate.replaced");
+ assertEvalEquals("foo", "#evaluate('$evaluate.replaced')");
+ assertContextValue("evaluate", "foo");
+ }
+
+ public void testMacroReplaced()
+ {
+ context.put("macro", "foo");
+ assertEvalEquals("foo foo foo", "$macro #macro(a)$macro.replaced#end#a() $macro");
+ assertContextValue("macro", "foo");
+ }
+
+ public void testForeachReplaced()
+ {
+ context.put("foreach", "foo");
+ assertEvalEquals("foofoofoo", "$foreach#foreach($i in [1..1])$foreach.replaced#end$foreach");
+ assertEquals("foo", context.get("foreach"));
+ context.put("foreach", "a");
+ assertEvalEquals("a", "#foreach($i in [1..1])#foreach($j in [1..1])$foreach.replaced#end#end");
+ assertContextValue("foreach", "a");
+ }
+
+ public void testDefineReplaced()
+ {
+ context.put("define", "a");
+ assertEvalEquals("a", "#define($a)$define.replaced#end$a");
+ assertContextValue("define", "a");
+ }
+
+ public void testBodyContentReplaced()
+ {
+ context.put("vm", "a");
+ assertEvalEquals("a", "#macro(vm)$bodyContent#end#@vm()$vm.replaced#end");
+ assertContextValue("vm", "a");
+ }
+
+ public void testInfoDepth()
+ {
+ String template = "#foreach($i in [1..1])"+
+ "#foreach($j in [0..0])"+
+ "$foreach.info.depth"+
+ "#end"+
+ "#end";
+ assertEvalEquals("2", template);
+ }
+
+ public void testInfoName()
+ {
+ String template = "#foreach($i in [1..1])"+
+ "$foreach.info.name #evaluate('$evaluate.info.name')"+
+ "#end";
+ assertEvalEquals("foreach evaluate", template);
+ }
+
+ public void testInfoType()
+ {
+ addTemplate("info", "#foreach($i in [1..1])"+
+ "$foreach.info.type"+
+ "#end "+
+ "#evaluate('$evaluate.info.type') "+
+ "$template.info.type");
+ assertTmplEquals("block line utf-8", "info");
+ }
+
+ public void testInfoLineAndColumn()
+ {
+ String template = " #evaluate('$evaluate.info.line, $evaluate.info.column')";
+ assertEvalEquals(" 1, 2", template);
+ assertEvalEquals("\n\n 3, 4", "\n\n "+template);
+ }
+
+ public void testInfoTemplate()
+ {
+ addTemplate("test", "#evaluate('$evaluate.info.template')");
+ assertTmplEquals("test", "test");
+ assertEvalEquals("test", "#parse('test')");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/SecureIntrospectionTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/SecureIntrospectionTestCase.java
new file mode 100644
index 00000000..f504029f
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/SecureIntrospectionTestCase.java
@@ -0,0 +1,179 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+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;
+import org.apache.velocity.util.introspection.SecureUberspector;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * Checks that the secure introspector is working properly.
+ *
+ * @author <a href="Will Glass-Husain">wglass@forio.com</a>
+ * @version $Id$
+ */
+public class SecureIntrospectionTestCase extends BaseTestCase
+{
+
+ /**
+ * Default constructor.
+ * @param name
+ */
+ public SecureIntrospectionTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(SecureIntrospectionTestCase.class);
+ }
+
+
+ private String [] badTemplateStrings =
+ {
+ "$test.Class.Methods",
+ "$test.Class.ClassLoader",
+ "$test.Class.ClassLoader.loadClass('java.util.HashMap').newInstance().size()"
+ };
+
+ private String [] goodTemplateStrings =
+ {
+ "#foreach($item in $test.collection)$item#end",
+ "$test.Class.Name",
+ "#set($test.Property = 'abc')$test.Property",
+ "$test.aTestMethod()"
+ };
+
+ /**
+ * Test to see that "dangerous" methods are forbidden
+ * @exception Exception
+ */
+ public void testBadMethodCalls()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.UBERSPECT_CLASSNAME, SecureUberspector.class.getName());
+ ve.init();
+
+ /*
+ * all of the following method calls should not work
+ */
+ doTestMethods(ve, badTemplateStrings, false);
+ }
+
+ /**
+ * Test to see that "dangerous" methods are forbidden
+ * @exception Exception
+ */
+ public void testGoodMethodCalls()
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(RuntimeConstants.UBERSPECT_CLASSNAME, SecureUberspector.class.getName());
+ ve.init();
+
+ /*
+ * all of the following method calls should not work
+ */
+ doTestMethods(ve, goodTemplateStrings, true);
+ }
+
+ private void doTestMethods(VelocityEngine ve, String[] templateStrings, boolean shouldeval)
+ {
+ Context c = new VelocityContext();
+ c.put("test", this);
+
+ try
+ {
+ for (String templateString : templateStrings)
+ {
+ if (shouldeval && !doesStringEvaluate(ve, c, templateString))
+ {
+ fail("Should have evaluated: " + templateString);
+ }
+
+ if (!shouldeval && doesStringEvaluate(ve, c, templateString))
+ {
+ fail("Should not have evaluated: " + templateString);
+ }
+ }
+
+ }
+ catch (Exception e)
+ {
+ fail(e.toString());
+ }
+ }
+
+ private boolean doesStringEvaluate(VelocityEngine ve, Context c, String inputString) throws ParseErrorException, MethodInvocationException, ResourceNotFoundException, IOException
+ {
+ // assume that an evaluation is bad if the input and result are the same (e.g. a bad reference)
+ // or the result is an empty string (e.g. bad #foreach)
+ Writer w = new StringWriter();
+ ve.evaluate(c, w, "foo", inputString);
+ String result = w.toString();
+ return (result.length() > 0 ) && !result.equals(inputString);
+ }
+
+ private String testProperty;
+ public String getProperty()
+ {
+ return testProperty;
+ }
+
+ public int aTestMethod()
+ {
+ return 1;
+ }
+
+ public void setProperty(String val)
+ {
+ testProperty = val;
+ }
+
+
+ public Collection getCollection()
+ {
+ Collection c = new HashSet();
+ c.add("aaa");
+ c.add("bbb");
+ c.add("ccc");
+ return c;
+ }
+}
+
+
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/SetTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/SetTestCase.java
new file mode 100644
index 00000000..e6794800
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/SetTestCase.java
@@ -0,0 +1,144 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.RuntimeConstants;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Test that an instance of a ResourceLoader can be successfully passed in.
+ *
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class SetTestCase extends BaseTestCase
+{
+ /**
+ * VTL file extension.
+ */
+ private static final String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ private final static String FILE_RESOURCE_LOADER_PATH = TEST_COMPARE_DIR + "/set";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/set";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/set/compare";
+
+ /**
+ * Default constructor.
+ */
+ public SetTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.addProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ assureResultsDirectoryExists(RESULTS_DIR);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testSetNull()
+ throws Exception
+ {
+ /*
+ * Check that #set does accept nulls
+ */
+ checkTemplate("set1");
+
+ /*
+ * Check that #set can accept nulls, and has the correct behaviour for complex LHS
+ */
+ checkTemplate("set2");
+ }
+
+ public void checkTemplate(String templateName) throws Exception
+ {
+ Template template;
+ FileOutputStream fos;
+ Writer fwriter;
+
+ template = engine.getTemplate(getFileName(null, templateName, TMPL_FILE_EXT));
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, templateName, RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, templateName, RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ }
+
+ public void testInvalidSet() throws Exception
+ {
+ /* the purpose of this test is to check that in case of error, the calculation of the
+ literal representation of the expression, which is displayed in the logs, does not raise a null exception
+ */
+ assertEvalEquals("", "#set($c = $a - $b - $c)");
+ assertEvalEquals("", "#set($c = $a + $b + $c)");
+ assertEvalEquals("", "#set($c = $a * $b * $c)");
+ assertEvalEquals("", "#set($c = $a / $b / $c)");
+ assertEvalEquals("", "#set($c = $a % $b % $c)");
+ assertEvalEquals("", "#set($c = $a && $b && $c)");
+ assertEvalEquals("", "#set($c = $a || $b || $c)");
+ assertEvalEquals("", "#set($c = $a + $b + !$c)");
+ assertEvalEquals("", "#set($c = $a + $b + (-$c))");
+ assertEvalEquals("", "#set($c = $a && ($b < $c))");
+ assertEvalEquals("", "#set($c = !$a)");
+ assertEvalEquals("", "#set($c = ($a < $b) - ($c < $d))");
+ assertEvalEquals("","#set($b = !$a)");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/SpaceGobblingTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/SpaceGobblingTestCase.java
new file mode 100644
index 00000000..e8d22bdf
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/SpaceGobblingTestCase.java
@@ -0,0 +1,143 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeConstants.SpaceGobbling;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Test case for conversion handler
+ */
+public class SpaceGobblingTestCase extends BaseTestCase
+{
+ private static final String RESULT_DIR = TEST_RESULT_DIR + "/gobbling";
+
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/gobbling/compare";
+
+ public SpaceGobblingTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ }
+
+ /**
+ * Test suite
+ * @return test suite
+ */
+ public static junit.framework.Test suite()
+ {
+ return new TestSuite(SpaceGobblingTestCase.class);
+ }
+
+ /**
+ * Return and initialize engine
+ * @return
+ */
+ private VelocityEngine createEngine(SpaceGobbling mode)
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty(Velocity.RUNTIME_LOG_INSTANCE, log);
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TEST_COMPARE_DIR + "/gobbling");
+ ve.setProperty(RuntimeConstants.SPACE_GOBBLING, mode.toString());
+ ve.init();
+
+ return ve;
+ }
+
+ public void testSpaceGobbling() throws Exception
+ {
+ for (SpaceGobbling mode : SpaceGobbling.values())
+ {
+ testMode(mode);
+ }
+ }
+
+ private void testMode(SpaceGobbling mode) throws Exception
+ {
+ File dir = new File(TEST_COMPARE_DIR + "/gobbling");
+ File[] directoryListing = dir.listFiles();
+ if (directoryListing != null)
+ {
+ for (File child : directoryListing)
+ {
+ if (child.isFile())
+ {
+ testTemplate(child.getName(), mode);
+ }
+ }
+ }
+ else
+ {
+ throw new Exception("cannot read input templates");
+ }
+ }
+
+ private void testTemplate(String templateFile, SpaceGobbling mode) throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+ FileOutputStream fos = new FileOutputStream (getFileName(RESULT_DIR, templateFile, mode.toString()));
+ VelocityContext context = new VelocityContext();
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityEngine ve = createEngine(mode);
+ Template template = ve.getTemplate(templateFile);
+ template.merge(context, writer);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, templateFile, mode.toString(), mode.toString()))
+ {
+ String result = getFileContents(RESULT_DIR, templateFile, mode.toString());
+ String compare = getFileContents(COMPARE_DIR, templateFile, mode.toString());
+
+ String msg = "Processed template did not match expected output for template " + templateFile + " and mode " + mode + "\n"+
+ "-----Result-----\n"+ result +
+ "----Expected----\n"+ compare +
+ "----------------";
+
+ fail(msg);
+ }
+
+ }
+}
+
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StaticUtilityMethodsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StaticUtilityMethodsTestCase.java
new file mode 100755
index 00000000..60cabbf6
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StaticUtilityMethodsTestCase.java
@@ -0,0 +1,57 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 tests support for putting static utility classes
+ * like java.lang.Math directly into the context in order to
+ * use their methods.
+ */
+public class StaticUtilityMethodsTestCase extends BaseTestCase
+{
+ public StaticUtilityMethodsTestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testMath()
+ {
+ context.put("Math", Math.class);
+ assertEvalEquals("java.lang.Math", "$Math.name");
+ assertEvalEquals("3.0", "$Math.ceil(2.5)");
+ }
+
+ public void testFoo()
+ {
+ context.put("Foo", Foo.class);
+ assertEvalEquals("test", "$Foo.foo('test')");
+ }
+
+
+ public static class Foo
+ {
+ private Foo() {}
+ public static String foo(String s)
+ {
+ return s == null ? "foo" : s;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StopDirectiveTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StopDirectiveTestCase.java
new file mode 100644
index 00000000..f09a53d0
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StopDirectiveTestCase.java
@@ -0,0 +1,85 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.test.misc.TestLogger;
+
+/**
+ * Test the #stop directive
+ */
+public class StopDirectiveTestCase extends BaseTestCase
+{
+ public StopDirectiveTestCase(String name)
+ {
+ super(name);
+ //DEBUG=true;
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TemplateTestBase.TEST_COMPARE_DIR + "/stop");
+ engine.setProperty(RuntimeConstants.VM_LIBRARY, "vmlib1.vm");
+ }
+
+ public void testStop()
+ {
+ // Make it work through the evaluate method call
+ assertEvalEquals("Text1", "Text1#{stop}Text2");
+ // Make sure stop works in a template
+ assertTmplEquals("Text 1", "stop1.vm");
+ // Make sure stop works when called from a velocity macro
+ assertTmplEquals("Text123stuff1", "stop2.vm");
+ // Make sure stop works when called located in another parsed file
+ assertTmplEquals("text1blaa1", "stop3.vm");
+ }
+
+ public void testNestedStopAll()
+ {
+ addTemplate("ns", ",template"+
+ "#macro(vm),macro${bodyContent}macro#end"+
+ "#define($define),define"+
+ "#foreach($i in [1..2]),foreach"+
+ "#{stop}foreach"+
+ "#{end}define"+
+ "#{end}"+
+ "#@vm(),bodyContent"+
+ "${define}bodyContent"+
+ "#{end}template");
+ String expected = "evaluate,template,macro,bodyContent,define,foreach";
+ assertEvalEquals(expected, "#evaluate('evaluate#parse(\"ns\")evaluate')");
+ }
+
+ public void testStopMessage()
+ {
+ log.setEnabledLevel(TestLogger.LOG_LEVEL_DEBUG);
+ context.put("log", log);
+
+ assertEvalEquals("a", "a$!log.startCapture()#stop('woogie!')b");
+
+ log.stopCapture();
+ log.on();
+ info("Log: "+log.getLog());
+ assertTrue(log.getLog().contains("StopCommand: woogie!"));
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictAlternateValuesTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictAlternateValuesTestCase.java
new file mode 100644
index 00000000..36fe903b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictAlternateValuesTestCase.java
@@ -0,0 +1,76 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+/**
+ * Base test case that provides utility methods for
+ * the rest of the tests.
+ *
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author Nathan Bubna
+ * @version $Id: AlternateValuesTestCase.java 1843764 2018-10-13 14:52:28Z cbrisson $
+ */
+public class StrictAlternateValuesTestCase extends BaseTestCase
+{
+ public StrictAlternateValuesTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("foo", null);
+ }
+
+ public void testDefault()
+ {
+ assertEvalEquals("<foo>", "<${foo|'foo'}>");
+ assertEvalEquals("bar", "#set($bar='bar')${foo|$bar}");
+ assertEvalEquals("bar", "#set($bar='bar')${foo|${bar}}");
+ assertEvalException("${foo.bar.baz()[5]|'hop'}", VelocityException.class);
+ assertEvalEquals("{foo}", "{${foo|'foo'}}");
+ assertEvalException("$foo", VelocityException.class);
+ }
+
+ public void testComplexEval()
+ {
+ assertEvalException("<${date.format('medium', $date.date)|'no date tool'}>", VelocityException.class);
+ assertEvalEquals("true", "#set($val=false)${val.toString().replace(\"false\", \"true\")|'so what'}");
+ assertEvalEquals("so what", "#set($foo='foo')${foo.contains('bar')|'so what'}");
+ assertEvalEquals("so what", "#set($val=false)${val.toString().contains('bar')|'so what'}");
+ assertEvalEquals("true", "#set($val=false)${val.toString().contains('false')|'so what'}");
+ assertEvalException("$!{null|$null}", VelocityException.class);
+ assertEvalEquals("null", "$!{null|'null'}");
+ assertEvalEquals("so what", "#set($spaces=' ')${spaces.trim()|'so what'}");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictCompareTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictCompareTestCase.java
new file mode 100644
index 00000000..5174f248
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictCompareTestCase.java
@@ -0,0 +1,76 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.RuntimeConstants;
+
+/**
+ * Make sure exceptions are thrown for strict comparisons that
+ * cannot be compared.
+ */
+public class StrictCompareTestCase extends BaseTestCase
+{
+ public StrictCompareTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+ context.put("NULL", null);
+ context.put("a", "abc");
+ context.put("b", 3);
+ context.put("TRUE", Boolean.TRUE);
+ }
+
+ public void testCompare()
+ {
+ assertVelocityEx("#if($a > $NULL)#end");
+ assertVelocityEx("#if($a < $NULL)#end");
+ assertVelocityEx("#if($a >= $NULL)#end");
+ assertVelocityEx("#if($a <= $NULL)#end");
+
+ assertVelocityEx("#if($NULL > $a)#end");
+ assertVelocityEx("#if($NULL < $a)#end");
+ assertVelocityEx("#if($NULL >= $a)#end");
+ assertVelocityEx("#if($NULL <= $a)#end");
+
+ assertVelocityEx("#if($NULL >= $NULL)#end");
+ assertVelocityEx("#if($a >= $b)#end");
+ assertVelocityEx("#if($a <= $b)#end");
+ assertVelocityEx("#if($a > $b)#end");
+ assertVelocityEx("#if($a < $b)#end");
+
+ assertVelocityEx("#if($a < 5)#end");
+ assertVelocityEx("#if($a > 5)#end");
+ }
+
+ /**
+ * Assert that we get a VelocityException when calling evaluate
+ */
+ public void assertVelocityEx(String template)
+ {
+ assertEvalException(template, VelocityException.class);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictEscapeTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictEscapeTestCase.java
new file mode 100644
index 00000000..b3ab04a6
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictEscapeTestCase.java
@@ -0,0 +1,139 @@
+package org.apache.velocity.test;
+/*
+ * 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;
+
+
+/**
+ * Test Strict escape mode
+ * property: RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE set to true
+ */
+public class StrictEscapeTestCase extends BaseTestCase
+{
+ public StrictEscapeTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT_ESCAPE, Boolean.TRUE);
+ context.put("pow", "bang");
+ context.put("NULL", null);
+ context.put("animal", new Animal());
+ // DEBUG = true;
+ }
+
+ public void testReferenceEscape()
+ {
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+
+ assertEvalException("\\\\$bogus");
+ assertEvalException("\\\\\\\\$bogus");
+
+ assertEvalEquals("$bogus", "\\$bogus");
+ assertEvalEquals("$bogus.xyz", "\\$bogus.xyz");
+ assertEvalEquals("${bogus}", "\\${bogus}");
+ assertEvalEquals("${bogus.xyz}", "\\${bogus.xyz}");
+ assertEvalEquals("\\$bogus", "\\\\\\$bogus");
+ assertEvalEquals("\\xyz","#set($foo = \"xyz\")\\\\$foo");
+ assertEvalEquals("\\$foo","#set($foo = \"xyz\")\\\\\\$foo");
+ assertEvalEquals("$foo\\","#set($foo = \"xyz\")\\$foo\\");
+ assertEvalEquals("$pow", "#set($foo = \"\\$pow\")$foo");
+ assertEvalEquals("\\bang", "#set($foo = \"\\\\$pow\")$foo");
+ assertEvalEquals("\\$pow", "#set($foo = \"\\\\\\$pow\")$foo");
+
+ assertEvalEquals("\\$%", "\\$%");
+
+ // This should work but does not... may be related to VELOCITY-679
+ // This is broken from existing escape behavior
+ // assertEvalEquals("\\$bang", "\\$$pow");
+
+ assertEvalEquals("$!foo", "#set($foo = $NULL)\\$!foo");
+ assertEvalEquals("$!animal.null", "\\$!animal.null");
+ assertEvalEquals("$!animal", "\\$!animal");
+ }
+
+ public void testMacroEscape()
+ {
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+ assertEvalException("\\\\#bogus()");
+
+ // define the foo macro
+ assertEvalEquals("", "#macro(foo)bar#end");
+
+ assertEvalEquals("#foo()", "\\#foo()");
+ assertEvalEquals("\\bar", "\\\\#foo()");
+ assertEvalEquals("\\#foo()", "\\\\\\#foo()");
+
+ assertEvalEquals("bar", "#set($abc = \"#foo()\")$abc");
+ assertEvalEquals("#foo()", "#set($abc = \"\\#foo()\")$abc");
+ assertEvalEquals("\\bar", "#set($abc = \"\\\\#foo()\")$abc");
+
+ assertEvalEquals("#@foo()", "\\#@foo()");
+ assertEvalEquals("#@foo", "\\#@foo");
+ assertEvalEquals("#@bar", "\\#@bar");
+ assertEvalEquals("\\bar", "\\\\#@foo()#end");
+ assertEvalEquals("#@foo()#end", "\\#@foo()\\#end");
+ assertEvalEquals("#@foo#end", "\\#@foo\\#end");
+ assertEvalEquals("#@bar #end", "\\#@bar \\#end");
+
+ assertEvalEquals("#end #foreach #define() #elseif", "\\#end \\#foreach \\#define() \\#elseif");
+ assertEvalEquals("#{end} #{foreach} #{define}() #{elseif}", "\\#{end} \\#{foreach} \\#{define}() \\#{elseif}");
+ assertEvalEquals("#macro(foo) #end", "\\#macro(foo) \\#end");
+
+ assertEvalException("\\\\#end");
+ assertEvalException("\\\\#if()");
+
+ // This should work but does not, probably related to VELOCITY-678
+ // this is broken from existing behavior
+ //assertEvalEquals("\\$bar", "\\$#foo()");
+ }
+
+ /**
+ * Tests for non strict-mode
+ */
+ public void testStrictMode()
+ {
+ assertEvalEquals("#bogus()", "\\#bogus()");
+ assertEvalEquals("\\#bogus", "\\\\#bogus");
+
+ assertEvalEquals("\\$bogus", "\\\\$bogus");
+ assertEvalEquals("\\\\$bogus", "\\\\\\\\$bogus");
+ assertEvalEquals("\\$bogus", "#set($foo = \"\\\\$bogus\")$foo");
+ }
+
+ /**
+ * Test object for escaping
+ */
+ public static class Animal
+ {
+ public Object getNull()
+ {
+ return null;
+ }
+
+ public String toString()
+ {
+ return null;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictForeachTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictForeachTestCase.java
new file mode 100755
index 00000000..3632016b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictForeachTestCase.java
@@ -0,0 +1,110 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.RuntimeConstants;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * This class tests support for strict foreach mode.
+ */
+public class StrictForeachTestCase extends BaseTestCase
+{
+ public StrictForeachTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.SKIP_INVALID_ITERATOR, Boolean.FALSE);
+ context.put("good", new GoodIterable());
+ context.put("bad", new BadIterable());
+ context.put("ugly", new UglyIterable());
+ }
+
+ public void testGood()
+ {
+ try
+ {
+ evaluate("#foreach( $i in $good )$i#end");
+ }
+ catch (VelocityException ve)
+ {
+ fail("Doing #foreach on $good should not have exploded!");
+ }
+ }
+
+ public void testBad()
+ {
+ try
+ {
+ evaluate("#foreach( $i in $bad )$i#end");
+ fail("Doing #foreach on $bad should have exploded!");
+ }
+ catch (VelocityException ve)
+ {
+ // success!
+ }
+ }
+
+ public void testUgly()
+ {
+ try
+ {
+ evaluate("#foreach( $i in $ugly )$i#end");
+ fail("Doing #foreach on $ugly should have exploded!");
+ }
+ catch (VelocityException ve)
+ {
+ // success!
+ }
+ }
+
+
+ public static class GoodIterable
+ {
+ public Iterator iterator()
+ {
+ return new ArrayList().iterator();
+ }
+ }
+
+ public static class BadIterable
+ {
+ public Object iterator()
+ {
+ return new Object();
+ }
+ }
+
+ public static class UglyIterable
+ {
+ public Iterator iterator()
+ {
+ return null;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictMathTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictMathTestCase.java
new file mode 100755
index 00000000..4515a899
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictMathTestCase.java
@@ -0,0 +1,86 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.MathException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+/**
+ * This class tests support for strict math mode.
+ */
+public class StrictMathTestCase extends BaseTestCase
+{
+ public StrictMathTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.STRICT_MATH, Boolean.TRUE);
+ context.put("num", 5);
+ context.put("zero", 0);
+ }
+
+ protected void assertNullMathEx(String operation)
+ {
+ String leftnull = "#set( $foo = $null "+operation+" $num )";
+ assertEvalException(leftnull, MathException.class);
+ String rightnull = "#set( $foo = $num "+operation+" $null )";
+ assertEvalException(rightnull, MathException.class);
+ }
+
+ protected void assertImaginaryMathEx(String operation)
+ {
+ String infinity = "#set( $foo = $num "+operation+" $zero )";
+ assertEvalException(infinity, MathException.class);
+ }
+
+
+ public void testAdd()
+ {
+ assertNullMathEx("+");
+ }
+
+ public void testSub()
+ {
+ assertNullMathEx("-");
+ }
+
+ public void testMul()
+ {
+ assertNullMathEx("*");
+ }
+
+ public void testMod()
+ {
+ assertNullMathEx("%");
+ assertImaginaryMathEx("%");
+ }
+
+ public void testDiv()
+ {
+ assertNullMathEx("/");
+ assertImaginaryMathEx("/");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java
new file mode 100644
index 00000000..5a6b6d17
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StrictReferenceTestCase.java
@@ -0,0 +1,257 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.VelocityException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+/**
+ * Test strict reference mode turned on by the velocity property
+ * runtime.references.strict
+ */
+public class StrictReferenceTestCase extends BaseTestCase
+{
+ public StrictReferenceTestCase(String name)
+ {
+ super(name);
+ }
+
+ // second engine to test WITH conversions
+ VelocityEngine engine2;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ /* first engine without conversions */
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+ engine.setProperty(RuntimeConstants.CONVERSION_HANDLER_CLASS, "none");
+
+ /* second engine with conversions */
+ engine2 = createEngine();
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+
+ context.put("NULL", null);
+ context.put("bar", null);
+ context.put("TRUE", Boolean.TRUE);
+ }
+
+ /**
+ * Test the modified behavior of #if in strict mode. Mainly, that
+ * single variables references in #if statements use non strict rules
+ */
+ public void testIfStatement()
+ {
+ Fargo fargo = new Fargo();
+ fargo.next = new Fargo();
+ context.put("fargo", fargo);
+ assertEvalEquals("", "#if($bogus)xxx#end");
+ assertEvalEquals("xxx", "#if($fargo)xxx#end");
+ assertEvalEquals("", "#if( ! $fargo)xxx#end");
+ assertEvalEquals("xxx", "#if($bogus || $fargo)xxx#end");
+ assertEvalEquals("", "#if($bogus && $fargo)xxx#end");
+ assertEvalEquals("", "#if($fargo != $NULL && $bogus)xxx#end");
+ assertEvalEquals("xxx", "#if($fargo == $NULL || ! $bogus)xxx#end");
+ assertEvalEquals("xxx", "#if(! $bogus1 && ! $bogus2)xxx#end");
+ assertEvalEquals("xxx", "#if($fargo.prop == \"propiness\" && ! $bogus && $bar == $NULL)xxx#end");
+ assertEvalEquals("", "#if($bogus && $bogus.foo)xxx#end");
+
+ assertMethodEx("#if($bogus.foo)#end");
+ assertMethodEx("#if(!$bogus.foo)#end");
+ }
+
+
+ /**
+ * We make sure that variables can actuall hold null
+ * values.
+ */
+ public void testAllowNullValues()
+ throws Exception
+ {
+ evaluate("$!bar");
+ assertEvalEquals("true", "#if($bar == $NULL)true#end");
+ assertEvalEquals("true", "#set($foobar = $NULL)#if($foobar == $NULL)true#end");
+ assertEvalEquals("13", "#set($list = [1, $NULL, 3])#foreach($item in $list)#if($item != $NULL)$item#end#end");
+ }
+
+ /**
+ * Test that variables references that have not been defined throw exceptions
+ */
+ public void testStrictVariableRef()
+ throws Exception
+ {
+ // We expect a Method exception on the following
+ assertMethodEx("$bogus");
+ assertMethodEx("#macro(test)$bogus#end #test()");
+
+ assertMethodEx("#set($bar = $bogus)");
+
+ assertMethodEx("#if($bogus == \"bar\") #end");
+ assertMethodEx("#if($bogus != \"bar\") #end");
+ assertMethodEx("#if(\"bar\" == $bogus) #end");
+ assertMethodEx("#if($bogus > 1) #end");
+ assertMethodEx("#foreach($item in $bogus)#end");
+
+ // make sure no exceptions are thrown here
+ evaluate("#set($foo = \"bar\") $foo");
+ evaluate("#macro(test1 $foo1) $foo1 #end #test1(\"junk\")");
+ evaluate("#macro(test2) #set($foo2 = \"bar\") $foo2 #end #test2()");
+ }
+
+ /**
+ * Test that exceptions are thrown when methods are called on
+ * references that contains objects that do not contains those
+ * methods.
+ */
+ public void testStrictMethodRef()
+ {
+ Fargo fargo = new Fargo();
+ fargo.next = new Fargo();
+ context.put("fargo", fargo);
+
+ // Mainly want to make sure no exceptions are thrown here
+ assertEvalEquals("propiness", "$fargo.prop");
+ assertEvalEquals("", "$!fargo.nullVal");
+ assertEvalEquals("propiness", "$fargo.next.prop");
+
+ assertMethodEx("$fargo.foobar");
+ assertMethodEx("$fargo.next.foobar");
+ assertMethodEx("$fargo.foobar()");
+ assertMethodEx("#set($fargo.next.prop = $TRUE)");
+ assertMethodEx("$fargo.next.setProp($TRUE)");
+ }
+
+ /**
+ * Make sure exceptions are thrown when when we attempt to call
+ * methods on null values.
+ */
+ public void testStrictMethodOnNull()
+ {
+ Fargo fargo = new Fargo();
+ fargo.next = new Fargo();
+ context.put("fargo", fargo);
+
+ assertVelocityEx("$NULL.bogus");
+ assertVelocityEx("$fargo.nullVal.bogus");
+ assertVelocityEx("$fargo.next.nullVal.bogus");
+ assertVelocityEx("#if (\"junk\" == $fargo.nullVal.bogus)#end");
+ assertVelocityEx("#if ($fargo.nullVal.bogus > 2)#end");
+ assertVelocityEx("#set($fargo.next.nullVal.bogus = \"junk\")");
+ assertVelocityEx("#set($foo = $NULL.bogus)");
+ assertVelocityEx("#foreach($item in $fargo.next.nullVal.bogus)#end");
+
+ evaluate("$fargo.prop.toString()");
+ assertVelocityEx("#set($fargo.prop = $NULL)$fargo.prop.next");
+
+ // make sure no exceptions are thrown here
+ evaluate("$!fargo.next.next");
+ evaluate("$!fargo.next.nullVal");
+ evaluate("#foreach($item in $fargo.nullVal)#end");
+ }
+
+ /**
+ * Make sure undefined macros throw exceptions
+ */
+ public void testMacros()
+ {
+ assertVelocityEx("#bogus()");
+ assertVelocityEx("#bogus ( )");
+ assertVelocityEx("#bogus( $a )");
+ assertVelocityEx("abc#bogus ( $a )a ");
+
+ assertEvalEquals(" true ", "#macro(test1) true #end#test1()");
+ assertEvalEquals(" true ", "#macro(test2 $a) $a #end#test2 ( \"true\")");
+ assertEvalEquals("#CCFFEE", "#CCFFEE");
+ assertEvalEquals("#F - ()", "#F - ()");
+ assertEvalEquals("#F{}", "#F{}");
+ }
+
+
+ public void testRenderingNull()
+ {
+ Fargo fargo = new Fargo();
+ fargo.next = new Fargo();
+ context.put("fargo", fargo);
+
+ assertVelocityEx("#set($foo = $NULL)$foo");
+ assertEvalEquals("", "#set($foo = $NULL)$!foo");
+ assertVelocityEx("$fargo.nullVal");
+ assertEvalEquals("", "$!fargo.nullVal");
+ assertVelocityEx("$fargo.next.next");
+ assertEvalEquals("", "$!fargo.next.next");
+ assertVelocityEx("$fargo.next.nullVal");
+ assertEvalEquals("", "$!fargo.next.nullVal");
+ }
+
+ /**
+ * Assert that we get a MethodInvocationException when calling evaluate
+ */
+ public void assertMethodEx(String template)
+ {
+ assertEvalException(template, MethodInvocationException.class);
+ }
+
+ /**
+ * Assert that we get a VelocityException when calling evaluate
+ */
+ public void assertVelocityEx(String template)
+ {
+ assertEvalException(template, VelocityException.class);
+ }
+
+ /**
+ * Assert that we get a MethodInvocationException when calling evaluate
+ */
+ public void assertParseEx(String template)
+ {
+ assertEvalException(template, ParseErrorException.class);
+ }
+
+
+ public static class Fargo
+ {
+ String prop = "propiness";
+ Fargo next = null;
+
+ public String getProp()
+ {
+ return prop;
+ }
+
+ public void setProp(String val)
+ {
+ this.prop = val;
+ }
+
+ public String getNullVal()
+ {
+ return null;
+ }
+
+ public Fargo getNext()
+ {
+ return next;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StringConcatenationTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StringConcatenationTestCase.java
new file mode 100755
index 00000000..f86e06c4
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StringConcatenationTestCase.java
@@ -0,0 +1,66 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 tests support for string concatenation.
+ */
+public class StringConcatenationTestCase extends BaseTestCase
+{
+ public StringConcatenationTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ context.put("foo", "foo");
+ context.put("baz", "baz");
+ }
+
+ public void testStringRefLeft()
+ {
+ assertEvalEquals("foobar", "#set( $o = $foo + 'bar' )$o");
+ assertEvalEquals("foo$bar", "#set( $o = $foo + $bar )$o");
+ assertEvalEquals("foo1", "#set( $o = $foo + 1 )$o");
+ assertEvalEquals("foobaz", "#set( $o = $foo + $baz )$o");
+ }
+
+ public void testStringRefRight()
+ {
+ assertEvalEquals("barfoo", "#set( $o = 'bar' + $foo )$o");
+ assertEvalEquals("$barfoo", "#set( $o = $bar + $foo )$o");
+ assertEvalEquals("1foo", "#set( $o = 1 + $foo )$o");
+ }
+
+ public void testNoRef()
+ {
+ assertEvalEquals("bar1", "#set( $o = 'bar' + 1 )$o");
+ }
+
+ public void testAll()
+ {
+ assertEvalEquals("foobar$bar1baz", "#set( $o = $foo + 'bar' + $bar + 1 + $baz )$o");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderRepositoryTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderRepositoryTestCase.java
new file mode 100644
index 00000000..23aee4ce
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderRepositoryTestCase.java
@@ -0,0 +1,214 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestCase;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.StringWriter;
+
+/**
+ * Tests ability to have multiple repositories in the same app.
+ *
+ * @author Nathan Bubna
+ * @version $Id: StringResourceLoaderRepositoryTestCase.java 479058 2006-11-25 00:26:32Z henning $
+ */
+public class StringResourceLoaderRepositoryTestCase extends TestCase
+{
+ private VelocityContext context;
+
+ public StringResourceLoaderRepositoryTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ Velocity.reset();
+ Velocity.setProperty(Velocity.RESOURCE_LOADERS, "string");
+ Velocity.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+ Velocity.addProperty("string.resource.loader.modificationCheckInterval", "1");
+ Velocity.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ Velocity.init();
+
+ StringResourceRepository repo = getRepo(null, null);
+ repo.putStringResource("foo", "This is $foo");
+ repo.putStringResource("bar", "This is $bar");
+
+ context = new VelocityContext();
+ context.put("foo", "wonderful!");
+ context.put("bar", "horrible!");
+ context.put("woogie", "a woogie");
+ }
+
+
+ protected VelocityEngine newStringEngine(String repoName, boolean isStatic)
+ {
+ VelocityEngine engine = new VelocityEngine();
+ TestLogger logger = new TestLogger();
+ engine.setProperty(Velocity.RESOURCE_LOADERS, "string");
+ engine.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+ if (repoName != null)
+ {
+ engine.addProperty("string.resource.loader.repository.name", repoName);
+ }
+ if (!isStatic)
+ {
+ engine.addProperty("string.resource.loader.repository.static", "false");
+ }
+ engine.addProperty("string.resource.loader.modificationCheckInterval", "1");
+ engine.setProperty(Velocity.RUNTIME_LOG_INSTANCE, logger);
+ return engine;
+ }
+
+ protected StringResourceRepository getRepo(String name, VelocityEngine engine)
+ {
+ if (engine == null)
+ {
+ if (name == null)
+ {
+ return StringResourceLoader.getRepository();
+ }
+ else
+ {
+ return StringResourceLoader.getRepository(name);
+ }
+ }
+ else
+ {
+ if (name == null)
+ {
+ return (StringResourceRepository)engine.getApplicationAttribute(StringResourceLoader.REPOSITORY_NAME_DEFAULT);
+ }
+ else
+ {
+ return (StringResourceRepository)engine.getApplicationAttribute(name);
+ }
+ }
+ }
+
+ protected String render(Template template) throws Exception
+ {
+ StringWriter out = new StringWriter();
+ template.merge(this.context, out);
+ return out.toString();
+ }
+
+
+ public void testSharedRepo() throws Exception
+ {
+ // this engine's string resource loader should share a repository
+ // with the singleton's string resource loader
+ VelocityEngine engine = newStringEngine(null, true);
+
+ // get and merge the same template from both runtimes with the same context
+ String engineOut = render(engine.getTemplate("foo"));
+ String singletonOut = render(RuntimeSingleton.getTemplate("foo"));
+
+ // make sure they're equal
+ assertEquals(engineOut, singletonOut);
+ }
+
+ public void testAlternateStaticRepo() throws Exception
+ {
+ VelocityEngine engine = newStringEngine("alternate.repo", true);
+ // should be null be for init
+ StringResourceRepository repo = getRepo("alternate.repo", null);
+ assertNull(repo);
+ engine.init();
+ // and not null after init
+ repo = getRepo("alternate.repo", null);
+ assertNotNull(repo);
+ repo.putStringResource("foo", "This is NOT $foo");
+
+ // get and merge template with the same name from both runtimes with the same context
+ String engineOut = render(engine.getTemplate("foo"));
+ String singletonOut = render(RuntimeSingleton.getTemplate("foo"));
+
+ // make sure they're NOT equal
+ assertFalse(engineOut.equals(singletonOut));
+ }
+
+ public void testPreCreatedStaticRepo() throws Exception
+ {
+ VelocityEngine engine = newStringEngine("my.repo", true);
+ MyRepo repo = new MyRepo();
+ repo.put("bar", "This is NOT $bar");
+ StringResourceLoader.setRepository("my.repo", repo);
+
+ String out = render(engine.getTemplate("bar"));
+ assertEquals(out, "This is NOT horrible!");
+ }
+
+ public void testAppRepo() throws Exception
+ {
+ VelocityEngine engine = newStringEngine(null, false);
+ engine.init();
+
+ StringResourceRepository repo = getRepo(null, engine);
+ assertNotNull(repo);
+ repo.putStringResource("woogie", "What is $woogie?");
+
+ String out = render(engine.getTemplate("woogie"));
+ assertEquals(out, "What is a woogie?");
+ }
+
+ public void testAlternateAppRepo() throws Exception
+ {
+ VelocityEngine engine = newStringEngine("alternate.app.repo", false);
+ engine.init();
+
+ StringResourceRepository repo = getRepo("alternate.app.repo", engine);
+ assertNotNull(repo);
+ repo.putStringResource("you/foo.vm", "You look $foo");
+
+ String out = render(engine.getTemplate("you/foo.vm"));
+ assertEquals(out, "You look wonderful!");
+ }
+
+ public void testPreCreatedAppRepo() throws Exception
+ {
+ VelocityEngine engine = newStringEngine("my.app.repo", false);
+ MyRepo repo = new MyRepo();
+ repo.put("you/bar.vm", "You look $bar");
+ engine.setApplicationAttribute("my.app.repo", repo);
+
+ String out = render(engine.getTemplate("you/bar.vm"));
+ assertEquals(out, "You look horrible!");
+ }
+
+ public static class MyRepo extends StringResourceRepositoryImpl
+ {
+ public void put(String name, String template)
+ {
+ putStringResource(name, template);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderTestCase.java
new file mode 100644
index 00000000..69c09bb5
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/StringResourceLoaderTestCase.java
@@ -0,0 +1,209 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * Multiple paths in the file resource loader.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class StringResourceLoaderTestCase extends BaseTestCase
+{
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/stringloader";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/stringloader/compare";
+
+ /**
+ * Default constructor.
+ */
+ public StringResourceLoaderTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(StringResourceLoaderTestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Velocity.reset();
+
+ Velocity.setProperty(Velocity.RESOURCE_LOADERS, "string");
+ Velocity.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+ Velocity.addProperty("string.resource.loader.modificationCheckInterval", "1");
+
+ // Silence the logger.
+ Velocity.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public void testSimpleTemplate()
+ throws Exception
+ {
+ StringResourceLoader.getRepository().putStringResource("simpletemplate.vm", "This is a test for ${foo}");
+
+ Template template = RuntimeSingleton.getTemplate(getFileName(null, "simpletemplate", TMPL_FILE_EXT));
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "simpletemplate", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityContext context = new VelocityContext();
+ context.put("foo", "a foo object");
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "simpletemplate",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect.");
+ }
+ }
+
+ public void testMultipleTemplates()
+ throws Exception
+ {
+ StringResourceLoader.getRepository().putStringResource("multi1.vm", "I am the $first template.");
+ StringResourceLoader.getRepository().putStringResource("multi2.vm", "I am the $second template.");
+
+ Template template1 = RuntimeSingleton.getTemplate(getFileName(null, "multi1", TMPL_FILE_EXT));
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "multi1", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityContext context = new VelocityContext();
+ context.put("first", 1);
+ context.put("second", "two");
+
+ template1.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ Template template2 = RuntimeSingleton.getTemplate(getFileName(null, "multi2", TMPL_FILE_EXT));
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, "multi2", RESULT_FILE_EXT));
+
+ writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ template2.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "multi1",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Template 1 incorrect.");
+ }
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "multi2",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Template 2 incorrect.");
+ }
+ }
+
+ public void testContentChange()
+ throws Exception
+ {
+ StringResourceLoader.getRepository().putStringResource("change.vm", "I am the $first template.");
+
+ Template template = RuntimeSingleton.getTemplate(getFileName(null, "change", TMPL_FILE_EXT));
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, "change1", RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityContext context = new VelocityContext();
+ context.put("first", 1);
+ context.put("second", "two");
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ StringResourceLoader.getRepository().putStringResource("change.vm", "I am the $second template.");
+ Thread.sleep(2000L);
+ template = RuntimeSingleton.getTemplate(getFileName(null, "change", TMPL_FILE_EXT));
+
+ fos = new FileOutputStream (
+ getFileName(RESULTS_DIR, "change2", RESULT_FILE_EXT));
+
+ writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "change1",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Template 1 incorrect.");
+ }
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, "change2",
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Template 2 incorrect.");
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestBase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestBase.java
new file mode 100644
index 00000000..e35e00d7
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestBase.java
@@ -0,0 +1,84 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 is a base interface that contains a bunch of static final
+ * strings that are of use when testing templates.
+ *
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ * @version $Id$
+ */
+public interface TemplateTestBase
+{
+ /**
+ * Directory relative to the distribution root, where the
+ * values to compare test results to are stored.
+ */
+ String TEST_COMPARE_DIR = System.getProperty("test.compare.dir");
+
+ /**
+ * Directory relative to the distribution root, where the
+ * test cases should put their output
+ */
+ String TEST_RESULT_DIR = System.getProperty("test.result.dir");
+
+
+ /**
+ * VTL file extension.
+ */
+ String TMPL_FILE_EXT = "vm";
+
+ /**
+ * Comparison file extension.
+ */
+ String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path for templates. This property will override the
+ * value in the default velocity properties file.
+ */
+ String FILE_RESOURCE_LOADER_PATH =
+ TEST_COMPARE_DIR + "/templates";
+
+ /**
+ * Properties file that lists which template tests to run.
+ */
+ String TEST_CASE_PROPERTIES =
+ FILE_RESOURCE_LOADER_PATH + "/templates.properties";
+
+ /**
+ * Results relative to the build directory.
+ */
+ String RESULT_DIR =
+ TEST_RESULT_DIR + "/templates";
+
+ /**
+ * Results relative to the build directory.
+ */
+ String COMPARE_DIR =
+ FILE_RESOURCE_LOADER_PATH + "/compare";
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestCase.java
new file mode 100644
index 00000000..99a4eacf
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestCase.java
@@ -0,0 +1,237 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.FieldMethodizer;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.test.provider.BoolObj;
+import org.apache.velocity.test.provider.NullToStringObject;
+import org.apache.velocity.test.provider.TestNumber;
+import org.apache.velocity.test.provider.TestProvider;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * Easily add test cases which evaluate templates and check their output.
+ *
+ * NOTE:
+ * This class DOES NOT extend RuntimeTestCase because the TemplateTestSuite
+ * already initializes the Velocity runtime and adds the template
+ * test cases. Having this class extend RuntimeTestCase causes the
+ * Runtime to be initialized twice which is not good. I only discovered
+ * this after a couple hours of wondering why all the properties
+ * being setup were ending up as Vectors. At first I thought it
+ * was a problem with the Configuration class, but the Runtime
+ * was being initialized twice: so the first time the property
+ * is seen it's stored as a String, the second time it's seen
+ * the Configuration class makes a Vector with both Strings.
+ * As a result all the getBoolean(property) calls were failing because
+ * the Configurations class was trying to create a Boolean from
+ * a Vector which doesn't really work that well. I have learned
+ * my lesson and now have to add some code to make sure the
+ * Runtime isn't initialized more then once :-)
+ *
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @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:jon@latchkey.com">Jon S. Stevens</a>
+ * @version $Id$
+ */
+public class TemplateTestCase extends BaseTestCase implements TemplateTestBase
+{
+ /**
+ * The base file name of the template and comparison file (i.e. array for
+ * array.vm and array.cmp).
+ */
+ protected String baseFileName;
+
+ private TestProvider provider;
+ private ArrayList al;
+ private Hashtable h;
+ private VelocityContext context;
+ private VelocityContext context1;
+ private VelocityContext context2;
+ private Vector vec;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param baseFileName The base name of the template and comparison file to
+ * use (i.e. array for array.vm and array.cmp).
+ */
+ public TemplateTestCase (String baseFileName)
+ {
+ super(getTestCaseName(baseFileName));
+ this.baseFileName = baseFileName;
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new TemplateTestSuite();
+ }
+
+ /**
+ * Sets up the test.
+ */
+ @Override
+ protected void setUp ()
+ throws Exception
+ {
+ super.setUp();
+ Velocity.reset();
+ Velocity.setProperty(
+ Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.setProperty("space.gobbling", "bc");
+
+ Velocity.init();
+
+ provider = new TestProvider();
+ al = provider.getCustomers();
+ h = new Hashtable();
+
+ h.put("Bar", "this is from a hashtable!");
+ h.put("Foo", "this is from a hashtable too!");
+
+ /*
+ * lets set up a vector of objects to test late introspection. See ASTMethod.java
+ */
+
+ vec = new Vector();
+
+ vec.addElement(new String("string1"));
+ vec.addElement(new String("string2"));
+
+ /*
+ * set up 3 chained contexts, and add our data
+ * throught the 3 of them.
+ */
+
+ context2 = new VelocityContext();
+ context1 = new VelocityContext( context2 );
+ context = new VelocityContext( context1 );
+
+ context.put("provider", provider);
+ context1.put("name", "jason");
+ context1.put("name2", new StringBuffer("jason"));
+ context1.put("name3", new StringBuffer("geoge"));
+ context2.put("providers", provider.getCustomers2());
+ context.put("list", al);
+ context1.put("hashtable", h);
+ context2.put("hashmap", new HashMap());
+ context2.put("search", provider.getSearch());
+ context.put("relatedSearches", provider.getRelSearches());
+ context1.put("searchResults", provider.getRelSearches());
+ context2.put("stringarray", provider.getArray());
+ context.put("vector", vec );
+ context.put("mystring", new String());
+ context.put("runtime", new FieldMethodizer( "org.apache.velocity.runtime.RuntimeSingleton" ));
+ context.put("fmprov", new FieldMethodizer( provider ));
+ context.put("Floog", "floogie woogie");
+ context.put("boolobj", new BoolObj() );
+
+ /*
+ * we want to make sure we test all types of iterative objects
+ * in #foreach()
+ */
+
+ Object[] oarr = { "a","b","c","d" } ;
+ int intarr[] = { 10, 20, 30, 40, 50 };
+ int emptyarr[] = {};
+
+ context.put( "collection", vec );
+ context2.put("iterator", vec.iterator());
+ context1.put("map", h );
+ context.put("obarr", oarr );
+ context.put("enumerator", vec.elements());
+ context.put("intarr", intarr );
+ context.put("emptyarr", emptyarr );
+
+ // Add some Numbers
+ context.put ("int1", 1000);
+ context.put ("long1", 10000000000l);
+ context.put ("float1", 1000.1234f);
+ context.put ("double1", 10000000000d);
+
+ // Add a TemplateNumber
+ context.put ("templatenumber1", new TestNumber (999.125));
+
+ /*
+ * Test #foreach() with a list containing nulls
+ */
+ ArrayList nullList = new ArrayList();
+ nullList.add("a");
+ nullList.add("b");
+ nullList.add(null);
+ nullList.add("d");
+ context.put("nullList", nullList);
+
+ // test silent references with a null tostring
+ context.put("nullToString",new NullToStringObject());
+ }
+
+ /**
+ * Runs the test.
+ */
+ @Override
+ public void runTest ()
+ throws Exception
+ {
+ Template template = RuntimeSingleton.getTemplate
+ (getFileName(null, baseFileName, TMPL_FILE_EXT));
+
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ /* get the file to write to */
+ FileOutputStream fos =
+ new FileOutputStream (getFileName(
+ RESULT_DIR, baseFileName, RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ /* process the template */
+ template.merge( context, writer);
+
+ /* close the file */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR,COMPARE_DIR,baseFileName,
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template "+getFileName(
+ RESULT_DIR, baseFileName, RESULT_FILE_EXT)+" did not match expected output");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestSuite.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestSuite.java
new file mode 100644
index 00000000..36c46951
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/TemplateTestSuite.java
@@ -0,0 +1,95 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestSuite;
+
+import java.io.FileInputStream;
+import java.util.Properties;
+
+/**
+ * Test suite for Templates.
+ *
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @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:jon@latchkey.com">Jon S. Stevens</a>
+ * @version $Id$
+ */
+public class TemplateTestSuite extends TestSuite implements TemplateTestBase
+{
+ private Properties testProperties;
+
+ /**
+ * Creates an instace of the Apache Velocity test suite.
+ */
+ public TemplateTestSuite()
+ {
+ try
+ {
+ testProperties = new Properties();
+ testProperties.load(new FileInputStream(TEST_CASE_PROPERTIES));
+ }
+ catch (Exception e)
+ {
+ System.err.println("Cannot setup TemplateTestSuite!");
+ e.printStackTrace();
+ System.exit(1);
+ }
+
+ addTemplateTestCases();
+ }
+
+ /**
+ * Adds the template test cases to run to this test suite. Template test
+ * cases are listed in the <code>TEST_CASE_PROPERTIES</code> file.
+ */
+ private void addTemplateTestCases()
+ {
+ String template;
+ for (int i = 1 ;; i++)
+ {
+ template = testProperties.getProperty(getTemplateTestKey(i));
+
+ if (template != null)
+ {
+ System.out.println("Adding TemplateTestCase : " + template);
+ addTest(new TemplateTestCase(template));
+ }
+ else
+ {
+ // Assume we're done adding template test cases.
+ break;
+ }
+ }
+ }
+
+ /**
+ * Macro which returns the properties file key for the specified template
+ * test number.
+ *
+ * @param nbr The template test number to return a property key for.
+ * @return The property key.
+ */
+ private static String getTemplateTestKey(int nbr)
+ {
+ return ("test.template." + nbr);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/TestBaseTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/TestBaseTestCase.java
new file mode 100644
index 00000000..ee4afe7c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/TestBaseTestCase.java
@@ -0,0 +1,64 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+
+import java.io.File;
+
+/**
+ * I keep breaking the getFileName method all the time...
+ */
+public class TestBaseTestCase
+ extends BaseTestCase
+{
+ public TestBaseTestCase(final String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(TestBaseTestCase.class);
+ }
+
+ public void testGetFileName()
+ throws Exception
+ {
+ String fs = System.getProperty("file.separator");
+ String pwd = System.getProperty("user.dir");
+
+ String root = new File("/").getCanonicalPath();
+
+ assertEquals(pwd + fs + "baz" + fs + "foo.bar", getFileName("baz", "foo", "bar"));
+ assertEquals(root + "baz" + fs + "foo.bar", getFileName(root + "baz", "foo", "bar"));
+ assertEquals(root + "foo.bar", getFileName("baz", root + "foo", "bar"));
+ assertEquals(root + "foo.bar", getFileName(root + "baz", root + "foo", "bar"));
+ assertEquals("", getFileName(null, "", ""));
+ assertEquals(root + "", getFileName("", "", ""));
+ assertEquals(".x", getFileName(null, "", "x"));
+ assertEquals(root + ".x", getFileName("", "", "x"));
+ assertEquals("foo.bar", getFileName(null, "foo", "bar"));
+ assertEquals(root + "foo.bar", getFileName(null, root + "foo", "bar"));
+ assertEquals(root + "foo.bar", getFileName("", "foo", "bar"));
+ assertEquals(root + "foo.bar", getFileName("", root + "foo", "bar"));
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/TextblockTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/TextblockTestCase.java
new file mode 100644
index 00000000..fc042544
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/TextblockTestCase.java
@@ -0,0 +1,163 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.RuntimeInstance;
+import org.apache.velocity.runtime.parser.Parser;
+import org.apache.velocity.runtime.parser.node.ASTTextblock;
+
+import java.lang.reflect.Field;
+
+/**
+ * This class tests the Textblock directive.
+ */
+public class TextblockTestCase extends BaseTestCase
+{
+ // these are all here so that the test case adapts instantly
+ // to changes in the textblock start/end sequences
+ private String START = null;
+ private String END = null;
+ private String PARTIAL_START = null;
+ private String PARTIAL_END = null;
+ private String END_OF_START = null;
+ private String START_OF_END = null;
+
+ public TextblockTestCase(String name)
+ {
+ super(name);
+ //DEBUG = true;
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ // get a valid parser instance to initialize string constants
+ Field riField = engine.getClass().getDeclaredField("ri");
+ riField.setAccessible(true);
+ RuntimeInstance ri = (RuntimeInstance)riField.get(engine);
+ Parser parser = ri.createNewParser();
+ ASTTextblock astTextblock = new ASTTextblock(parser, 0);
+ START = astTextblock.START;
+ END = astTextblock.END;
+ PARTIAL_START = START.substring(0, START.length() - 1);
+ PARTIAL_END = END.substring(1);
+ END_OF_START = START.substring(START.length() - 1);
+ START_OF_END = END.substring(0, 1);
+ }
+
+ public String textblock(String s)
+ {
+ return START + s + END;
+ }
+
+ public void assertTextblockEvalEquals(String s) throws Exception
+ {
+ assertEvalEquals(s, textblock(s));
+ }
+
+ /**
+ * https://issues.apache.org/jira/browse/VELOCITY-661
+ */
+ public void testTextblockAjaxcode() throws Exception
+ {
+ String s = "var myId = 'someID';$('#test).append($.template('<div id=\"${myId}\"></div>').apply({myId: myId}));";
+ assertEvalEquals(s + " 123", textblock(s)+" #foreach($i in [1..3])$i#end");
+ }
+
+ public void testLooseTextblockEnd() throws Exception
+ {
+ // just like a multi-line comment end (*#), this must be
+ // followed by a character. by itself, it bombs for some reason.
+ assertEvalEquals(END+" ", END+" ");
+ }
+
+ public void testTextblockStartInTextblock() throws Exception
+ {
+ assertTextblockEvalEquals(START);
+ }
+
+ public void testTextblockEndBetweenTwoTextblockHalves() throws Exception
+ {
+ // just like a multi-line comment end (*#), the end token
+ // in the middle must be followed by some character.
+ // by itself, it bombs. not sure why that is, but the
+ // same has been true of multi-line comments without complaints,
+ // so i'm not going to worry about it just yet.
+ assertEvalEquals(" "+END+" ", textblock(" ")+END+" "+textblock(" "));
+ }
+
+ public void testZerolengthTextblock() throws Exception
+ {
+ assertTextblockEvalEquals("");
+ }
+
+ public void testTextblockInsideForeachLoop() throws Exception
+ {
+ String s = "var myId = 'someID';$('#test).append($.template('<div id=\"${myId}\"></div>').apply({myId: myId}));";
+ assertEvalEquals("1 "+s+"2 "+s+"3 "+s, "#foreach($i in [1..3])$i "+ textblock(s) + "#end");
+ }
+
+ public void testSingleHashInsideTextblock() throws Exception
+ {
+ assertTextblockEvalEquals(" # ");
+ }
+
+ public void testDollarInsideTextblock() throws Exception
+ {
+ assertTextblockEvalEquals("$");
+ }
+
+ public void testTextblockInsideComment() throws Exception
+ {
+ String s = "FOOBAR";
+ assertEvalEquals("", "#* comment "+textblock(s) + " *#");
+ }
+
+ public void testPartialStartEndTokensInsideTextblock() throws Exception
+ {
+ assertTextblockEvalEquals(PARTIAL_START+"foo"+PARTIAL_END);
+ }
+
+ public void testDupeTokenChars() throws Exception
+ {
+ assertTextblockEvalEquals(END_OF_START+START_OF_END);
+ assertTextblockEvalEquals(END_OF_START+END_OF_START+START_OF_END+START_OF_END);
+ assertTextblockEvalEquals(END_OF_START+END_OF_START+"#"+START_OF_END+START_OF_END);
+ }
+
+ /**
+ * https://issues.apache.org/jira/browse/VELOCITY-584
+ */
+ public void testServerSideIncludeEscaping() throws Exception
+ {
+ assertTextblockEvalEquals("<!--#include file=\"wisdom.inc\"--> ");
+ }
+
+ /**
+ * https://issues.apache.org/jira/browse/VELOCITY-676
+ */
+ public void testLineCommentInsideTextblock() throws Exception
+ {
+ assertTextblockEvalEquals("##x");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/URLResourceLoaderTimeoutTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/URLResourceLoaderTimeoutTestCase.java
new file mode 100755
index 00000000..775a75b3
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/URLResourceLoaderTimeoutTestCase.java
@@ -0,0 +1,82 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.loader.URLResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
+
+/**
+ * This class tests support for custom timeouts in URLResourceLoader.
+ */
+public class URLResourceLoaderTimeoutTestCase extends BaseTestCase
+{
+ private static boolean isJava5plus;
+ static
+ {
+ try
+ {
+ Class.forName("java.lang.annotation.Annotation");
+ isJava5plus = true;
+ }
+ catch (ClassNotFoundException cnfe)
+ {
+ isJava5plus = false;
+ }
+ }
+ private TestLogger logger = new TestLogger();
+ private URLResourceLoader loader = new URLResourceLoader();
+ private int timeout = 2000;
+
+ public URLResourceLoaderTimeoutTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty("resource.loader", "url");
+ engine.setProperty("url.resource.loader.instance", loader);
+ engine.setProperty("url.resource.loader.timeout", timeout);
+
+ // actual instance of logger
+ logger.on();
+ engine.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, logger);
+ engine.setProperty("runtime.log.logsystem.test.level", "debug");
+ engine.init();
+ }
+
+ public void testTimeout()
+ {
+ if (isJava5plus)
+ {
+ System.out.println("Testing a 1.5+ JDK");
+ assertEquals(timeout, loader.getTimeout());
+ }
+ else
+ {
+ System.out.println("Testing a pre-1.5 JDK");
+ assertEquals(-1, loader.getTimeout());
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/UberspectorTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/UberspectorTestCase.java
new file mode 100644
index 00000000..4e243c3b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/UberspectorTestCase.java
@@ -0,0 +1,300 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.test.misc.GetPutObject;
+import org.apache.velocity.test.misc.UberspectorTestObject;
+import org.apache.velocity.util.introspection.Uberspect;
+import org.apache.velocity.util.introspection.VelPropertyGet;
+import org.apache.velocity.util.introspection.VelPropertySet;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class UberspectorTestCase
+ extends BaseTestCase
+{
+ private RuntimeInstance ri;
+
+ public UberspectorTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(UberspectorTestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ ri = new RuntimeInstance();
+ ri.init();
+ }
+
+ public void testNullObjects()
+ throws Exception
+ {
+ // How about some null objects... Gee, I'm mean. ;-)
+ Uberspect u = ri.getUberspect();
+
+ VelPropertyGet getter = u.getPropertyGet(null, "foo", null);
+ assertNull(getter);
+
+ VelPropertySet setter = u.getPropertySet(null, "foo", Object.class, null);
+ assertNull(setter);
+ }
+
+ public void testEmptyPropertyGetter()
+ throws Exception
+ {
+ Uberspect u = ri.getUberspect();
+ Map map = new HashMap();
+
+ VelPropertyGet getter = u.getPropertyGet(map, "", null);
+
+ // Don't screw up on empty properties. That should map to get("")
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "get", getter.getMethodName());
+ }
+
+ public void testEmptyPropertySetter()
+ throws Exception
+ {
+ Uberspect u = ri.getUberspect();
+ Map map = new HashMap();
+
+ VelPropertySet setter = u.getPropertySet(map, "", Object.class, null);
+
+ // Don't screw up on empty properties. That should map to put("", Object)
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "put", setter.getMethodName());
+ }
+
+ public void testNullPropertyGetter()
+ throws Exception
+ {
+ Uberspect u = ri.getUberspect();
+ GetPutObject gpo = new GetPutObject();
+ Map map = new HashMap();
+
+ VelPropertyGet getter = u.getPropertyGet(gpo, null, null);
+
+ // Don't screw up on null properties. That should map to get() on the GPO.
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "get", getter.getMethodName());
+
+ // And should be null on a Map which does not have a get()
+ getter = u.getPropertyGet(map, null, null);
+ assertNull(getter);
+
+ }
+
+ public void testNullPropertySetter()
+ throws Exception
+ {
+ Uberspect u = ri.getUberspect();
+ GetPutObject gpo = new GetPutObject();
+ Map map = new HashMap();
+
+ // Don't screw up on null properties. That should map to put() on the GPO.
+ VelPropertySet setter = u.getPropertySet(gpo, null, "", null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "put", setter.getMethodName());
+
+ // And should be null on a Map which does not have a put()
+ setter = u.getPropertySet(map, null, "", null);
+ assertNull(setter);
+ }
+
+ public void testNullParameterType()
+ throws Exception
+ {
+ VelPropertySet setter;
+
+ Uberspect u = ri.getUberspect();
+ UberspectorTestObject uto = new UberspectorTestObject();
+
+ // setRegular()
+ setter = u.getPropertySet(uto, "Regular", null, null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setRegular", setter.getMethodName());
+
+ // setAmbigous() - String and StringBuffer available
+ setter = u.getPropertySet(uto, "Ambigous", null, null);
+ assertNull(setter);
+
+ // setAmbigous() - same with Object?
+ setter = u.getPropertySet(uto, "Ambigous", new Object(), null);
+ assertNull(setter);
+ }
+
+ public void testMultipleParameterTypes()
+ throws Exception
+ {
+ VelPropertySet setter;
+
+ Uberspect u = ri.getUberspect();
+ UberspectorTestObject uto = new UberspectorTestObject();
+
+ // setAmbigous() - String
+ setter = u.getPropertySet(uto, "Ambigous", "", null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setAmbigous", setter.getMethodName());
+
+ // setAmbigous() - StringBuffer
+ setter = u.getPropertySet(uto, "Ambigous", new StringBuffer(), null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setAmbigous", setter.getMethodName());
+ }
+
+
+ public void testRegularGetters()
+ throws Exception
+ {
+ VelPropertyGet getter;
+
+ Uberspect u = ri.getUberspect();
+ UberspectorTestObject uto = new UberspectorTestObject();
+
+ // getRegular()
+ getter = u.getPropertyGet(uto, "Regular", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "getRegular", getter.getMethodName());
+
+ // Lowercase regular
+ getter = u.getPropertyGet(uto, "regular", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "getRegular", getter.getMethodName());
+
+ // lowercase: getpremium()
+ getter = u.getPropertyGet(uto, "premium", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "getpremium", getter.getMethodName());
+
+ // test uppercase: getpremium()
+ getter = u.getPropertyGet(uto, "Premium", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "getpremium", getter.getMethodName());
+ }
+
+ public void testBooleanGetters()
+ throws Exception
+ {
+ VelPropertyGet getter;
+
+ Uberspect u = ri.getUberspect();
+ UberspectorTestObject uto = new UberspectorTestObject();
+
+ // getRegular()
+ getter = u.getPropertyGet(uto, "RegularBool", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "isRegularBool", getter.getMethodName());
+
+ // Lowercase regular
+ getter = u.getPropertyGet(uto, "regularBool", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "isRegularBool", getter.getMethodName());
+
+ // lowercase: getpremiumBool()
+ getter = u.getPropertyGet(uto, "premiumBool", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "ispremiumBool", getter.getMethodName());
+
+ // test uppercase: ()
+ getter = u.getPropertyGet(uto, "PremiumBool", null);
+ assertNotNull(getter);
+ assertEquals("Found wrong method", "ispremiumBool", getter.getMethodName());
+ }
+
+
+ public void testRegularSetters()
+ throws Exception
+ {
+ VelPropertySet setter;
+
+ Uberspect u = ri.getUberspect();
+ UberspectorTestObject uto = new UberspectorTestObject();
+
+ // setRegular()
+ setter = u.getPropertySet(uto, "Regular", "", null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setRegular", setter.getMethodName());
+
+ // Lowercase regular
+ setter = u.getPropertySet(uto, "regular", "", null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setRegular", setter.getMethodName());
+
+ // lowercase: setpremium()
+ setter = u.getPropertySet(uto, "premium", "", null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setpremium", setter.getMethodName());
+
+ // test uppercase: getpremium()
+ setter = u.getPropertySet(uto, "Premium", "", null);
+ assertNotNull(setter);
+ assertEquals("Found wrong method", "setpremium", setter.getMethodName());
+ }
+
+ public void testDisambiguation()
+ throws Exception
+ {
+ VelPropertySet setter;
+
+ Uberspect u = ri.getUberspect();
+ UberspectorTestObject uto = new UberspectorTestObject();
+
+ // setUnambigous() - String
+ setter = u.getPropertySet(uto, "unambiguous", "string", null);
+ assertNotNull(setter);
+ setter.invoke(uto, "string");
+
+ // setUnambigous() - HashMap
+ setter = u.getPropertySet(uto, "unambiguous", new HashMap(), null);
+ assertNotNull(setter);
+ setter.invoke(uto, new HashMap());
+ }
+
+ /*
+ *
+ * public void testMapGetSet()
+ * throws Exception
+ * {
+ * Uberspect u = ri.getUberspect();
+ * Map map = new HashMap();
+ *
+ * VelPropertyGet getter = u.getPropertyGet(map, "", null);
+ * VelPropertySet setter = u.getPropertySet(map, "", Object.class, null);
+ *
+ * assertNotNull("Got a null getter", getter);
+ * assertNotNull("Got a null setter", setter);
+ *
+ * assertEquals("Got wrong getter", "foo", getter.getMethodName());
+ * assertEquals("Got wrong setter", "bar", setter.getMethodName());
+ * }
+ */
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/UnicodeEscapeTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/UnicodeEscapeTestCase.java
new file mode 100644
index 00000000..1cb45475
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/UnicodeEscapeTestCase.java
@@ -0,0 +1,61 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.ASTStringLiteral;
+
+/**
+ * Test Case for <a href="https://issues.apache.org/jira/browse/VELTOOLS-520">Velocity Tools Issue 520</a>.
+ */
+public class UnicodeEscapeTestCase extends BaseTestCase
+{
+ public UnicodeEscapeTestCase(final String name) throws Exception
+ {
+ super(name);
+ }
+
+ public void testUnicodeEscape() throws Exception
+ {
+ assertEvalEquals("a", "#set($v = \"\\u0061\")$v");
+ }
+
+ private void assertUnescape(String expected, String escaped)
+ {
+ String unescaped = ASTStringLiteral.unescape(escaped);
+ assertEquals(expected, unescaped);
+ if (escaped.equals(expected))
+ {
+ // checking that no new string allocated, for perfomance
+ assertSame(unescaped, escaped);
+ }
+ }
+
+ public void testASTStringLiteralUnescape()
+ {
+ assertUnescape("", "");
+ assertUnescape("bebe", "bebe");
+ assertUnescape("as\\nsd", "as\\nsd");
+ assertUnescape("a", "\\u0061");
+ assertUnescape("abc", "\\u0061bc");
+ assertUnescape("\u0061bc\u0064", "\\u0061bc\\u0064");
+ assertUnescape("z\u0061bc\u0064f", "z\\u0061bc\\u0064f");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/VMLibraryTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/VMLibraryTestCase.java
new file mode 100644
index 00000000..dde8cbd7
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/VMLibraryTestCase.java
@@ -0,0 +1,325 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Macro library inclution via the Template.merge method is tested using this
+ * class.
+ */
+
+public class VMLibraryTestCase extends BaseTestCase
+{
+ /**
+ * This engine is used with local namespaces
+ */
+ private VelocityEngine ve1 = new VelocityEngine();
+
+ /**
+ * This engine is used with global namespaces
+ */
+ private VelocityEngine ve2 = new VelocityEngine();
+
+ private static final String RESULT_DIR = TEST_RESULT_DIR + "/macrolibs";
+
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/macrolibs/compare";
+
+ public VMLibraryTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ /*
+ * setup local scope for templates
+ */
+ ve1.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve1.setProperty("velocimacro.permissions.allow.inline.to.replace.global",
+ Boolean.FALSE);
+ /*
+ * Turn on the cache
+ */
+ ve1.setProperty("file.resource.loader.cache", Boolean.TRUE);
+
+ ve1.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ ve1.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve1.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ TEST_COMPARE_DIR + "/macrolibs");
+ ve1.init();
+
+ /*
+ * Set to global namespaces
+ */
+ ve2.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.FALSE);
+ ve2.setProperty("velocimacro.permissions.allow.inline.to.replace.global",
+ Boolean.TRUE);
+ /*
+ * Turn on the cache
+ */
+ ve2.setProperty("file.resource.loader.cache", Boolean.FALSE);
+
+ ve2.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ ve2.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve2.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH,
+ TEST_COMPARE_DIR + "/macrolibs");
+ ve2.init();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new TestSuite(VMLibraryTestCase.class);
+ }
+
+ /*
+ * Runs the tests with local namespace.
+ */
+ public void testVelociMacroLibWithLocalNamespace()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+ /*
+ * Clear the file before proceeding
+ */
+ File file = new File(getFileName(
+ RESULT_DIR, "vm_library_local", RESULT_FILE_EXT));
+ if (file.exists())
+ {
+ file.delete();
+ }
+
+ /*
+ * Create a file output stream for appending
+ */
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, "vm_library_local", RESULT_FILE_EXT), true);
+
+ List templateList = new ArrayList();
+ VelocityContext context = new VelocityContext();
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ templateList.add("vm_library1.vm");
+
+ Template template = ve1.getTemplate("vm_library_local.vm");
+ template.merge(context, writer, templateList);
+
+ /*
+ * remove the first template library and includes a new library
+ * with a new definition for macros
+ */
+ templateList.remove(0);
+ templateList.add("vm_library2.vm");
+ template = ve1.getTemplate("vm_library_local.vm");
+ template.merge(context, writer, templateList);
+
+ /*
+ *Show that caching is working
+ */
+ Template t1 = ve1.getTemplate("vm_library_local.vm");
+ Template t2 = ve1.getTemplate("vm_library_local.vm");
+
+ assertEquals("Both templates refer to the same object", t1, t2);
+
+ /*
+ * Remove the libraries
+ */
+ template = ve1.getTemplate("vm_library_local.vm");
+ template.merge(context, writer);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, "vm_library_local",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template did not match expected output");
+ }
+ }
+
+ /*
+ * Runs the tests with global namespace.
+ */
+ public void testVelociMacroLibWithGlobalNamespace()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+ /*
+ * Clear the file before proceeding
+ */
+ File file = new File(getFileName(
+ RESULT_DIR, "vm_library_global", RESULT_FILE_EXT));
+ if (file.exists())
+ {
+ file.delete();
+ }
+
+ /*
+ * Create a file output stream for appending
+ */
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, "vm_library_global", RESULT_FILE_EXT), true);
+
+ List templateList = new ArrayList();
+ VelocityContext context = new VelocityContext();
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ templateList.add("vm_library1.vm");
+
+ Template template = ve1.getTemplate("vm_library_global.vm");
+ template.merge(context, writer, templateList);
+
+ /*
+ * remove the first template library and includes a new library
+ * with a new definition for macros
+ */
+ templateList.remove(0);
+ templateList.add("vm_library2.vm");
+ template = ve1.getTemplate("vm_library_global.vm");
+ template.merge(context, writer, templateList);
+
+ /*
+ *Show that caching is not working (We have turned off cache)
+ */
+ Template t1 = ve2.getTemplate("vm_library_global.vm");
+ Template t2 = ve2.getTemplate("vm_library_global.vm");
+
+ assertNotSame("Defferent objects", t1, t2);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, "vm_library_global",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template did not match expected output");
+ }
+ }
+
+ /*
+ * Runs the tests with global namespace.
+ */
+ public void testVelociMacroLibWithDuplicateDefinitions()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+ /*
+ * Clear the file before proceeding
+ */
+ File file = new File(getFileName(
+ RESULT_DIR, "vm_library_duplicate", RESULT_FILE_EXT));
+ if (file.exists())
+ {
+ file.delete();
+ }
+
+ /*
+ * Create a file output stream for appending
+ */
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, "vm_library_duplicate", RESULT_FILE_EXT), true);
+
+ List templateList = new ArrayList();
+ VelocityContext context = new VelocityContext();
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ templateList.add("vm_library1.vm");
+ templateList.add("vm_library2.vm");
+
+ Template template = ve1.getTemplate("vm_library.vm");
+ template.merge(context, writer, templateList);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, "vm_library_duplicate",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template did not match expected output");
+ }
+ }
+
+ /*
+ * Test whether the literal text is given if a definition cannot be
+ * found for a macro.
+ *
+ * @throws Exception
+ */
+ public void testMacrosWithNoDefinition()
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, "vm_library", RESULT_FILE_EXT));
+
+ VelocityContext context = new VelocityContext();
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ Template template = ve1.getTemplate("vm_library.vm");
+ template.merge(context, writer, null);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ /*
+ * outputs the macro calls
+ */
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, "vm_library",
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ fail("Processed template did not match expected output");
+ }
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/VarargMethodsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/VarargMethodsTestCase.java
new file mode 100644
index 00000000..a87bba8e
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/VarargMethodsTestCase.java
@@ -0,0 +1,278 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+
+/**
+ * Used to check that vararg method calls on references work properly
+ */
+public class VarargMethodsTestCase extends BaseTestCase
+{
+ public VarargMethodsTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("nice", new NiceTool());
+ context.put("nasty", new NastyTool());
+ context.put("objects", new Object[] { this, VelocityContext.class });
+ context.put("strings", new String[] { "one", "two" });
+ context.put("doubles", new double[] { 1.5, 2.5 });
+ context.put("float", 1f);
+ context.put("ints", new int[] { 1, 2 });
+ }
+
+ public void testStrings()
+ {
+ assertEvalEquals("onetwo", "$nice.var($strings)");
+ assertEvalEquals("onetwo", "$nice.var('one','two')");
+ assertEvalEquals("one", "$nice.var('one')");
+ assertEvalEquals("", "$nice.var()");
+ }
+
+ public void testDoubles()
+ {
+ assertEvalEquals("4.0", "$nice.add($doubles)");
+ assertEvalEquals("3.0", "$nice.add(1,2)");
+ assertEvalEquals("1.0", "$nice.add(1)");
+ assertEvalEquals("0.0", "$nice.add()");
+ }
+
+ public void testFloatToDoubleVarArg()
+ {
+ assertEvalEquals("1.0", "$nice.add($float)");
+ }
+
+ public void testStringVsStrings()
+ {
+ assertEvalEquals("onlyone", "$nasty.var('one')");
+ assertEvalEquals("onlynull", "$nasty.var($null)");
+ assertEvalEquals("", "$nasty.var()");
+ }
+
+ public void testIntVsDoubles()
+ {
+ assertEvalEquals("1", "$nasty.add(1)");
+ assertEvalEquals("1.0", "$nasty.add(1.0)");
+ assertEvalEquals("3.0", "$nasty.add(1.0,2)");
+ }
+
+ public void testInts()
+ {
+ assertEvalEquals("3", "$nasty.add($ints)");
+ assertEvalEquals("3", "$nasty.add(1,2)");
+ assertEvalEquals("1", "$nasty.add(1)");
+ // add(int[]) wins because it is "more specific"
+ assertEvalEquals("0", "$nasty.add()");
+ }
+
+ public void testStringsVsObjectsAKASubclassVararg()
+ {
+ assertEvalEquals("objects", "$nice.test($objects)");
+ assertEvalEquals("objects", "$nice.test($nice,$nasty,$ints)");
+ assertEvalEquals("strings", "$nice.test('foo')");
+ }
+
+ public void testObjectVarArgVsObjectEtc()
+ {
+ assertEvalEquals("object,string", "$nasty.test($nice,'foo')");
+ }
+
+ public void testObjectVarArgVsObjectVelocity605()
+ {
+ assertEvalEquals("string", "$nasty.test('joe')");
+ assertEvalEquals("object", "$nasty.test($nice)");
+ }
+
+ public void testNoArgs()
+ {
+ assertEvalEquals("noargs", "$nasty.test()");
+ }
+
+ public void testPassingArrayToVarArgVelocity642()
+ {
+ assertEvalEquals("[one, two]", "$nasty.test642($strings)");
+ assertEvalEquals("[1, 2]", "#set( $list = [1..2] )$nasty.test642($list.toArray())");
+ }
+
+ public void testNullToPrimitiveVarArg()
+ {
+ assertEvalEquals("int[]", "$nasty.test649($null)");
+ }
+
+ public void testArgsBeforeVarargWithNoArgs()
+ {
+ assertEvalEquals("String,String,Object[]", "$nasty.test651('a','b')");
+ }
+
+ public void testVelocity651()
+ {
+ assertEvalEquals("String,List", "$nasty.test651('test',['TEST'])");
+ }
+
+ public void testMax()
+ {
+ assertEvalEquals("4", "$nasty.max(4, 3.5)");
+ }
+
+ public static class NiceTool
+ {
+ public String var(String[] ss)
+ {
+ StringBuilder out = new StringBuilder();
+ for (String s : ss)
+ {
+ out.append(s);
+ }
+ return out.toString();
+ }
+
+ public double add(double[] dd)
+ {
+ double total = 0;
+ for (double aDd : dd)
+ {
+ total += aDd;
+ }
+ return total;
+ }
+
+ public String test(Object[] oo)
+ {
+ return "objects";
+ }
+
+ public String test(String[] oo)
+ {
+ return "strings";
+ }
+
+ }
+
+ public static class NastyTool extends NiceTool
+ {
+ public String var(String s)
+ {
+ return "only"+s;
+ }
+
+ public int add(int[] ii)
+ {
+ int total = 0;
+ for (int anIi : ii)
+ {
+ total += anIi;
+ }
+ return total;
+ }
+
+ public int add(int i)
+ {
+ return i;
+ }
+
+ public String test()
+ {
+ return "noargs";
+ }
+
+ public Object test(Object arg)
+ {
+ return "object";
+ }
+
+ public Object test(String arg)
+ {
+ return "string";
+ }
+
+ @Override
+ public String test(Object[] array)
+ {
+ return "object[]";
+ }
+
+ public String test(Object object, String property)
+ {
+ return "object,string";
+ }
+
+ public String test642(Object[] array)
+ {
+ //JDK5: return Arrays.deepToString(array);
+ if (array == null)
+ {
+ return null;
+ }
+ StringBuilder o = new StringBuilder("[");
+ for (int i=0; i < array.length; i++)
+ {
+ if (i > 0)
+ {
+ o.append(", ");
+ }
+ o.append((array[i]));
+ }
+ o.append("]");
+ return o.toString();
+ }
+
+ public String test649(int[] array)
+ {
+ return "int[]";
+ }
+
+ public String test651(String s, String s2, Object[] args)
+ {
+ return "String,String,Object[]";
+ }
+
+ public String test651(String s, java.util.List l)
+ {
+ return "String,List";
+ }
+
+ public Number max(Number n1, Number n2) { return max(new Number[] { n1, n2 }); }
+
+ public Number max(Number[] numbers)
+ {
+ if (numbers.length == 0) return null;
+ int minindex = -1, i = 0;
+ double val = Double.MIN_VALUE;
+ for (Number n : numbers)
+ {
+ if (n.floatValue() > val)
+ {
+ minindex = i;
+ val = n.floatValue();
+ }
+ ++i;
+ }
+ if (minindex < 0) minindex = 0;
+ return numbers[minindex];
+ }
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroBCModeTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroBCModeTestCase.java
new file mode 100644
index 00000000..e41fe434
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroBCModeTestCase.java
@@ -0,0 +1,100 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+/**
+ * This class tests the mode where velocimacros do preserve arguments literals
+ */
+
+public class VelocimacroBCModeTestCase extends BaseTestCase
+{
+ private static final String BASE_DIR = TEST_COMPARE_DIR + "/bc_mode";
+ private static final String CMP_DIR = BASE_DIR + "/compare";
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/bc_mode";
+
+ public VelocimacroBCModeTestCase(final String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ boolean bcMode = !getName().contains("NoPreserve");
+ engine.setProperty(RuntimeConstants.VM_ENABLE_BC_MODE, bcMode);
+ engine.setProperty("file.resource.loader.path", TEST_COMPARE_DIR + "/bc_mode");
+ }
+
+ public void testPreserveLiterals()
+ {
+ assertEvalEquals("$bar","#macro(m $foo)$foo#end#m($bar)");
+ }
+
+ public void testGlobalDefaults()
+ {
+ assertEvalEquals("foo","#macro(m $foo)$foo#end#set($foo='foo')#m()");
+ }
+
+ public void testVariousCasesPreserve() throws Exception
+ {
+ doTestVariousCases("bc_mode_enabled");
+ }
+
+ public void testVariousCasesNoPreserve() throws Exception
+ {
+ doTestVariousCases("bc_mode_disabled");
+ }
+
+ private void doTestVariousCases(String compare_ext) throws Exception
+ {
+ assureResultsDirectoryExists(RESULTS_DIR);
+ String basefilename = "test_bc_mode";
+ Template template = engine.getTemplate( getFileName(null, basefilename, "vtl") );
+ context = new VelocityContext();
+ FileOutputStream fos;
+ Writer fwriter;
+
+ fos = new FileOutputStream (getFileName(RESULTS_DIR, basefilename, RESULT_FILE_EXT));
+
+ fwriter = new BufferedWriter( new OutputStreamWriter(fos) );
+
+ template.merge(context, fwriter);
+ fwriter.flush();
+ fwriter.close();
+
+ if (!isMatch(RESULTS_DIR, CMP_DIR, basefilename, RESULT_FILE_EXT, compare_ext))
+ {
+ String result = getFileContents(RESULTS_DIR, basefilename, RESULT_FILE_EXT);
+ String compare = getFileContents(CMP_DIR, basefilename, compare_ext);
+
+ assertEquals(compare, result);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroTestCase.java
new file mode 100644
index 00000000..445cf44a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocimacroTestCase.java
@@ -0,0 +1,135 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.MacroOverflowException;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.StringWriter;
+
+/**
+ * This class tests strange Velocimacro issues.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class VelocimacroTestCase extends TestCase
+{
+ private String template1 = "#macro(foo $a)$a#end #macro(bar $b)#foo($b)#end #foreach($i in [1..3])#if($i == 3)#foo($i)#else#bar($i)#end#end";
+ private String result1 = " 123";
+ private String template2 = "#macro(bar $a)#set($a = $a + 1)$a#bar($a)#end#bar(0)";
+ private String template3 = "#macro(baz $a)#set($a = $a + 1)$a#inner($a)#end#macro(inner $b)#baz($b)#end#baz(0)";
+ private String template4 = "#macro(bad $a)#set($a = $a + 1)$a#inside($a)#end#macro(inside $b)#loop($b)#end#macro(loop $c)#bad($c)#end#bad(0)";
+
+ public VelocimacroTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ /*
+ * setup local scope for templates
+ */
+ Velocity.reset();
+ Velocity.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ Velocity.setProperty( Velocity.VM_MAX_DEPTH, 5);
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ Velocity.init();
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(VelocimacroTestCase.class);
+ }
+
+ /**
+ * Runs the test.
+ */
+ public void testVelociMacro ()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+
+ StringWriter writer = new StringWriter();
+ Velocity.evaluate(context, writer, "vm_chain1", template1);
+
+ String out = writer.toString();
+
+ if( !result1.equals( out ) )
+ {
+ fail("output incorrect.");
+ }
+ }
+
+ /**
+ * Test case for evaluating max calling depths of macros
+ */
+ public void testVelociMacroCallMax()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+ StringWriter writer = new StringWriter();
+
+ try
+ {
+ Velocity.evaluate(context, writer, "vm_chain2", template2);
+ fail("Did not exceed max macro call depth as expected");
+ }
+ catch (MacroOverflowException e)
+ {
+ assertEquals("Max calling depth of 5 was exceeded in macro 'bar'"+
+ " with Call Stack:bar->bar->bar->bar->bar at vm_chain2[line 1, column 15]",
+ e.getMessage());
+ }
+
+ try
+ {
+ Velocity.evaluate(context, writer, "vm_chain3", template3);
+ fail("Did not exceed max macro call depth as expected");
+ }
+ catch (MacroOverflowException e)
+ {
+ assertEquals("Max calling depth of 5 was exceeded in macro 'inner'"+
+ " with Call Stack:baz->inner->baz->inner->baz at vm_chain3[line 1, column 64]",
+ e.getMessage());
+ }
+
+ try
+ {
+ Velocity.evaluate(context, writer, "vm_chain4", template4);
+ fail("Did not exceed max macro call depth as expected");
+ }
+ catch (MacroOverflowException e)
+ {
+ assertEquals("Max calling depth of 5 was exceeded in macro 'loop'"+
+ " with Call Stack:bad->inside->loop->bad->inside at vm_chain4[line 1, column 94]",
+ e.getMessage());
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocityAppTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocityAppTestCase.java
new file mode 100644
index 00000000..ecc9f8fc
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/VelocityAppTestCase.java
@@ -0,0 +1,80 @@
+package org.apache.velocity.test;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.Velocity;
+
+import java.io.StringWriter;
+
+/**
+ * This class is intended to test the app.Velocity.java class.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
+ * @version $Id$
+ */
+public class VelocityAppTestCase extends BaseTestCase implements TemplateTestBase
+{
+ private StringWriter compare1 = new StringWriter();
+ private String input1 = "My name is $name -> $Floog";
+ private String result1 = "My name is jason -> floogie woogie";
+
+ public VelocityAppTestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testVelocityApp()
+ throws Exception
+ {
+ engine.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ engine.init();
+
+ // the usage of engine here is equivalent to using static calls to Velocity. class
+
+ VelocityContext context = new VelocityContext();
+ context.put("name", "jason");
+ context.put("Floog", "floogie woogie");
+
+ String cmp = "Hello jason! Nice floogie woogie!";
+
+ engine.evaluate(context, compare1, "evaltest", input1);
+ if (!result1.equals(compare1.toString()))
+ {
+ fail("Output 1 incorrect.");
+ }
+
+ StringWriter result2 = new StringWriter();
+ engine.mergeTemplate("mergethis.vm", "UTF-8", context, result2);
+ if (!result2.toString().equals(cmp))
+ {
+ fail("Output 2 incorrect.");
+ }
+
+ StringWriter result3 = new StringWriter();
+ engine.invokeVelocimacro("floog", "test", new String[]{"name", "Floog"}, context, result3);
+
+ if (!result3.toString().equals(cmp))
+ {
+ fail("Output 3 incorrect.");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/WrappedExceptionTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/WrappedExceptionTestCase.java
new file mode 100644
index 00000000..1c8bd11f
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/WrappedExceptionTestCase.java
@@ -0,0 +1,84 @@
+package org.apache.velocity.test;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.test.provider.TestProvider;
+
+import java.io.StringWriter;
+
+/**
+ * Test thrown exceptions include a proper cause (under JDK 1.4+).
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class WrappedExceptionTestCase extends BaseTestCase implements TemplateTestBase
+{
+ VelocityEngine ve;
+
+ /**
+ * Default constructor.
+ */
+ public WrappedExceptionTestCase(String name)
+ {
+ super(name);
+ }
+
+ public static Test suite ()
+ {
+ return new TestSuite(WrappedExceptionTestCase.class);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ve = new VelocityEngine();
+ ve.init();
+ }
+
+
+ public void testMethodException() throws Exception
+ {
+
+ // accumulate a list of invalid references
+ Context context = new VelocityContext();
+ StringWriter writer = new StringWriter();
+ context.put("test",new TestProvider());
+
+ try
+ {
+ ve.evaluate(context,writer,"test","$test.getThrow()");
+ fail ("expected an exception");
+ }
+ catch (MethodInvocationException E)
+ {
+ assertEquals(Exception.class,E.getCause().getClass());
+ assertEquals("From getThrow()",E.getCause().getMessage());
+ }
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler1.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler1.java
new file mode 100644
index 00000000..52220557
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler1.java
@@ -0,0 +1,75 @@
+package org.apache.velocity.test.eventhandler;
+
+/*
+ * 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.IncludeEventHandler;
+import org.apache.velocity.app.event.MethodExceptionEventHandler;
+import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.util.introspection.Info;
+
+import java.util.Locale;
+
+/**
+ * This is a test set of event handlers, used to test event handler sequences.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class Handler1
+ implements ReferenceInsertionEventHandler, MethodExceptionEventHandler, IncludeEventHandler {
+
+ /**
+ * display output twice, once uppercase and once lowercase
+ */
+ @Override
+ public Object referenceInsert(Context context, String reference, Object value)
+ {
+ if (value == null)
+ return null;
+ else
+ return value.toString().toUpperCase(Locale.ROOT) + value.toString().toLowerCase(Locale.ROOT);
+ }
+
+ /**
+ * throw the exception
+ */
+ @Override
+ public Object methodException(Context context, Class<?> claz, String method, Exception e, Info info)
+ {
+ throw new RuntimeException(e);
+ }
+
+ /*
+ * redirect all requests to a page "login.vm" (simulates access control).
+ */
+ @Override
+ public String includeEvent(
+ Context context,
+ String includeResourcePath,
+ String currentResourcePath,
+ String directiveName)
+ {
+
+ return "notfound.vm";
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler2.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler2.java
new file mode 100644
index 00000000..033c9655
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/eventhandler/Handler2.java
@@ -0,0 +1,75 @@
+package org.apache.velocity.test.eventhandler;
+
+/*
+ * 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.IncludeEventHandler;
+import org.apache.velocity.app.event.MethodExceptionEventHandler;
+import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.util.introspection.Info;
+
+import java.util.Locale;
+
+/**
+ * This is a test set of event handlers, used to test event handler sequences.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class Handler2
+ implements ReferenceInsertionEventHandler, MethodExceptionEventHandler, IncludeEventHandler {
+
+ /**
+ * convert output to upper case
+ */
+ @Override
+ public Object referenceInsert(Context context, String reference, Object value)
+ {
+ if (value == null)
+ return null;
+ else
+ return value.toString().toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * print the exception
+ */
+ @Override
+ public Object methodException(Context context, Class<?> claz, String method, Exception e, Info info)
+ {
+ return "Exception: " + e;
+ }
+
+ /*
+ * redirect all requests to a new directory "subdir" (simulates localization).
+ */
+ @Override
+ public String includeEvent(
+ Context context,
+ String includeResourcePath,
+ String currentResourcePath,
+ String directiveName)
+ {
+
+ return "subdir/" + includeResourcePath;
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/StackOverflow32805217TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/StackOverflow32805217TestCase.java
new file mode 100755
index 00000000..d70e0bcd
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/StackOverflow32805217TestCase.java
@@ -0,0 +1,39 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests bugfix for http://stackoverflow.com/questions/32805217/bug-or-hidden-feature-in-apache-velocity
+ */
+public class StackOverflow32805217TestCase extends BaseTestCase
+{
+ public StackOverflow32805217TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testIt()
+ {
+ assertEvalEquals("$map{value}", "$map{value}");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java
new file mode 100644
index 00000000..529683ff
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/VelTools66TestCase.java
@@ -0,0 +1,181 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.Introspector;
+
+import java.lang.reflect.Method;
+import java.security.AccessControlException;
+import java.security.Permission;
+
+/**
+ * Test Case for <a href="https://issues.apache.org/jira/browse/VELTOOLS-66">Velocity Tools Issue 66</a>.
+ */
+public class VelTools66TestCase
+ extends BaseTestCase
+{
+ protected static boolean DEBUG = false;
+
+ public VelTools66TestCase(final String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(VelTools66TestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ Velocity.setProperty(
+ Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ System.setSecurityManager(new TestSecurityManager());
+
+ }
+
+ protected static void log(String out)
+ {
+ Velocity.getLog().debug(out);
+ if (DEBUG)
+ {
+ System.out.println(out);
+ }
+ }
+
+ @Override
+ public void tearDown()
+ {
+ System.setSecurityManager(null);
+ }
+
+ public void testVelTools66()
+ throws Exception
+ {
+ /* the testcase is obsolete in JDK 8, as SystemManager.checkMemberAccess is not anymore called
+ * by Class.getMethods() */
+
+ String [] javaVersionFields = System.getProperty("java.version").split("\\.");
+ int javaVersion = Integer.parseInt(javaVersionFields[0]);
+ if (javaVersion == 1)
+ {
+ javaVersion = Integer.parseInt(javaVersionFields[1]);
+ }
+
+ if (javaVersion >= 8)
+ {
+ return;
+ }
+
+ Method verifyMethod = TestInterface.class.getMethod("getTestValue");
+
+ RuntimeInstance ri = new RuntimeInstance();
+ log = new TestLogger(false, false);
+ Introspector introspector = new Introspector(log);
+
+ Method testMethod = introspector.getMethod(TestObject.class, "getTestValue", new Object[0]);
+ assertNotNull(testMethod);
+ assertEquals("Method object does not match!", verifyMethod, testMethod);
+ }
+
+ public interface TestInterface
+ {
+ String getTestValue();
+
+ void setTestValue(String testValue);
+ }
+
+ public static final class TestObject
+ implements TestInterface
+ {
+ String testValue = null;
+
+ public TestObject()
+ {
+ }
+
+ @Override
+ public String getTestValue()
+ {
+ return testValue;
+ }
+
+ @Override
+ public void setTestValue(final String testValue)
+ {
+ this.testValue = testValue;
+ }
+ }
+
+ public static final class TestSecurityManager extends SecurityManager
+ {
+ private final Class<?> clazz = TestObject.class;
+
+ public TestSecurityManager()
+ {
+ super();
+ }
+
+ public void checkMemberAccess(final Class<?> c, final int i)
+ {
+ log("checkMemberAccess(" + c.getName() + ", " + i + ")");
+
+ if (c.equals(clazz))
+ {
+ throw new AccessControlException("You are not allowed to access TestObject directly!");
+ }
+ }
+
+ @Override
+ public void checkRead(final String file)
+ {
+ log("checkRead(" + file + ")");
+ }
+
+ @Override
+ public void checkPackageAccess(final String s)
+ {
+ log("checkPackageAccess(" + s + ")");
+ }
+
+ @Override
+ public void checkPropertyAccess(final String s)
+ {
+ log("checkPropertyAccess(" + s + ")");
+ }
+
+ @Override
+ public void checkPermission(final Permission p)
+ {
+ log("checkPermission(" + p + ")");
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity532TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity532TestCase.java
new file mode 100755
index 00000000..16eaef1c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity532TestCase.java
@@ -0,0 +1,52 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-532.
+ */
+public class Velocity532TestCase extends BaseTestCase
+{
+ public Velocity532TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void test532()
+ {
+ String template = "#macro( test )$foreach.count#end"+
+ "#foreach( $i in [1..5] )#test()#end";
+ assertEvalEquals("12345", template);
+ }
+
+ public void test532b()
+ {
+ // try something a little more like Matt's example
+ String template = "#macro( test $baz )"+
+ "#if( $foo == $null )"+
+ "#if( $foreach.count == 3 )bar#end"+
+ "#end#end"+
+ "#foreach( $i in [1..5] )#test($i)#end";
+ assertEvalEquals("bar", template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity537TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity537TestCase.java
new file mode 100755
index 00000000..02ee5efb
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity537TestCase.java
@@ -0,0 +1,121 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Test Case for <a href="https://issues.apache.org/jira/browse/VELOCITY-537">Velocity Issue 537</a>.
+ */
+public class Velocity537TestCase extends BaseTestCase
+{
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/issues/velocity-537";
+
+ /**
+ * Template Directory
+ */
+ private static final String TEMPLATE_DIR = TEST_COMPARE_DIR + "/issues/velocity-537/templates";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/issues/velocity-537/compare";
+
+ public Velocity537TestCase(final String name) throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(Velocity537TestCase.class);
+ }
+
+ private VelocityEngine velocityEngine;
+ @Override
+ public void setUp() throws Exception
+ {
+
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ velocityEngine = new VelocityEngine();
+ velocityEngine.addProperty(Velocity.FILE_RESOURCE_LOADER_PATH, TEMPLATE_DIR);
+
+ velocityEngine.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ velocityEngine.init();
+ }
+
+ public void testVelocity537() throws Exception
+ {
+ executeTest("velocity537.vm");
+ }
+
+ public void testVelocity537Again() throws Exception
+ {
+ executeTest("velocity537b.vm");
+ }
+
+ protected Template executeTest(final String templateName) throws Exception
+ {
+ Template template = velocityEngine.getTemplate(templateName);
+
+ FileOutputStream fos = new FileOutputStream(getFileName(RESULTS_DIR, templateName, RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityContext context = new VelocityContext();
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, templateName, RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ // just to be useful, output the output in the fail message
+ StringWriter out = new StringWriter();
+ template.merge(context, out);
+
+ String compare = getFileContents(COMPARE_DIR, templateName, CMP_FILE_EXT);
+
+ fail("Output incorrect for Template: " + templateName + ": \""+out+"\""+
+ "; it did not match: \""+compare+"\"");
+ }
+
+ return template;
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity544TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity544TestCase.java
new file mode 100644
index 00000000..1018208c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity544TestCase.java
@@ -0,0 +1,75 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * @see <a href="https://issues.apache.org/jira/browse/VELOCITY-544">VELOCITY-544</a>
+ */
+public class Velocity544TestCase
+ extends BaseTestCase
+{
+ public Velocity544TestCase(final String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(Velocity544TestCase.class);
+ }
+
+ public void testBooleanPropertyExecutor()
+ throws Exception
+ {
+ context.put("foobarTrue", new Foobar(true));
+ context.put("foobarFalse", new Foobar(false));
+
+ String template = "$foobarTrue.True $foobarFalse.True $foobarTrue.TrueObject $foobarFalse.TrueObject";
+
+ String result = evaluate(template);
+
+ assertEquals("true false true false", result);
+ }
+
+ public static class Foobar
+ {
+ private boolean value;
+
+ public Foobar(boolean value)
+ {
+ this.value = value;
+ }
+
+ public boolean isTrue()
+ {
+ return(value);
+ }
+
+ public Boolean isTrueObject()
+ {
+ return(value);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity579TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity579TestCase.java
new file mode 100755
index 00000000..3918178a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity579TestCase.java
@@ -0,0 +1,82 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-579 and with some related stuff
+ * from VELOCITY-70 thrown in.
+ */
+public class Velocity579TestCase extends BaseTestCase
+{
+ public Velocity579TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testPublicMethodInPrivateImplOfPublicInterface()
+ {
+ context.put("foo", new Foobar());
+ assertEvalEquals("bar", "$foo.foo('bar')");
+ assertEvalEquals("$foo.bar()", "$foo.bar()");
+ }
+
+ public void testPublicMethodInheritedFromPrivateClass() throws Exception
+ {
+ context.put("bar", new MyBar());
+ // ugly hack to avoid failed test when running JDK 1.5 or earlier
+ String javaVersion = System.getProperty("java.version");
+ if (javaVersion.startsWith("1.6"))
+ assertEvalEquals("bar", "$bar.bar()");
+ }
+
+ public interface Foo
+ {
+ String foo(String s);
+ }
+
+ private static abstract class FooImpl implements Foo
+ {
+ @Override
+ public String foo(String s)
+ {
+ return s == null ? "foo" : s;
+ }
+ }
+
+ private static class Foobar extends FooImpl
+ {
+ public String bar()
+ {
+ return "bar";
+ }
+ }
+
+ public static class MyBar extends Foobar
+ {
+ @Override
+ public String bar()
+ {
+ return super.bar();
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity580TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity580TestCase.java
new file mode 100755
index 00000000..14904163
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity580TestCase.java
@@ -0,0 +1,116 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeSingleton;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Test Case for <a href="https://issues.apache.org/jira/browse/VELOCITY-580">Velocity Issue 580</a>.
+ */
+public class Velocity580TestCase extends BaseTestCase
+{
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/issues/velocity-580";
+
+ /**
+ * Template Directory
+ */
+ private static final String TEMPLATE_DIR = TEST_COMPARE_DIR + "/issues/velocity-580/templates";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/issues/velocity-580/compare";
+
+ public Velocity580TestCase(final String name) throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(Velocity580TestCase.class);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ Velocity.reset();
+
+ Velocity.addProperty(Velocity.FILE_RESOURCE_LOADER_PATH, TEMPLATE_DIR);
+
+ Velocity.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+
+ Velocity.init();
+ }
+
+ public void testVelocity580() throws Exception
+ {
+ executeTest("velocity580.vm");
+ }
+
+ protected Template executeTest(final String templateName) throws Exception
+ {
+ Template template = RuntimeSingleton.getTemplate(templateName);
+
+ FileOutputStream fos = new FileOutputStream(getFileName(RESULTS_DIR, templateName, RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityContext context = new VelocityContext();
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, templateName, RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ // just to be useful, output the output in the fail message
+ StringWriter out = new StringWriter();
+ template.merge(context, out);
+
+ String compare = getFileContents(COMPARE_DIR, templateName, CMP_FILE_EXT);
+
+ fail("Output incorrect for Template: " + templateName + ": \""+out+"\""+
+ "; it did not match: \""+compare+"\"");
+ }
+
+ return template;
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity587TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity587TestCase.java
new file mode 100755
index 00000000..7d5da4c2
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity587TestCase.java
@@ -0,0 +1,64 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-587.
+ */
+public class Velocity587TestCase extends BaseTestCase
+{
+ public Velocity587TestCase(String name)
+ {
+ super(name);
+ }
+
+ // remember, they're all doubled, since java will use them as escapes first.
+ public void testLiteralTwoBackslashes()
+ {
+ String template = "#set( $bs2 = \'\\\\\' )$bs2";
+ String expected = "\\\\";
+ assertEvalEquals(expected, template);
+ }
+
+ public void testLiteralOneBackslash()
+ {
+ String template = "#set( $bs = \'\\\' )$bs";
+ String expected = "\\";
+ assertEvalEquals(expected, template);
+ }
+
+ // remember, they're all doubled, since java will use them as escapes first.
+ public void testInterpolatedTwoBackslashes()
+ {
+ String template = "#set( $bs2 = \"\\\\\" )$bs2";
+ String expected = "\\\\";
+ assertEvalEquals(expected, template);
+ }
+
+ public void testInterpolatedOneBackslash()
+ {
+ String template = "#set( $bs = \"\\\" )$bs";
+ String expected = "\\";
+ assertEvalEquals(expected, template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity589TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity589TestCase.java
new file mode 100755
index 00000000..6502dc26
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity589TestCase.java
@@ -0,0 +1,40 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-589.
+ */
+public class Velocity589TestCase extends BaseTestCase
+{
+ public Velocity589TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testIt()
+ {
+ context.put("myId", "test");
+ assertEvalEquals("#test_bg", "#${myId}_bg");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity614TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity614TestCase.java
new file mode 100755
index 00000000..746b40a6
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity614TestCase.java
@@ -0,0 +1,90 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.TemplateInitException;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-614.
+ */
+public class Velocity614TestCase extends BaseTestCase
+{
+ public Velocity614TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testSchmoo()
+ {
+ String template = "#something(Stuff)";
+ assertEvalEquals(template, template);
+ }
+
+ public void testEscapeSchmooButNotReallySinceSchmooHasNoEscaping()
+ {
+ String template = "\\#something(Stuff)";
+ assertEvalEquals(template, template);
+ }
+
+ public void testEscapeMacroWithBadArg()
+ {
+ String template = "#macro( evil $arg )$arg#end \\#evil(bar)";
+ assertEvalEquals(" #evil(bar)", template);
+ }
+
+ public void testEarlyDefinedMacroWithBadArg()
+ {
+ // make sure this still bombs, but don't spam sysout
+ log.off();
+ assertEvalException("#macro( evil $arg )$arg#end #evil(bar)");
+ log.on();
+ }
+
+ // just make sure this doesn't get broken
+ public void testLateDefinedMacroWithGoodArg()
+ {
+ String good = "#good('bar') #macro( good $arg )$arg#end";
+ assertEvalEquals("bar ", good);
+ }
+
+ public void testDirectivesWithBadArg()
+ {
+ // make sure these all still bomb, but don't spam sysout
+ log.off();
+ assertEvalException("#foreach(Stuff in That)foo#end");
+ assertEvalException("#include(Stuff)");
+ assertEvalException("#parse(Stuff)");
+ assertEvalException("#define(Stuff)foo#end");
+ assertEvalException("#macro( name Stuff)foo#end");
+ assertEvalException("#foreach($i in [1..3])#break(Stuff)#end");
+ assertEvalException("#literal(Stuff)foo#end");
+ assertEvalException("#evaluate(Stuff)", ParseErrorException.class);
+ log.on();
+ }
+
+ public void testLateDefinedMacroWithBadArg()
+ {
+ String evil = "#evil(bar) #macro( evil $arg )$arg#end";
+ assertEvalException(evil, TemplateInitException.class);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity615TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity615TestCase.java
new file mode 100755
index 00000000..fec7796d
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity615TestCase.java
@@ -0,0 +1,139 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-615.
+ */
+public class Velocity615TestCase extends BaseTestCase
+{
+ public Velocity615TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty("velocimacro.permissions.allow.inline", "true");
+ engine.setProperty("velocimacro.permissions.allow.inline.to.replace.global", "false");
+ engine.setProperty("velocimacro.permissions.allow.inline.local.scope", "true");
+ engine.setProperty("velocimacro.arguments.strict", "true");
+ engine.setProperty("space.gobbling", "bc");
+ }
+
+ public void testIt()
+ {
+ String template = "#set( $foo = 'old' )"+
+ "#macro( test $foo )"+
+ "#set( $foo = \"new $foo \" )"+
+ "$foo"+
+ "#end"+
+ "#test( 'foo' )"+
+ "$foo";
+ assertEvalEquals("new foo new foo ", template);
+ }
+
+ public void testForIrrationallyFearedRelatedPossibleProblem()
+ {
+ context.put("i", new Inc());
+ String template = "#macro( test $a )"+
+ "$a"+
+ "$a"+
+ "#end"+
+ "#test( \"$i\" )$i";
+ assertEvalEquals("001", template);
+ }
+
+ public void testForIrrationallyFearedRelatedPossibleProblem2()
+ {
+ context.put("i", new Inc());
+ String template = "#macro( test $a )"+
+ "#set( $a = 'a' )"+
+ "$a"+
+ "$a"+
+ "#end"+
+ "#test( \"$i\" )$i";
+ assertEvalEquals("aa1", template);
+ }
+
+ public void testForIrrationallyFearedRelatedPossibleProblem3()
+ {
+ context.put("i", new Inc());
+ String template = "#macro( test $a )"+
+ "$a"+
+ "$a"+
+ "#end"+
+ "#test( $i )$i";
+ assertEvalEquals("012", template);
+ }
+
+ public void testForIrrationallyFearedRelatedPossibleProblem4()
+ {
+ context.put("i", new Inc());
+ String template = "#macro( test $a )"+
+ "$a"+
+ "$a"+
+ "#end"+
+ "#test( $i.plus() )$i";
+ assertEvalEquals("001", template);
+ }
+
+ public void testForIrrationallyFearedRelatedPossibleProblem5()
+ {
+ context.put("i", new Inc());
+ String template = "#macro( test $a )"+
+ "#set( $a = $i )"+
+ "$a"+
+ "$a"+
+ "#end"+
+ "#test( 'a' )$i";
+ assertEvalEquals("012", template);
+ }
+
+ public void testVelocity681()
+ {
+ String template = "#macro(myMacro $result)"+
+ " #set($result = 'some value')"+
+ "#end"+
+ "#myMacro($x)"+
+ "$x";
+ assertEvalEquals("$x", template);
+ }
+
+ public static class Inc
+ {
+ private int i=0;
+
+ public int plus()
+ {
+ return i++;
+ }
+
+ public String toString()
+ {
+ return String.valueOf(i++);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity616TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity616TestCase.java
new file mode 100755
index 00000000..e2974e26
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity616TestCase.java
@@ -0,0 +1,70 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-616.
+ */
+public class Velocity616TestCase extends BaseTestCase
+{
+ public Velocity616TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ context.put("bar", "bar");
+ context.put("foo", Boolean.FALSE);
+ }
+
+ public void testIfNoBrackets()
+ {
+ String template = "\\#if ($foo) \\$bar \\#end";
+ String expected = "#if (false) $bar #end";
+ assertEvalEquals(expected, template);
+ }
+
+ public void testForeachBrackets()
+ {
+ String template = "\\#{foreach}( $i in [1..3] )$i\\#{end}";
+ String expected = "#{foreach}( $i in [1..3] )$i#{end}";
+ assertEvalEquals(expected, template);
+ }
+
+ public void testIfBrackets()
+ {
+ String template = "\\#{if} ($foo) \\$bar \\#{end}";
+ String expected = "#{if} (false) $bar #{end}";
+ assertEvalEquals(expected, template);
+ }
+
+ public void testIfBracketsOnEndOnly()
+ {
+ String template = "\\#if( $foo ) \\$bar \\#{end}";
+ String expected = "#if( false ) $bar #{end}";
+ assertEvalEquals(expected, template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity625TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity625TestCase.java
new file mode 100755
index 00000000..bdbf43fe
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity625TestCase.java
@@ -0,0 +1,40 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-625.
+ */
+public class Velocity625TestCase extends BaseTestCase
+{
+ public Velocity625TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void test1()
+ {
+ String template = "#macro(test $a $b)test#end#test('x')";
+ assertEvalEquals("test", template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity627TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity627TestCase.java
new file mode 100644
index 00000000..faff4cad
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity627TestCase.java
@@ -0,0 +1,49 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-627. Make sure Foreach
+ * Error message reports correct line numbers.
+ */
+
+public class Velocity627TestCase extends BaseTestCase
+{
+ public Velocity627TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.SKIP_INVALID_ITERATOR, Boolean.FALSE);
+ }
+
+ public void test627()
+ {
+ // Make sure the error ouput contains "line 3, column 16"
+ assertEvalExceptionAt("##\n##\n#foreach($i in \"junk\")blaa#end", 3, 16);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity629TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity629TestCase.java
new file mode 100644
index 00000000..aa9132cf
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity629TestCase.java
@@ -0,0 +1,55 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-629. Make sure string literals
+ * Error message reports correct line and column numbers.
+ */
+public class Velocity629TestCase extends BaseTestCase
+{
+ public Velocity629TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void test629()
+ {
+ String template = "##\n"+
+ "##\n"+
+ "#set($list=[1])#set($x=\"\n"+
+ "$list.get(1)\n"+
+ "\")";
+ // Make sure the error ouput contains "line 4, column 7" if not throw
+ assertEvalExceptionAt(template, 4, 7);
+
+ template = "##\n"+
+ "##\n"+
+ "#set($x=\"#if\")";
+ assertEvalExceptionAt(template, 3, 9);
+
+ template = "##\n"+
+ "##\n"+
+ "#macro(test $i)$i#end#set($list=[1])#test(\"$list.get(1)\")";
+ assertEvalExceptionAt(template, 3, 50);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity62TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity62TestCase.java
new file mode 100755
index 00000000..e76a18bd
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity62TestCase.java
@@ -0,0 +1,63 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-62.
+ */
+public class Velocity62TestCase extends BaseTestCase
+{
+ public Velocity62TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ context.put("foo", "foo");
+ }
+
+ public void testNested()
+ {
+ String template = "#macro( outer )#set( $foo = 'bar' )#inner()#end"+
+ "#macro( inner )$foo#end"+
+ "#inner()#outer()#inner()";
+ assertEvalEquals("foobarbar", template);
+ }
+
+ public void testRecursive()
+ {
+ context.put("i", 1);
+ String template = "#macro(recurse $i)"+
+ "$i"+
+ "#if( $i < 5 )"+
+ "#set( $i = $i + 1 )"+
+ "#recurse($i)"+
+ "#end"+
+ "#end"+
+ "#recurse(1)";
+ assertEvalEquals("12345", template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity631TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity631TestCase.java
new file mode 100755
index 00000000..99289433
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity631TestCase.java
@@ -0,0 +1,49 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-631.
+ */
+public class Velocity631TestCase extends BaseTestCase
+{
+ public Velocity631TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty("space.gobbling", "bc");
+ }
+
+
+ public void test631()
+ {
+ assertEvalEquals("$a", "$a #set($b = 1)");
+ assertEvalEquals("$a", "$a#set($b = 1)");
+ assertEvalEquals("$a.b", "$a.b#set($b = 1)");
+ assertEvalEquals("$a.b(", "$a.b(#set($b = 1)");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity644TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity644TestCase.java
new file mode 100644
index 00000000..9e74fe24
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity644TestCase.java
@@ -0,0 +1,57 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-644. Make sure the reported filename
+ * is correct in exceptions when an error occurs in another template file.
+ */
+public class Velocity644TestCase extends BaseTestCase
+{
+ public Velocity644TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, FILE_RESOURCE_LOADER_PATH);
+ engine.setProperty(RuntimeConstants.VM_LIBRARY, "testCase644.vm");
+ engine.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, Boolean.TRUE);
+ context.put("NULL", null);
+ }
+
+ public void test629()
+ {
+ // Calling a null method
+ assertEvalExceptionAt("#nullMethod()", "testCase644.vm", 9, 8);
+ // An invalid array
+ assertEvalExceptionAt("#arrayError()", "testCase644.vm", 4, 8);
+ // An invalid reference
+ assertEvalExceptionAt("#badRef()", "testCase644.vm", 13, 3);
+ // Non iterable object
+ assertEvalExceptionAt("#forloop()", "testCase644.vm", 18, 18);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity667TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity667TestCase.java
new file mode 100644
index 00000000..87f4e5a9
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity667TestCase.java
@@ -0,0 +1,39 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-667. Make "#macro" throws a parse exception
+ */
+public class Velocity667TestCase extends BaseTestCase
+{
+ public Velocity667TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void test667()
+ {
+ assertEvalExceptionAt("#macro", 1, 7);
+ assertEvalExceptionAt("#macro #macro", 1, 7);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity682TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity682TestCase.java
new file mode 100644
index 00000000..8808ac3d
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity682TestCase.java
@@ -0,0 +1,63 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-682.
+ */
+public class Velocity682TestCase extends BaseTestCase
+{
+ public Velocity682TestCase(String name)
+ {
+ super(name);
+ //DEBUG = true;
+ }
+
+ public void test682()
+ {
+ engine.setProperty(RuntimeConstants.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ assertEvalEquals("foo1foo2", "#macro(eval $e)#evaluate($e)#end#eval('foo1')#eval('foo2')");
+ }
+
+ public void test682b()
+ {
+ String template = "#macro( eval $e )#evaluate($e)#end" +
+ "#eval('foo')" +
+ "#eval('bar')";
+ String expected = "foo"+
+ "bar";
+ assertEvalEquals(expected, template);
+ }
+
+ public void test682c()
+ {
+ //NOTE: #eval call is apparently swallowing preceding newlines. :(
+ // appears to be a parser issue unrelated to VELOCITY-682
+ String template = "#macro( eval $e )#evaluate($e)#end" +
+ "\n#eval('foo')" +
+ "\n\n#eval('bar')";
+ String expected = "foo"+
+ "\nbar";
+ assertEvalEquals(expected, template);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity689TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity689TestCase.java
new file mode 100755
index 00000000..7ff2ddf8
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity689TestCase.java
@@ -0,0 +1,78 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-689.
+ */
+public class Velocity689TestCase extends BaseTestCase
+{
+ public Velocity689TestCase(String name)
+ {
+ super(name);
+ //DEBUG = true;
+ }
+
+ @Override
+ public void setUpContext(VelocityContext ctx)
+ {
+ ctx.put("foo", new Foo());
+ }
+
+ public void testIt()
+ {
+ String template = "$foo.baz, $foo.bar";
+ assertEvalEquals("baz, bar", template);
+ }
+
+ public interface HasMethod
+ {
+ String getBar();
+ }
+
+ public interface HasOtherMethod extends HasMethod
+ {
+ String getBaz();
+ }
+
+ public interface NoMethod extends HasOtherMethod
+ {
+ // nada!
+ }
+
+ private static class Foo implements NoMethod
+ {
+ @Override
+ public String getBar()
+ {
+ return "bar";
+ }
+
+ @Override
+ public String getBaz()
+ {
+ return "baz";
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity701TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity701TestCase.java
new file mode 100755
index 00000000..69bbf21e
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity701TestCase.java
@@ -0,0 +1,72 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-701.
+ */
+public class Velocity701TestCase extends BaseTestCase
+{
+ public Velocity701TestCase(String name)
+ {
+ super(name);
+ //DEBUG = true;
+ }
+
+ public void testAbstractClass()
+ {
+ context.put("foo", new Foo() {
+ @Override
+ public String getBar() {
+ return "bar";
+ }
+ });
+ assertEvalEquals("bar", "$foo.bar");
+ }
+
+ public static abstract class Foo {
+
+ public abstract String getBar();
+
+ }
+
+ public void testEnum()
+ {
+ context.put("bar", Bar.ONE);
+ assertEvalEquals("foo", "$bar.foo");
+ }
+
+ public enum Bar {
+
+ ONE(){
+ @Override
+ public String getFoo() {
+ return "foo";
+ }
+ };
+
+ //This was an abstract method, but Velocity 1.6 quit working with it.
+ public abstract String getFoo();
+
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity702TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity702TestCase.java
new file mode 100755
index 00000000..ed74c6e3
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity702TestCase.java
@@ -0,0 +1,99 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.runtime.resource.util.StringResourceRepository;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-702.
+ */
+public class Velocity702TestCase extends BaseTestCase
+{
+ public Velocity702TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty(RuntimeConstants.RESOURCE_LOADERS, "high,low");
+ engine.addProperty("high.resource.loader.class", StringResourceLoader.class.getName());
+ engine.addProperty("high.resource.loader.cache", "false");
+ engine.addProperty("high.resource.loader.repository.name", "high");
+ engine.addProperty("high.resource.loader.repository.static", "false");
+ engine.addProperty("high.resource.loader.modificationCheckInterval", "1");
+ engine.addProperty("low.resource.loader.class", StringResourceLoader.class.getName());
+ engine.addProperty("low.resource.loader.cache", "true");
+ engine.addProperty("low.resource.loader.repository.name", "low");
+ engine.addProperty("low.resource.loader.repository.static", "false");
+ engine.addProperty("low.resource.loader.modificationCheckInterval", "1");
+ engine.init();
+ }
+
+ public void testIt() throws Exception
+ {
+ addToHigh("foo", "foo");
+ addToLow("foo", "bar");
+ assertTmplEquals("foo", "foo");
+
+ removeFromHigh("foo");
+ assertTmplEquals("bar", "foo");
+
+ Thread.sleep(1500);
+ addToHigh("foo", "woogie");
+ assertTmplEquals("woogie", "foo");
+ }
+
+ private void addToHigh(String name, String content)
+ {
+ getHighRepo().putStringResource(name, content);
+ }
+
+ private void removeFromHigh(String name)
+ {
+ getHighRepo().removeStringResource(name);
+ }
+
+ private StringResourceRepository getHighRepo()
+ {
+ return (StringResourceRepository)engine.getApplicationAttribute("high");
+ }
+
+ private void addToLow(String name, String content)
+ {
+ getLowRepo().putStringResource(name, content);
+ }
+
+ private void removeFromLow(String name)
+ {
+ getLowRepo().removeStringResource(name);
+ }
+
+ private StringResourceRepository getLowRepo()
+ {
+ return (StringResourceRepository)engine.getApplicationAttribute("low");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity709TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity709TestCase.java
new file mode 100644
index 00000000..50219726
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity709TestCase.java
@@ -0,0 +1,54 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-709.
+ */
+public class Velocity709TestCase extends BaseTestCase
+{
+ public Velocity709TestCase(String name)
+ {
+ super(name);
+ // DEBUG = true;
+ }
+
+ public void testEscapedBackslashInSetDirective()
+ {
+ String backslash = "\\";
+ String template = "#set($var = \"" + backslash + "\" )#set($var2 = \"${var}\")$var2";
+ System.out.println(template);
+ assertEvalEquals("\\", template);
+ }
+
+ public void testEscapedDoubleQuote()
+ {
+ String template = "#set($foo = \"jeah \"\"baby\"\" jeah! \"\"\"\"\")$foo";
+ assertEvalEquals("jeah \"baby\" jeah! \"\"", template);
+ }
+
+ public void testEscapedSingleQuote()
+ {
+ String template = "#set($foo = 'jeah ''baby'' jeah!')$foo";
+ assertEvalEquals("jeah 'baby' jeah!", template);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity727TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity727TestCase.java
new file mode 100644
index 00000000..46038152
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity727TestCase.java
@@ -0,0 +1,40 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-727.
+ */
+public class Velocity727TestCase extends BaseTestCase
+{
+ public Velocity727TestCase(String name)
+ {
+ super(name);
+ DEBUG = false;
+ }
+
+ public void testDefineWithNoArgument()
+ {
+ assertEvalException("#define() foo bar #end", VelocityException.class);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity728TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity728TestCase.java
new file mode 100644
index 00000000..824576d8
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity728TestCase.java
@@ -0,0 +1,40 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-728.
+ */
+public class Velocity728TestCase extends BaseTestCase
+{
+ public Velocity728TestCase(String name)
+ {
+ super(name);
+ DEBUG = false;
+ }
+
+ public void testParseWithNoArgument()
+ {
+ assertEvalException("#parse()", VelocityException.class);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity729TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity729TestCase.java
new file mode 100644
index 00000000..869c609f
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity729TestCase.java
@@ -0,0 +1,46 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-729.
+ */
+public class Velocity729TestCase extends BaseTestCase
+{
+ public Velocity729TestCase(String name)
+ {
+ super(name);
+ // DEBUG = true;
+ }
+
+ public void testDotRightAfterDollarReference()
+ {
+ String s = "$.x schmoo $jee";
+ context.put("jee", "foo");
+ assertEvalEquals("$.x schmoo foo", s);
+ }
+
+ public void testVelocity754jQueryPost()
+ {
+ assertSchmoo("$.post(\"someUrl\", \"\")");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity736TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity736TestCase.java
new file mode 100644
index 00000000..0a62dce3
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity736TestCase.java
@@ -0,0 +1,76 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-736.
+ */
+public class Velocity736TestCase extends BaseTestCase
+{
+ public Velocity736TestCase(String name)
+ {
+ super(name);
+ DEBUG = true;
+ }
+
+ public void testPublicMethodInheritedFromAbstractProtectedClass() throws Exception
+ {
+ try
+ {
+ toobig(100);
+ }
+ catch (Exception e)
+ {
+ context.put("e", e);
+ assertEvalEquals("100", "$e.permittedSize");
+ }
+ }
+
+ public void toobig(long permitted) throws Exception
+ {
+ throw new FileSizeLimitExceededException(permitted);
+ }
+
+ public static class FileUploadException extends Exception {}
+
+ protected abstract static class SizeException extends FileUploadException
+ {
+ private final long permitted;
+ protected SizeException(long permitted)
+ {
+ this.permitted = permitted;
+ }
+ public long getPermittedSize()
+ {
+ return this.permitted;
+ }
+ }
+
+ public static class FileSizeLimitExceededException extends SizeException
+ {
+ public FileSizeLimitExceededException(long permitted)
+ {
+ super(permitted);
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity742TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity742TestCase.java
new file mode 100644
index 00000000..45b7dfde
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity742TestCase.java
@@ -0,0 +1,57 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-742.
+ */
+public class Velocity742TestCase extends BaseTestCase
+{
+ public Velocity742TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ // we need to call init here because otherwise it is not called until assertEvalEquals
+ // and therefore the removeDirective call is ignored.
+ engine.init();
+ }
+
+ public void testDisableAndRestoreDirective()
+ {
+ String s = "#include('doesnotexist.vm') directive is disabled";
+
+ // first remove the #include directive and see that is treated as normal text
+ engine.removeDirective("include");
+ assertEvalEquals(s, s);
+
+ // now reload the directive and see that the include directive works again and
+ // Velocity throws ResourceNotFoundException because it can't find the template
+ engine.loadDirective("org.apache.velocity.runtime.directive.Include");
+ assertEvalException(s, ResourceNotFoundException.class);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity747TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity747TestCase.java
new file mode 100644
index 00000000..5dba3633
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity747TestCase.java
@@ -0,0 +1,102 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.io.FileReader;
+import java.io.StringWriter;
+import java.util.Properties;
+
+/**
+ * This class tests VELOCITY-785.
+ */
+public class Velocity747TestCase extends BaseTestCase
+{
+ public Velocity747TestCase(String name)
+ {
+ super(name);
+ }
+
+ VelocityEngine engine1;
+ VelocityEngine engine2;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ Properties props = new Properties();
+ /* The props file contains *spaces* at the end of the line:
+ * velocimacro.permissions.allow.inline.local.scope = true
+ * which caused the initial problem
+ */
+ props.load(new FileReader(TEST_COMPARE_DIR + "/issues/velocity-747/vel.props"));
+ props.setProperty("file.resource.loader.path", TEST_COMPARE_DIR + "/issues/velocity-747/");
+ engine1 = new VelocityEngine(props);
+
+ //by default, make the engine's log output go to the test-report
+ log = new TestLogger(false, false);
+ engine1.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, log);
+
+ engine2 = new VelocityEngine();
+ engine2.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file,string");
+ engine2.addProperty("file.resource.loader.path", TEST_COMPARE_DIR + "/issues/velocity-747/");
+ engine2.addProperty("file.resource.loader.cache", "true");
+ engine2.addProperty("file.resource.loader.modificationCheckInterval", "-1");
+ engine2.addProperty("velocimacro.permissions.allow.inline.local.scope", "true");
+ engine2.addProperty("velocimacro.max.depth", "-1");
+ engine2.addProperty("string.resource.loader.class", StringResourceLoader.class.getName());
+ engine2.addProperty("string.resource.loader.repository.name", "stringRepo");
+ engine2.addProperty("string.resource.loader.repository.static", "false");
+ log = new TestLogger(false, false);
+ engine2.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, log);
+ }
+
+ public void testMacroIsolation1()
+ {
+ StringWriter writer = new StringWriter();
+ VelocityContext ctx = new VelocityContext();
+ engine1.mergeTemplate("one.vm", "UTF-8", new VelocityContext(), writer);
+ String result = writer.toString();
+ assertEquals(result, "This is from Test1 macro of one.vm");
+ writer = new StringWriter();
+ engine1.mergeTemplate("two.vm", "UTF-8", ctx, writer);
+ result = writer.toString();
+ assertEquals(result, "This is from Test1 macro of two.vm");
+ }
+
+ public void testMacroIsolation2()
+ {
+ StringWriter writer = new StringWriter();
+ VelocityContext ctx = new VelocityContext();
+ engine2.mergeTemplate("one.vm", "UTF-8", new VelocityContext(), writer);
+ String result = writer.toString();
+ assertEquals(result, "This is from Test1 macro of one.vm");
+
+ writer = new StringWriter();
+ engine2.mergeTemplate("two.vm", "UTF-8", ctx, writer);
+ result = writer.toString();
+ assertEquals(result, "This is from Test1 macro of two.vm");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity753TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity753TestCase.java
new file mode 100755
index 00000000..21fff75c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity753TestCase.java
@@ -0,0 +1,62 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-753.
+ */
+public class Velocity753TestCase extends BaseTestCase
+{
+ public Velocity753TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testFloatArg() throws Exception
+ {
+ // verify precedence outside of Velocity
+ Tool tool = new Tool();
+ Float f = 5.23f;
+ assertEquals("object", tool.test(f));
+
+ context.put("tool", tool);
+ context.put("float", f);
+
+ String template = "$tool.test($float)";
+ // in reflection-land, Float and float are equivalent, so double is selected
+ assertEvalEquals("double", template);
+ }
+
+ public static class Tool
+ {
+ public String test(double d)
+ {
+ return "double";
+ }
+
+ public String test(Object o)
+ {
+ return "object";
+ }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity755TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity755TestCase.java
new file mode 100755
index 00000000..c3261da4
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity755TestCase.java
@@ -0,0 +1,41 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-755.
+ */
+public class Velocity755TestCase extends BaseTestCase
+{
+ public Velocity755TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testMapOrder()
+ {
+ String template = "#set( $map = {'a': 1, 'b': true, 'c': 3, 'd': false, 'e': 5} )"+
+ "#foreach( $i in $map )$i#end";
+ assertEvalEquals("1true3false5", template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity758TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity758TestCase.java
new file mode 100644
index 00000000..4c8d84c2
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity758TestCase.java
@@ -0,0 +1,66 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.EventCartridge;
+import org.apache.velocity.app.event.IncludeEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-758.
+ */
+public class Velocity758TestCase extends BaseTestCase
+{
+ public Velocity758TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testNullArgumentForParse()
+ {
+ assertEvalEquals("", "#parse($foo)");
+ }
+
+ public void testOverrideNullArgumentForParse()
+ {
+ String nullContent = "Parse arg was null";
+ addTemplate("null.vm", nullContent);
+
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(new Handler());
+ ec.attachToContext(context);
+
+ assertEvalEquals(nullContent, "#parse($foo)");
+ }
+
+ public static class Handler implements IncludeEventHandler
+ {
+ @Override
+ public String includeEvent(Context context, String parsePath, String parentPath, String directive)
+ {
+ if (parsePath == null)
+ {
+ parsePath = "null.vm";
+ }
+ return parsePath;
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity762TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity762TestCase.java
new file mode 100755
index 00000000..ea025ef3
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity762TestCase.java
@@ -0,0 +1,40 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-762.
+ */
+public class Velocity762TestCase extends BaseTestCase
+{
+ public Velocity762TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testForeachIsLast()
+ {
+ String template = "#foreach( $i in [1..3] )$foreach.last #end";
+ assertEvalEquals("false false true ", template);
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity785TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity785TestCase.java
new file mode 100644
index 00000000..83a60a09
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity785TestCase.java
@@ -0,0 +1,42 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-785.
+ */
+public class Velocity785TestCase extends BaseTestCase
+{
+ public Velocity785TestCase(String name)
+ {
+ super(name);
+ // DEBUG = true;
+ }
+
+ public void testQuoteEscapes()
+ {
+ assertEvalEquals("\"", "#set($double_double = \"\"\"\")$double_double");
+ assertEvalEquals("'", "#set($single_single = '''')$single_single");
+ assertEvalEquals("''", "#set($double_single = \"''\")$double_single");
+ assertEvalEquals("\"\"", "#set($single_double = '\"\"')$single_double");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity830TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity830TestCase.java
new file mode 100644
index 00000000..4f014f4c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity830TestCase.java
@@ -0,0 +1,58 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests the VELOCITY-830 issue.
+ *
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ */
+public class Velocity830TestCase extends BaseTestCase
+{
+ public Velocity830TestCase(String name)
+ {
+ super(name);
+ }
+
+ public static class UnderscoreMethodObject
+ {
+ public String check() { return "ok"; }
+ public String _1() { return "gotit"; }
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("obj", new UnderscoreMethodObject());
+ }
+
+ /**
+ * Tests methods name beginning with _
+ */
+ public void testUnderscoreMethod()
+ throws Exception
+ {
+ assertEvalEquals("ok", "$obj.check()");
+ assertEvalEquals("gotit", "$obj._1()");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity855TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity855TestCase.java
new file mode 100755
index 00000000..5d2c94da
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity855TestCase.java
@@ -0,0 +1,47 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-855.
+ */
+public class Velocity855TestCase extends BaseTestCase
+{
+
+ public Velocity855TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("elementKind", javax.lang.model.element.ElementKind.class);
+ context.put("typeKind", javax.lang.model.type.TypeKind.class);
+ }
+
+ public void testVelocity855()
+ {
+ assertEvalEquals("ENUM DECLARED", "$elementKind.valueOf('ENUM') $typeKind.valueOf('DECLARED')");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity889TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity889TestCase.java
new file mode 100755
index 00000000..d4532f84
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity889TestCase.java
@@ -0,0 +1,51 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-589.
+ */
+public class Velocity889TestCase extends BaseTestCase
+{
+ public Velocity889TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testSpaceBeforeRParen()
+ {
+ assertEvalEquals("#foo(\n)", "#foo(\n)");
+ assertEvalEquals("#foo(\n )", "#foo(\n )");
+ }
+
+ public void testSpaceBeforeRParenWithArg()
+ {
+ assertEvalEquals("#foo(\n$bar\n)", "#foo(\n$bar\n)");
+ assertEvalEquals("#foo(\n $bar\n )", "#foo(\n $bar\n )");
+ }
+
+ public void testSpaceBeforeRParenWithDefaultArg()
+ {
+ assertEvalEquals("", "#macro(\nfoo\n,\n$bar\n=\n'bar')\n#end");
+ assertEvalEquals("", "#macro(\n foo\n ,\n $bar\n =\n 'bar'\n )\n #end");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity896TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity896TestCase.java
new file mode 100755
index 00000000..d7ea45da
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity896TestCase.java
@@ -0,0 +1,40 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-589.
+ */
+public class Velocity896TestCase extends BaseTestCase
+{
+ public Velocity896TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testTailingHash()
+ {
+ assertEvalEquals("#", "#");
+ assertEvalEquals("$", "$");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity904TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity904TestCase.java
new file mode 100755
index 00000000..86caa7cf
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity904TestCase.java
@@ -0,0 +1,116 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityEngine;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-904.
+ */
+public class Velocity904TestCase extends BaseTestCase
+{
+ public Velocity904TestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ // that will also test the deprecation of velocimacro.arguments.preserve_literals towards velocimacro.enable_bc_mode
+ engine.setProperty("velocimacro.arguments.preserve_literals", getName().contains("NoPreserve") ? "false" : "true");
+ }
+
+ public void testNullArgNoPreserve()
+ {
+ assertEvalEquals("$parameter", "#macro(testmacro $parameter)$parameter#end#testmacro($return)");
+ }
+
+ public void testNullArgPreserve()
+ {
+ assertEvalEquals("$return", "#macro(testmacro $parameter)$parameter#end#testmacro($return)");
+ }
+
+ public void testArgSetToNullNoPreserve()
+ {
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#set($variable = 'value')#mymacro($variable)");
+ }
+
+ public void testArgSetToNullPreserve()
+ {
+ assertEvalEquals("$variable", "#macro(mymacro $input)#set($input = $null)$input#end#set($variable = 'value')#mymacro($variable)");
+ }
+
+ public void testSubMacroNoPreserve()
+ {
+ assertEvalEquals("$return$return$return", "#macro(macro1 $return)$return#macro2($param2)$return#end#macro(macro2 $return)$return#end#macro1($param)");
+ }
+
+ public void testSubMacroPreserve()
+ {
+ assertEvalEquals("$param$param2$param", "#macro(macro1 $return)$return#macro2($param2)$return#end#macro(macro2 $return)$return#end#macro1($param)");
+ }
+
+ public void testNoArgNoPreserve()
+ {
+ assertEvalEquals("","#macro(testMacro $param)#end#testMacro()");
+ }
+
+ public void testNoArgPreserve()
+ {
+ assertEvalEquals("","#macro(testMacro $param)#end#testMacro()");
+ }
+
+ public void testConstantSetToNullNoPreserve()
+ {
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro('string-value')");
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro(\"interpolated-$bar-value\")");
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro(true)");
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro(4.5)");
+ }
+
+ public void testConstantSetToNullPreserve()
+ {
+ assertEvalEquals("'string-value'", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro('string-value')");
+ assertEvalEquals("\"interpolated-$bar-value\"", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro(\"interpolated-$bar-value\")");
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro(true)");
+ assertEvalEquals("$input", "#macro(mymacro $input)#set($input = $null)$input#end#mymacro(4.5)");
+ }
+
+ public void testConstantNoPreserve()
+ {
+ assertEvalEquals("true", "#macro(mymacro $input)$input#end#mymacro(true)");
+ assertEvalEquals("1.5", "#macro(mymacro $input)$input#end#mymacro(1.5)");
+ assertEvalEquals("foo", "#macro(mymacro $input)$input#end#mymacro('foo')");
+ assertEvalEquals("{}", "#macro(mymacro $input)$input#end#mymacro({})");
+ assertEvalEquals("[]", "#macro(mymacro $input)$input#end#mymacro([])");
+ }
+
+ public void testConstantPreserve()
+ {
+ assertEvalEquals("true", "#macro(mymacro $input)$input#end#mymacro(true)");
+ assertEvalEquals("1.5", "#macro(mymacro $input)$input#end#mymacro(1.5)");
+ assertEvalEquals("foo", "#macro(mymacro $input)$input#end#mymacro('foo')");
+ assertEvalEquals("{}", "#macro(mymacro $input)$input#end#mymacro({})");
+ assertEvalEquals("[]", "#macro(mymacro $input)$input#end#mymacro([])");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity919TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity919TestCase.java
new file mode 100644
index 00000000..33397cfc
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity919TestCase.java
@@ -0,0 +1,23 @@
+package org.apache.velocity.test.issues;
+
+import org.apache.velocity.test.BaseTestCase;
+
+public class Velocity919TestCase extends BaseTestCase
+{
+ public Velocity919TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testUnbreakableSpace() throws Exception
+ {
+ assertEvalEquals("before\u200Bafter", "before\u200Bafter");
+ }
+
+ public void testUserFileSeparator() throws Exception
+ {
+ assertEvalEquals("before\u001Cafter", "before\u001Cafter");
+ }
+
+}
+
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity924TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity924TestCase.java
new file mode 100755
index 00000000..5c1ee041
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity924TestCase.java
@@ -0,0 +1,65 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-855.
+ */
+public class Velocity924TestCase extends BaseTestCase
+{
+ public Velocity924TestCase(String name)
+ {
+ super(name);
+ }
+
+ public static class Foo
+ {
+ public String getName() { return "foo"; }
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("var", new Foo());
+ }
+
+ public void testVelocity924Getter()
+ {
+ assertEvalEquals("org.apache.velocity.test.issues.Velocity924TestCase$Foo foo", "$var.class.name $var.name");
+ }
+
+ public void testVelocity924Method()
+ {
+ assertEvalEquals("org.apache.velocity.test.issues.Velocity924TestCase$Foo foo", "$var.class.getName() $var.getName()");assertEvalEquals("org.apache.velocity.test.issues.Velocity924TestCase$Foo foo", "$var.class.name $var.name");
+ }
+
+ public void testVelocity924Getter2()
+ {
+ assertEvalEquals("foo org.apache.velocity.test.issues.Velocity924TestCase$Foo", "$var.name $var.class.name");
+ }
+
+ public void testVelocity924Method2()
+ {
+ assertEvalEquals("foo org.apache.velocity.test.issues.Velocity924TestCase$Foo", "$var.getName() $var.class.getName()");assertEvalEquals("org.apache.velocity.test.issues.Velocity924TestCase$Foo foo", "$var.class.name $var.name");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity926TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity926TestCase.java
new file mode 100755
index 00000000..41a33213
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity926TestCase.java
@@ -0,0 +1,38 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests VELOCITY-926.
+ */
+public class Velocity926TestCase extends BaseTestCase
+{
+ public Velocity926TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testNamesCollision()
+ {
+ assertEvalEquals("bar foo", "#set($foo='foo')#set($bar='bar')#macro(test, $foo, $bar)$foo $bar#end#test($bar, $foo)");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity927TestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity927TestCase.java
new file mode 100755
index 00000000..218ed7f2
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/issues/Velocity927TestCase.java
@@ -0,0 +1,39 @@
+package org.apache.velocity.test.issues;
+
+/*
+ * 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.test.BaseTestCase;
+
+/**
+ * This class tests bugfix for http://stackoverflow.com/questions/32805217/bug-or-hidden-feature-in-apache-velocity
+ */
+public class Velocity927TestCase extends BaseTestCase
+{
+ public Velocity927TestCase(String name)
+ {
+ super(name);
+ }
+
+ public void testIt()
+ {
+ assertEvalEquals("", "#set($map = {\n })");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingDirective.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingDirective.java
new file mode 100644
index 00000000..0717310a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingDirective.java
@@ -0,0 +1,61 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.directive.Directive;
+import org.apache.velocity.runtime.parser.node.Node;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * that always throws an exception. Used to test
+ * that RuntimeExceptions are passed through.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ExceptionGeneratingDirective extends Directive
+{
+ @Override
+ public String getName()
+ {
+ return "Exception";
+ }
+
+ @Override
+ public int getType()
+ {
+ return Directive.BLOCK;
+ }
+
+ @Override
+ public boolean render(InternalContextAdapter context, Writer writer, Node node)
+ throws IOException, ResourceNotFoundException, ParseErrorException,
+ MethodInvocationException
+ {
+ throw new RuntimeException("exception");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingEventHandler.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingEventHandler.java
new file mode 100644
index 00000000..037594ac
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingEventHandler.java
@@ -0,0 +1,55 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.IncludeEventHandler;
+import org.apache.velocity.app.event.MethodExceptionEventHandler;
+import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.util.introspection.Info;
+
+/**
+ * Event handlers that always throws an exception. Used to test
+ * that RuntimeExceptions are passed through.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ExceptionGeneratingEventHandler implements IncludeEventHandler,
+ MethodExceptionEventHandler, ReferenceInsertionEventHandler
+{
+
+ @Override
+ public String includeEvent(Context context, String includeResourcePath, String currentResourcePath,
+ String directiveName)
+ {
+ throw new RuntimeException("exception");
+ }
+
+ @Override
+ public Object methodException(Context context, Class<?> claz, String method, Exception e, Info info) { throw new RuntimeException("exception"); }
+
+ @Override
+ public Object referenceInsert(Context context, String reference, Object value)
+ {
+ throw new RuntimeException("exception");
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java
new file mode 100644
index 00000000..fef7adb3
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/ExceptionGeneratingResourceLoader.java
@@ -0,0 +1,62 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.runtime.resource.Resource;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+import org.apache.velocity.util.ExtProperties;
+
+import java.io.Reader;
+
+/**
+ * Resource Loader that always throws an exception. Used to test
+ * that RuntimeExceptions are passed through.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ExceptionGeneratingResourceLoader extends ResourceLoader
+{
+
+ @Override
+ public void init(ExtProperties configuration)
+ {
+ }
+
+ @Override
+ public Reader getResourceReader(String source, String encoding) throws ResourceNotFoundException
+ {
+ throw new RuntimeException("exception");
+ }
+
+ @Override
+ public boolean isSourceModified(Resource resource)
+ {
+ return false;
+ }
+
+ @Override
+ public long getLastModified(Resource resource)
+ {
+ return 0;
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/GetPutObject.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/GetPutObject.java
new file mode 100644
index 00000000..1cc528c2
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/GetPutObject.java
@@ -0,0 +1,35 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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 class GetPutObject
+{
+ private Object value;
+
+ public Object get()
+ {
+ return value;
+ }
+
+ public void put(final Object value)
+ {
+ this.value = value;
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestContext.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestContext.java
new file mode 100644
index 00000000..4b8e3bda
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestContext.java
@@ -0,0 +1,85 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.context.Context;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Used for testing EvaluateContext. For testing purposes, this is a case insensitive
+ * context.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class TestContext implements Context
+{
+ Context innerContext = new VelocityContext();
+ Map<String, String> originalKeys = new HashMap<>();
+
+ @Override
+ public boolean containsKey(String key)
+ {
+ return innerContext.containsKey(normalizeKey(key));
+ }
+
+ @Override
+ public Object get(String key)
+ {
+ return innerContext.get(normalizeKey(key));
+ }
+
+ @Override
+ public String[] getKeys()
+ {
+ return originalKeys.values().toArray(new String[originalKeys.size()]);
+ }
+
+ @Override
+ public Object put(String key, Object value)
+ {
+ String normalizedKey = normalizeKey(key);
+ originalKeys.put(key, normalizedKey);
+ return innerContext.put(normalizedKey, value);
+ }
+
+ @Override
+ public Object remove(String key)
+ {
+ originalKeys.remove(key);
+ return innerContext.remove(normalizeKey(key));
+ }
+
+ private String normalizeKey(String key)
+ {
+ if (key == null)
+ {
+ return null;
+ }
+ else
+ {
+ return key.toUpperCase(Locale.ROOT);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestLogger.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestLogger.java
new file mode 100644
index 00000000..e7a4e990
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/TestLogger.java
@@ -0,0 +1,360 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.helpers.FormattingTuple;
+import org.slf4j.helpers.MarkerIgnoringBase;
+import org.slf4j.helpers.MessageFormatter;
+import org.slf4j.spi.LocationAwareLogger;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/**
+ * Logger implementation that can easily capture output
+ * or suppress it entirely. By default, both capture and suppress
+ * are on. To have this behave like a normal Logger,
+ * you must turn it on() and stopCapture().
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author Nathan Bubna
+ * @author <a href="mailto:cbrisson@apache.org">Claude Brisson</a>
+ * @version $Id$
+ */
+public class TestLogger extends MarkerIgnoringBase
+{
+ private ByteArrayOutputStream log;
+ private PrintStream systemDotIn;
+
+ private boolean suppress = true;
+ private boolean capture = true;
+ private int enabledLevel = LOG_LEVEL_INFO;
+
+ public TestLogger()
+ {
+ this(true, true);
+ }
+
+ public TestLogger(boolean suppress, boolean capture)
+ {
+ this.suppress = suppress;
+ this.capture = capture;
+ if (suppress)
+ {
+ off();
+ }
+ else if (capture)
+ {
+ startCapture();
+ }
+ }
+
+ public static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT;
+ public static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT;
+ public static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT;
+ public static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT;
+ public static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT;
+
+ private static int stringToLevel(String levelStr)
+ {
+ if ("trace".equalsIgnoreCase(levelStr)) return LOG_LEVEL_TRACE;
+ else if ("debug".equalsIgnoreCase(levelStr)) return LOG_LEVEL_DEBUG;
+ else if ("info".equalsIgnoreCase(levelStr)) return LOG_LEVEL_INFO;
+ else if ("warn".equalsIgnoreCase(levelStr)) return LOG_LEVEL_WARN;
+ else if ("error".equalsIgnoreCase(levelStr)) return LOG_LEVEL_ERROR;
+ // assume INFO by default
+ return LOG_LEVEL_INFO;
+ }
+
+ private static String getPrefix(int level)
+ {
+ if (level <= LOG_LEVEL_TRACE) return " [trace] ";
+ else if (level <= LOG_LEVEL_DEBUG) return " [debug] ";
+ else if (level <= LOG_LEVEL_INFO) return " [info] ";
+ else if (level <= LOG_LEVEL_WARN) return " [warn] ";
+ else return " [error]";
+ }
+
+ public synchronized void on()
+ {
+ if (suppress)
+ {
+ suppress = false;
+ if (capture)
+ {
+ startCapture();
+ }
+ }
+ }
+
+ public synchronized void off()
+ {
+ suppress = true;
+ }
+
+ public synchronized void startCapture()
+ {
+ capture = true;
+ if (!suppress)
+ {
+ log = new ByteArrayOutputStream();
+ systemDotIn = new PrintStream(log, true);
+ }
+ }
+
+ public synchronized void stopCapture()
+ {
+ capture = false;
+ }
+
+ public void setEnabledLevel(int level)
+ {
+ enabledLevel = level;
+ }
+
+ public boolean isLevelEnabled(int level)
+ {
+ return !suppress && level >= enabledLevel;
+ }
+
+ /**
+ * Return the captured log messages to date.
+ * @return log messages
+ */
+ public String getLog()
+ {
+ return log.toString();
+ }
+
+ private synchronized void log(int level, String msg, Throwable t)
+ {
+ if(!suppress && level >= enabledLevel)
+ {
+ PrintStream writer = capture ? systemDotIn : System.err;
+ writer.print(getPrefix(enabledLevel));
+ writer.println(msg);
+ if (t != null)
+ {
+ writer.println(t.getMessage());
+ t.printStackTrace(writer);
+ }
+ writer.flush();
+ }
+ }
+
+ /**
+ * Logging API
+ */
+
+ @Override
+ public boolean isTraceEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_TRACE);
+ }
+
+ @Override
+ public void trace(String msg)
+ {
+ log(LOG_LEVEL_TRACE, msg, null);
+ }
+
+ @Override
+ public void trace(String format, Object arg)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg);
+ log(LOG_LEVEL_TRACE, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void trace(String format, Object arg1, Object arg2)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
+ log(LOG_LEVEL_TRACE, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void trace(String format, Object[] argArray)
+ {
+ FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
+ log(LOG_LEVEL_TRACE, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void trace(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_TRACE, msg, t);
+ }
+
+ @Override
+ public boolean isDebugEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_DEBUG);
+ }
+
+ @Override
+ public void debug(String msg)
+ {
+ log(LOG_LEVEL_DEBUG, msg, null);
+ }
+
+ @Override
+ public void debug(String format, Object arg)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg);
+ log(LOG_LEVEL_DEBUG, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void debug(String format, Object arg1, Object arg2)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
+ log(LOG_LEVEL_DEBUG, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void debug(String format, Object[] argArray)
+ {
+ FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
+ log(LOG_LEVEL_DEBUG, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void debug(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_DEBUG, msg, t);
+ }
+
+ @Override
+ public boolean isInfoEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_INFO);
+ }
+
+ @Override
+ public void info(String msg)
+ {
+ log(LOG_LEVEL_INFO, msg, null);
+ }
+
+ @Override
+ public void info(String format, Object arg)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg);
+ log(LOG_LEVEL_INFO, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void info(String format, Object arg1, Object arg2)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
+ log(LOG_LEVEL_INFO, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void info(String format, Object[] argArray)
+ {
+ FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
+ log(LOG_LEVEL_INFO, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void info(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_INFO, msg, t);
+ }
+
+ @Override
+ public boolean isWarnEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_WARN);
+ }
+
+ @Override
+ public void warn(String msg)
+ {
+ log(LOG_LEVEL_WARN, msg, null);
+ }
+
+ @Override
+ public void warn(String format, Object arg)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg);
+ log(LOG_LEVEL_WARN, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void warn(String format, Object arg1, Object arg2)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
+ log(LOG_LEVEL_WARN, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void warn(String format, Object[] argArray)
+ {
+ FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
+ log(LOG_LEVEL_WARN, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void warn(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_WARN, msg, t);
+ }
+
+ @Override
+ public boolean isErrorEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_ERROR);
+ }
+
+ @Override
+ public void error(String msg)
+ {
+ log(LOG_LEVEL_ERROR, msg, null);
+ }
+
+ @Override
+ public void error(String format, Object arg)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg);
+ log(LOG_LEVEL_ERROR, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void error(String format, Object arg1, Object arg2)
+ {
+ FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
+ log(LOG_LEVEL_ERROR, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void error(String format, Object[] argArray)
+ {
+ FormattingTuple ft = MessageFormatter.arrayFormat(format, argArray);
+ log(LOG_LEVEL_ERROR, ft.getMessage(), ft.getThrowable());
+ }
+
+ @Override
+ public void error(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_ERROR, msg, t);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestException.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestException.java
new file mode 100644
index 00000000..71a96a4a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestException.java
@@ -0,0 +1,62 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.util.introspection.Info;
+
+
+
+/**
+ * Exception that returns an Info object for testing after a introspection problem.
+ * This extends Error so that it will stop parsing and allow
+ * internal info to be examined.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ * @author <a href="mailto:isidore@setgame.com">Llewellyn Falco</a>
+ * @version $Id$
+ */
+public class UberspectTestException extends RuntimeException
+{
+
+ /**
+ * Version Id for serializable
+ */
+ private static final long serialVersionUID = 3956896150436225712L;
+
+ Info info;
+
+ public UberspectTestException(String message, Info i)
+ {
+ super(message);
+ info = i;
+ }
+
+ public Info getInfo()
+ {
+ return info;
+ }
+
+ @Override
+ public String getMessage()
+ {
+ return super.getMessage() + "\n failed at " + info;
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestImpl.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestImpl.java
new file mode 100644
index 00000000..b07f6725
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectTestImpl.java
@@ -0,0 +1,66 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.util.introspection.Info;
+import org.apache.velocity.util.introspection.UberspectImpl;
+import org.apache.velocity.util.introspection.VelMethod;
+import org.apache.velocity.util.introspection.VelPropertyGet;
+
+
+/**
+ * A introspector that allows testing when methods are not found.
+ */
+public class UberspectTestImpl extends UberspectImpl
+{
+
+ @Override
+ public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
+ {
+ VelMethod method = super.getMethod(obj, methodName, args, i);
+
+ if (method == null)
+ {
+ if (obj == null)
+ throw new UberspectTestException("Can't call method '" + methodName + "' on null object",i);
+ else
+ throw new UberspectTestException("Did not find method "+ obj.getClass().getName()+"."+methodName, i);
+ }
+
+ return method;
+ }
+
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
+ {
+ VelPropertyGet propertyGet = super.getPropertyGet(obj, identifier, i);
+
+ if (propertyGet == null)
+ {
+ if (obj == null)
+ throw new UberspectTestException("Can't call getter '" + identifier + "' on null object",i);
+ else
+ throw new UberspectTestException("Did not find "+ obj.getClass().getName()+"."+identifier, i);
+ }
+
+ return propertyGet;
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectorTestObject.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectorTestObject.java
new file mode 100644
index 00000000..34b9cbe0
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/misc/UberspectorTestObject.java
@@ -0,0 +1,133 @@
+package org.apache.velocity.test.misc;
+
+/*
+ * 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.util.Map;
+
+public class UberspectorTestObject
+{
+ private String regular;
+ private String premium;
+
+ private boolean regularBool;
+ private boolean premiumBool;
+
+ private String ambigous;
+
+ private String unambiguous;
+
+ /**
+ * @return the premium
+ */
+ public String getpremium()
+ {
+ return premium;
+ }
+
+ /**
+ * @param premium the premium to set
+ */
+ public void setpremium(String premium)
+ {
+ this.premium = premium;
+ }
+
+ /**
+ * @return the premiumBool
+ */
+ public boolean ispremiumBool()
+ {
+ return premiumBool;
+ }
+
+ /**
+ * @param premiumBool the premiumBool to set
+ */
+ public void setpremiumBool(boolean premiumBool)
+ {
+ this.premiumBool = premiumBool;
+ }
+
+ /**
+ * @return the regular
+ */
+ public String getRegular()
+ {
+ return regular;
+ }
+
+ /**
+ * @param regular the regular to set
+ */
+ public void setRegular(String regular)
+ {
+ this.regular = regular;
+ }
+
+ /**
+ * @return the regularBool
+ */
+ public boolean isRegularBool()
+ {
+ return regularBool;
+ }
+
+ /**
+ * @param regularBool the regularBool to set
+ */
+ public void setRegularBool(boolean regularBool)
+ {
+ this.regularBool = regularBool;
+ }
+
+ /**
+ * @return the ambigous
+ */
+ public String getAmbigous()
+ {
+ return ambigous;
+ }
+
+ /**
+ * @param ambigous the ambigous to set
+ */
+ public void setAmbigous(String ambigous)
+ {
+ this.ambigous = ambigous;
+ }
+
+ /**
+ * @param ambigous the ambigous to set
+ */
+ public void setAmbigous(StringBuffer ambigous)
+ {
+ this.ambigous = ambigous.toString();
+ }
+
+ public void setUnambiguous(String unambiguous)
+ {
+ this.unambiguous = unambiguous;
+ }
+
+ public void setUnambiguous(Map unambiguous)
+ {
+ this.unambiguous = unambiguous.toString();
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/BoolObj.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/BoolObj.java
new file mode 100644
index 00000000..0e90299b
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/BoolObj.java
@@ -0,0 +1,46 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.
+ */
+
+/**
+ * simple class to test boolean property
+ * introspection - can't use TestProvider
+ * as there is a get( String )
+ * and that comes before isProperty
+ * in the search pattern
+ *
+ * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a>
+ */
+public class BoolObj
+{
+ public boolean isBoolean()
+ {
+ return true;
+ }
+
+ /*
+ * not isProperty as it's not
+ * boolean return valued...
+ */
+ public String isNotboolean()
+ {
+ return "hello";
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Child.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Child.java
new file mode 100644
index 00000000..6515c752
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Child.java
@@ -0,0 +1,37 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.
+ */
+
+/**
+ * Rudimentary class used in the testbed to test
+ * introspection with subclasses of a particular
+ * class.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class Child extends Person
+{
+ @Override
+ public String getName()
+ {
+ return "Child";
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/ForeachMethodCallHelper.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/ForeachMethodCallHelper.java
new file mode 100644
index 00000000..042f8657
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/ForeachMethodCallHelper.java
@@ -0,0 +1,31 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.
+ */
+
+/**
+ * Provides overloaded methods for testing method execution within a foreach
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class ForeachMethodCallHelper
+{
+ public String getFoo(Integer v) { return "int "+v; }
+ public String getFoo(String v) { return "str "+v; }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NullToStringObject.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NullToStringObject.java
new file mode 100644
index 00000000..f30e81cb
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NullToStringObject.java
@@ -0,0 +1,33 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.
+ */
+
+/**
+ * Used to confirm that a null to string is processed properly
+ * @author <a href="mailto:wglass@apache.org">Will Glass-Husain</a>
+ * @version $Id$
+ */
+public class NullToStringObject
+{
+ public String toString()
+ {
+ return null;
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NumberMethods.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NumberMethods.java
new file mode 100644
index 00000000..eb70fd9c
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/NumberMethods.java
@@ -0,0 +1,70 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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;
+
+
+/**
+ * Used to check that method calls with number parameters are executed correctly.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ */
+public class NumberMethods
+{
+
+ public String numMethod(byte val)
+ {
+ return "byte (" + val + ")";
+ }
+
+ public String numMethod(short val)
+ {
+ return "short (" + val + ")";
+ }
+
+ public String numMethod(int val)
+ {
+ return "int (" + val + ")";
+ }
+
+ public String numMethod(double val)
+ {
+ return "double (" + val + ")";
+ }
+
+ public String numMethod(long val)
+ {
+ return "long (" + val + ")";
+ }
+
+ public String numMethod(BigInteger val)
+ {
+ return "BigInteger (" + val + ")";
+ }
+
+ public String numMethod(BigDecimal val)
+ {
+ return "BigDecimal (" + val + ")";
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Person.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Person.java
new file mode 100644
index 00000000..239739d0
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/Person.java
@@ -0,0 +1,39 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.
+ */
+
+/**
+ * Rudimentary class used in the testbed to test
+ * introspection with subclasses of a particular
+ * class.
+ *
+ * This class need to be greatly extended to
+ * be useful :-)
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class Person
+{
+ public String getName()
+ {
+ return "Person";
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestNumber.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestNumber.java
new file mode 100644
index 00000000..5850eec5
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestNumber.java
@@ -0,0 +1,47 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.util.TemplateNumber;
+
+/**
+ * Used for testing purposes to check that an object implementing TemplateNumber
+ * will be treated as a Number.
+ *
+ * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
+ */
+public class TestNumber implements TemplateNumber
+{
+
+ private Number n;
+
+ public TestNumber(double val)
+ {
+ n = val;
+ }
+
+ @Override
+ public Number getAsNumber()
+ {
+ return n;
+ }
+
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestProvider.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestProvider.java
new file mode 100644
index 00000000..60eea81f
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/provider/TestProvider.java
@@ -0,0 +1,362 @@
+package org.apache.velocity.test.provider;
+
+/*
+ * 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.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
+/**
+ * This class is used by the testbed. Instances of the class
+ * are fed into the context that is set before the AST
+ * is traversed and dynamic content generated.
+ *
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @version $Id$
+ */
+public class TestProvider
+{
+ String title = "lunatic";
+ boolean state;
+ Object ob = null;
+
+ public static String PUB_STAT_STRING = "Public Static String";
+
+ int stateint = 0;
+
+
+ public String getName()
+ {
+ return "jason";
+ }
+
+ public Stack getStack()
+ {
+ Stack stack = new Stack();
+ stack.push("stack element 1");
+ stack.push("stack element 2");
+ stack.push("stack element 3");
+ return stack;
+ }
+
+ public List getEmptyList()
+ {
+ return new ArrayList();
+ }
+
+ public List getList()
+ {
+ List list = new ArrayList();
+ list.add("list element 1");
+ list.add("list element 2");
+ list.add("list element 3");
+
+ return list;
+ }
+
+ public Hashtable getSearch()
+ {
+ Hashtable h = new Hashtable();
+ h.put("Text", "this is some text");
+ h.put("EscText", "this is escaped text");
+ h.put("Title", "this is the title");
+ h.put("Index", "this is the index");
+ h.put("URL", "http://periapt.com");
+
+ ArrayList al = new ArrayList();
+ al.add(h);
+
+ h.put("RelatedLinks", al);
+
+ return h;
+ }
+
+ public Hashtable getHashtable()
+ {
+ Hashtable h = new Hashtable();
+ h.put("key0", "value0");
+ h.put("key1", "value1");
+ h.put("key2", "value2");
+
+ return h;
+ }
+
+ public ArrayList getRelSearches()
+ {
+ ArrayList al = new ArrayList();
+ al.add(getSearch());
+
+ return al;
+ }
+
+ public String getTitle()
+ {
+ return title;
+ }
+
+ public void setTitle(String title)
+ {
+ this.title = title;
+ }
+
+ public Object[] getMenu()
+ {
+ //ArrayList al = new ArrayList();
+ Object[] menu = new Object[3];
+ for (int i = 0; i < 3; i++)
+ {
+ Hashtable item = new Hashtable();
+ item.put("id", "item" + (i+1));
+ item.put("name", "name" + (i+1));
+ item.put("label", "label" + (i+1));
+ //al.add(item);
+ menu[i] = item;
+ }
+
+ //return al;
+ return menu;
+ }
+
+ public ArrayList getCustomers()
+ {
+ ArrayList list = new ArrayList();
+
+ list.add("ArrayList element 1");
+ list.add("ArrayList element 2");
+ list.add("ArrayList element 3");
+ list.add("ArrayList element 4");
+
+ return list;
+ }
+
+ public ArrayList getCustomers2()
+ {
+ ArrayList list = new ArrayList();
+
+ list.add(new TestProvider());
+ list.add(new TestProvider());
+ list.add(new TestProvider());
+ list.add(new TestProvider());
+
+ return list;
+ }
+
+ public Object me()
+ {
+ return this;
+ }
+
+ public String toString()
+ {
+ return ("test provider");
+ }
+
+ public Vector getVector()
+ {
+ Vector list = new Vector();
+
+ list.addElement("vector element 1");
+ list.addElement("vector element 2");
+
+ return list;
+ }
+
+ public String[] getArray()
+ {
+ String[] strings = new String[2];
+ strings[0] = "first element";
+ strings[1] = "second element";
+ return strings;
+ }
+
+ public boolean theAPLRules()
+ {
+ return true;
+ }
+
+ public boolean getStateTrue()
+ {
+ return true;
+ }
+
+ public boolean getStateFalse()
+ {
+ return false;
+ }
+
+ public String objectArrayMethod(Object[] o)
+ {
+ return "result of objectArrayMethod";
+ }
+
+ public String concat(Object[] strings)
+ {
+ StringBuilder result = new StringBuilder();
+
+ for (Object string : strings)
+ {
+ result.append((String) string).append(' ');
+ }
+
+ return result.toString();
+ }
+
+ public String concat(List strings)
+ {
+ StringBuilder result = new StringBuilder();
+
+ for (Object string : strings)
+ {
+ result.append((String) string).append(' ');
+ }
+
+ return result.toString();
+ }
+
+ public String objConcat(List objects)
+ {
+ StringBuilder result = new StringBuilder();
+
+ for (Object object : objects)
+ {
+ result.append(object).append(' ');
+ }
+
+ return result.toString();
+ }
+
+ public String parse(String a, Object o, String c, String d)
+ {
+ return a + o.toString() + c + d;
+ }
+
+ public String concat(String a, String b)
+ {
+ return a + b;
+ }
+
+ // These two are for testing subclasses.
+
+ public Person getPerson()
+ {
+ return new Person();
+ }
+
+ public Child getChild()
+ {
+ return new Child();
+ }
+
+ public String showPerson(Person person)
+ {
+ return person.getName();
+ }
+
+ /**
+ * Chop i characters off the end of a string.
+ *
+ * @param string String to chop.
+ * @param i Number of characters to chop.
+ * @return String with processed answer.
+ */
+ public String chop(String string, int i)
+ {
+ return(string.substring(0, string.length() - i));
+ }
+
+ public boolean allEmpty(Object[] list)
+ {
+ int size = list.length;
+
+ for (Object aList : list)
+ if (aList.toString().length() > 0)
+ return false;
+
+ return true;
+ }
+
+ /*
+ * This can't have the signature
+ *
+ * public void setState(boolean state)
+ *
+ * or dynamically invoking the method
+ * doesn't work ... you would have to
+ * put a wrapper around a method for a
+ * real boolean property that takes a
+ * Boolean object if you wanted this to
+ * work. Not really sure how useful it
+ * is anyway. Who cares about boolean
+ * values you can just set a variable.
+ *
+ */
+
+ public void setState(Boolean state)
+ {
+ }
+
+ public void setBangStart( Integer i )
+ {
+ System.out.println("SetBangStart() : called with val = " + i );
+ stateint = i;
+ }
+ public Integer bang()
+ {
+ System.out.println("Bang! : " + stateint );
+ Integer ret = stateint;
+ stateint++;
+ return ret;
+ }
+
+ /**
+ * Test the ability of vel to use a get(key)
+ * method for any object type, not just one
+ * that implements the Map interface.
+ */
+ public String get(String key)
+ {
+ return key;
+ }
+
+ /**
+ * Test the ability of vel to use a put(key)
+ * method for any object type, not just one
+ * that implements the Map interface.
+ */
+ public String put(String key, Object o)
+ {
+ ob = o;
+ return key;
+ }
+
+ public String getFoo()
+ throws Exception
+ {
+ throw new Exception("From getFoo()");
+ }
+
+ public String getThrow()
+ throws Exception
+ {
+ throw new Exception("From getThrow()");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/BaseSQLTest.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/BaseSQLTest.java
new file mode 100644
index 00000000..8bf31dca
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/BaseSQLTest.java
@@ -0,0 +1,96 @@
+package org.apache.velocity.test.sql;
+
+/*
+ * 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.test.BaseTestCase;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+/**
+ * A base class to implement tests that need a running
+ * Velocity engine and an initialized HSQLDB Database.
+ * It can also be used to test against other database engines
+ * by means of the proper environment parameters, see velocity-engine-core pom.xml file.
+ *
+ * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a>
+ * @version $Id$
+ */
+
+public abstract class BaseSQLTest
+ extends BaseTestCase
+{
+ private static DBHelper dbHelper = null;
+
+ protected String TEST_JDBC_DRIVER_CLASS = System.getProperty("test.jdbc.driver.className");
+ protected String TEST_JDBC_URI = System.getProperty("test.jdbc.uri");
+ protected String TEST_JDBC_LOGIN = System.getProperty("test.jdbc.login");
+ protected String TEST_JDBC_PASSWORD = System.getProperty("test.jdbc.password");
+
+ /**
+ * String (not containing any VTL) used to test unicode
+ */
+ protected String UNICODE_TEMPLATE = "\\u00a9 test \\u0410 \\u0411";
+
+ /**
+ * Name of template for testing unicode.
+ */
+ protected String UNICODE_TEMPLATE_NAME = "testUnicode";
+
+
+ public BaseSQLTest(String name, String path)
+ throws Exception
+ {
+ super(name);
+
+ if (dbHelper == null)
+ {
+ dbHelper = new DBHelper(TEST_JDBC_DRIVER_CLASS, TEST_JDBC_URI, TEST_JDBC_LOGIN, TEST_JDBC_PASSWORD,path + "/create-db.sql");
+ setUpUnicode();
+ }
+ }
+
+ private void setUpUnicode()
+ throws Exception
+ {
+ String insertString = "insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES " +
+ "( '" + UNICODE_TEMPLATE_NAME + "', current_timestamp, '" + UNICODE_TEMPLATE + "');";
+ executeSQL(insertString);
+ insertString = "insert into velocity_template_clob (vt_id, vt_timestamp, vt_def) VALUES " +
+ "( '" + UNICODE_TEMPLATE_NAME + "', current_timestamp, '" + UNICODE_TEMPLATE + "');";
+ executeSQL(insertString);
+ }
+
+
+ public void executeSQL(String sql)
+ throws SQLException
+ {
+ Connection connection = dbHelper.getConnection();
+ Statement statement = connection.createStatement();
+ // Oracle and Derby do not want any final ';'
+ if ((TEST_JDBC_DRIVER_CLASS.equals("oracle.jdbc.OracleDriver")
+ || TEST_JDBC_DRIVER_CLASS.equals("org.apache.derby.jdbc.EmbeddedDriver")) && sql.endsWith(";"))
+ {
+ sql = sql.substring(0, sql.length() - 1);
+ }
+ statement.executeUpdate(sql);
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DBHelper.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DBHelper.java
new file mode 100644
index 00000000..4acef0f8
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DBHelper.java
@@ -0,0 +1,123 @@
+package org.apache.velocity.test.sql;
+
+/*
+ * 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 java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class DBHelper
+{
+ private String driverClass = null;
+ private Connection connection = null;
+
+ public DBHelper(String driverClass, String uri, String login, String password, String loadFile) throws Exception
+ {
+ this.driverClass = driverClass;
+ Class.forName(driverClass);
+
+ this.connection = DriverManager.getConnection(uri, login, password);
+
+ if (StringUtils.isNotEmpty(loadFile))
+ {
+ loadSqlFile(loadFile);
+ }
+ }
+
+ public Connection getConnection()
+ {
+ return connection;
+ }
+
+ public void close()
+ {
+
+ try
+ {
+ connection.close();
+ }
+ catch (Exception e)
+ {
+ System.out.println("While closing Connection" + e.getMessage());
+ }
+ }
+
+ // avoid ';' inside BEGIN/END blocks
+ private static int nextSemiColon(final String cmd)
+ {
+ int start = 0;
+ int ret = -1;
+ while (true)
+ {
+ ret = cmd.indexOf(';', start);
+ if (ret == -1) break;
+ int begin = cmd.lastIndexOf("BEGIN", ret);
+ int end = cmd.lastIndexOf("END;", ret);
+ if (begin == -1) break;
+ if (end > begin) break;
+ start = ret + 1;
+ }
+ return ret;
+ }
+
+ private void loadSqlFile(String fileName) throws Exception
+ {
+ Statement statement = null;
+
+ try
+ {
+ String commands = new String(Files.readAllBytes(Paths.get(fileName)), StandardCharsets.UTF_8);
+ // manually eat comments, some engines don't like them
+ Pattern removeComments = Pattern.compile("^--.*$", Pattern.MULTILINE);
+ Matcher matcher = removeComments.matcher(commands);
+ commands = matcher.replaceAll("");
+ for (int targetPos = nextSemiColon(commands); targetPos > -1; targetPos = nextSemiColon(commands))
+ {
+ statement = connection.createStatement();
+ String cmd = commands.substring(0, targetPos + 1);
+ // Oracle doesn't like semi-colons at the end, except for BEGIN/END blocks...
+ // nor does Derby
+ if (driverClass.equals("oracle.jdbc.OracleDriver") && !cmd.endsWith("END;") ||
+ driverClass.equals("org.apache.derby.jdbc.EmbeddedDriver"))
+ {
+ cmd = cmd.substring(0, cmd.length() - 1);
+ }
+ statement.executeUpdate(cmd);
+ commands = commands.substring(targetPos + 2);
+ statement.close();
+ }
+ }
+ finally
+ {
+ if (statement != null)
+ {
+ statement.close();
+ }
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DataSourceResourceLoaderTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DataSourceResourceLoaderTestCase.java
new file mode 100644
index 00000000..757b6e84
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/DataSourceResourceLoaderTestCase.java
@@ -0,0 +1,229 @@
+package org.apache.velocity.test.sql;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.runtime.resource.loader.DataSourceResourceLoader;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.ExtProperties;
+
+import javax.sql.DataSource;
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+
+public class DataSourceResourceLoaderTestCase
+ extends BaseSQLTest
+{
+ /**
+ * Comparison file extension.
+ */
+ private static final String CMP_FILE_EXT = "cmp";
+
+ /**
+ * Comparison file extension.
+ */
+ private static final String RESULT_FILE_EXT = "res";
+
+ /**
+ * Path to template file. This will get combined with the
+ * application directory to form an absolute path
+ */
+ private final static String DATA_PATH = TEST_COMPARE_DIR + "/ds";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String RESULTS_DIR = TEST_RESULT_DIR + "/ds";
+
+ /**
+ * Results relative to the build directory.
+ */
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/ds/templates";
+
+ /* engine with VARCHAR templates data source */
+ private RuntimeInstance varcharTemplatesEngine = null;
+
+ /* engine with CLOB templates data source */
+ private RuntimeInstance clobTemplatesEngine = null;
+
+ public DataSourceResourceLoaderTestCase(final String name)
+ throws Exception
+ {
+ super(name, DATA_PATH);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(DataSourceResourceLoaderTestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+
+ assureResultsDirectoryExists(RESULTS_DIR);
+
+ DataSource ds1 = new TestDataSource(TEST_JDBC_DRIVER_CLASS, TEST_JDBC_URI, TEST_JDBC_LOGIN, TEST_JDBC_PASSWORD);
+ DataSourceResourceLoader rl1 = new DataSourceResourceLoader();
+ rl1.setDataSource(ds1);
+
+ DataSource ds2 = new TestDataSource(TEST_JDBC_DRIVER_CLASS, TEST_JDBC_URI, TEST_JDBC_LOGIN, TEST_JDBC_PASSWORD);
+ DataSourceResourceLoader rl2 = new DataSourceResourceLoader();
+ rl2.setDataSource(ds2);
+
+ ExtProperties props = new ExtProperties();
+ props.addProperty( "resource.loader", "ds" );
+ props.setProperty( "ds.resource.loader.instance", rl1);
+ props.setProperty( "ds.resource.loader.resource.table", "velocity_template_varchar");
+ props.setProperty( "ds.resource.loader.resource.keycolumn", "vt_id");
+ props.setProperty( "ds.resource.loader.resource.templatecolumn", "vt_def");
+ props.setProperty( "ds.resource.loader.resource.timestampcolumn", "vt_timestamp");
+ props.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger(false, false));
+
+ varcharTemplatesEngine = new RuntimeInstance();
+ varcharTemplatesEngine.setConfiguration(props);
+ varcharTemplatesEngine.init();
+
+ ExtProperties props2 = (ExtProperties)props.clone();
+ props2.setProperty( "ds.resource.loader.instance", rl2);
+ props2.setProperty( "ds.resource.loader.resource.table", "velocity_template_clob");
+ clobTemplatesEngine = new RuntimeInstance();
+ clobTemplatesEngine.setConfiguration(props2);
+ clobTemplatesEngine.init();
+ }
+
+ /**
+ * Tests loading and rendering of a simple template. If that works, we are able to get data
+ * from the database.
+ */
+ public void testSimpleTemplate()
+ throws Exception
+ {
+ Template t = executeTest("testTemplate1", varcharTemplatesEngine);
+ assertFalse("Timestamp is 0", 0 == t.getLastModified());
+ t = executeTest("testTemplate1", clobTemplatesEngine);
+ assertFalse("Timestamp is 0", 0 == t.getLastModified()); }
+
+ public void testUnicode(RuntimeInstance engine)
+ throws Exception
+ {
+ Template template = engine.getTemplate(UNICODE_TEMPLATE_NAME);
+
+ Writer writer = new StringWriter();
+ VelocityContext context = new VelocityContext();
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ String outputText = writer.toString();
+
+ if (!normalizeNewlines(UNICODE_TEMPLATE).equals(
+ normalizeNewlines( outputText ) ))
+ {
+ fail("Output incorrect for Template: " + UNICODE_TEMPLATE_NAME);
+ }
+ }
+
+ /**
+ * Now we have a more complex example. Run a very simple tool.
+ * from the database.
+ */
+ public void testRenderTool()
+ throws Exception
+ {
+ Template t = executeTest("testTemplate2", varcharTemplatesEngine);
+ assertFalse("Timestamp is 0", 0 == t.getLastModified());
+ t = executeTest("testTemplate2", clobTemplatesEngine);
+ assertFalse("Timestamp is 0", 0 == t.getLastModified());
+ }
+
+ /**
+ * Will a NULL timestamp choke the loader?
+ */
+ public void testNullTimestamp()
+ throws Exception
+ {
+ Template t = executeTest("testTemplate3", varcharTemplatesEngine);
+ assertEquals("Timestamp is not 0", 0, t.getLastModified());
+ t = executeTest("testTemplate3", clobTemplatesEngine);
+ assertEquals("Timestamp is not 0", 0, t.getLastModified()); }
+
+ /**
+ * Does it load the global Macros from the DB?
+ */
+ public void testMacroInvocation()
+ throws Exception
+ {
+ Template t = executeTest("testTemplate4", varcharTemplatesEngine);
+ assertFalse("Timestamp is 0", 0 == t.getLastModified());
+ t = executeTest("testTemplate4", clobTemplatesEngine);
+ assertFalse("Timestamp is 0", 0 == t.getLastModified());
+ }
+
+ protected Template executeTest(final String templateName, RuntimeInstance engine)
+ throws Exception
+ {
+ Template template = engine.getTemplate(templateName);
+
+ FileOutputStream fos =
+ new FileOutputStream (
+ getFileName(RESULTS_DIR, templateName, RESULT_FILE_EXT));
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ VelocityContext context = new VelocityContext();
+ context.put("tool", new DSRLTCTool());
+
+ template.merge(context, writer);
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULTS_DIR, COMPARE_DIR, templateName,
+ RESULT_FILE_EXT, CMP_FILE_EXT))
+ {
+ fail("Output incorrect for Template: " + templateName);
+ }
+
+ return template;
+ }
+
+ public static final class DSRLTCTool
+ {
+ public int add(final int a, final int b)
+ {
+ return a + b;
+ }
+
+ public String getMessage()
+ {
+ return "And the result is:";
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/TestDataSource.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/TestDataSource.java
new file mode 100644
index 00000000..286cfc2a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/sql/TestDataSource.java
@@ -0,0 +1,105 @@
+package org.apache.velocity.test.sql;
+
+/*
+ * 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 javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.logging.Logger;
+
+public class TestDataSource implements DataSource
+{
+
+ private final String url;
+ private final String user;
+ private final String password;
+
+ private PrintWriter logWriter = null;
+
+ private int loginTimeout = 0;
+
+ public TestDataSource(final String driverClass, final String url, final String user, final String password) throws Exception
+ {
+ this.url = url;
+ this.user = user;
+ this.password = password;
+ Class.forName(driverClass);
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException
+ {
+ return DriverManager.getConnection(url, user, password);
+ }
+
+ @Override
+ public Connection getConnection(final String username, final String password)
+ throws SQLException
+ {
+ return DriverManager.getConnection(url, username, password);
+ }
+
+ @Override
+ public PrintWriter getLogWriter() throws SQLException
+ {
+ return logWriter;
+ }
+
+ @Override
+ public int getLoginTimeout() throws SQLException
+ {
+ return loginTimeout;
+ }
+
+ @Override
+ public void setLogWriter(final PrintWriter logWriter) throws SQLException
+ {
+ this.logWriter = logWriter;
+ }
+
+ @Override
+ public void setLoginTimeout(final int loginTimeout) throws SQLException
+ {
+ this.loginTimeout = loginTimeout;
+ }
+
+ @Override
+ public boolean isWrapperFor(final Class iface) throws SQLException
+ {
+ return false;
+ }
+
+ @Override
+ public Object unwrap(final Class iface) throws SQLException
+ {
+ throw new SQLException("Not implemented");
+ }
+
+ /* added to be able to compile with jdk 1.7+ */
+ @Override
+ public Logger getParentLogger() throws SQLFeatureNotSupportedException
+ {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ChainedUberspectorsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ChainedUberspectorsTestCase.java
new file mode 100644
index 00000000..2687d662
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ChainedUberspectorsTestCase.java
@@ -0,0 +1,121 @@
+package org.apache.velocity.test.util.introspection;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.AbstractChainableUberspector;
+import org.apache.velocity.util.introspection.Info;
+import org.apache.velocity.util.introspection.UberspectImpl;
+import org.apache.velocity.util.introspection.VelPropertyGet;
+import org.apache.velocity.util.introspection.VelPropertySet;
+
+import java.io.StringWriter;
+
+/**
+ * Tests uberspectors chaining
+ */
+public class ChainedUberspectorsTestCase extends BaseTestCase {
+
+ public ChainedUberspectorsTestCase(String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(ChainedUberspectorsTestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ Velocity.reset();
+ Velocity.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ Velocity.addProperty(Velocity.UBERSPECT_CLASSNAME,"org.apache.velocity.util.introspection.UberspectImpl");
+ Velocity.addProperty(Velocity.UBERSPECT_CLASSNAME,"org.apache.velocity.test.util.introspection.ChainedUberspectorsTestCase$ChainedUberspector");
+ Velocity.addProperty(Velocity.UBERSPECT_CLASSNAME,"org.apache.velocity.test.util.introspection.ChainedUberspectorsTestCase$LinkedUberspector");
+ Velocity.init();
+ }
+
+ @Override
+ public void tearDown()
+ {
+ }
+
+ public void testChaining()
+ throws Exception
+ {
+ VelocityContext context = new VelocityContext();
+ context.put("foo",new Foo());
+ StringWriter writer = new StringWriter();
+
+ Velocity.evaluate(context,writer,"test","$foo.zeMethod()");
+ assertEquals(writer.toString(),"ok");
+
+ Velocity.evaluate(context,writer,"test","#set($foo.foo = 'someValue')");
+
+ writer = new StringWriter();
+ Velocity.evaluate(context,writer,"test","$foo.bar");
+ assertEquals(writer.toString(),"someValue");
+
+ writer = new StringWriter();
+ Velocity.evaluate(context,writer,"test","$foo.foo");
+ assertEquals(writer.toString(),"someValue");
+ }
+
+ // replaces getFoo by getBar
+ public static class ChainedUberspector extends AbstractChainableUberspector
+ {
+ @Override
+ public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info info)
+ {
+ identifier = identifier.replaceAll("foo","bar");
+ return inner.getPropertySet(obj,identifier,arg,info);
+ }
+ }
+
+ // replaces setFoo by setBar
+ public static class LinkedUberspector extends UberspectImpl
+ {
+ @Override
+ public VelPropertyGet getPropertyGet(Object obj, String identifier, Info info)
+ {
+ identifier = identifier.replaceAll("foo","bar");
+ return super.getPropertyGet(obj,identifier,info);
+ }
+ }
+
+ public static class Foo
+ {
+ private String bar;
+
+ public String zeMethod() { return "ok"; }
+ public String getBar() { return bar; }
+ public void setBar(String s) { bar = s; }
+ }
+
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ClassMapTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ClassMapTestCase.java
new file mode 100644
index 00000000..be0a1e31
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ClassMapTestCase.java
@@ -0,0 +1,110 @@
+package org.apache.velocity.test.util.introspection;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.ClassMap;
+import org.slf4j.Logger;
+
+/**
+ * Test the ClassMap Lookup
+ */
+public class ClassMapTestCase
+ extends BaseTestCase
+{
+ public ClassMapTestCase(final String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(ClassMapTestCase.class);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ Velocity.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger());
+ Velocity.init();
+ }
+
+ @Override
+ public void tearDown()
+ {
+ }
+
+ public void testPrimitives()
+ throws Exception
+ {
+ Logger log = Velocity.getLog();
+
+ ClassMap c = new ClassMap(TestClassMap.class, log);
+ assertNotNull(c.findMethod("setBoolean", new Object[] { Boolean.TRUE }));
+ assertNotNull(c.findMethod("setByte", new Object[] { new Byte((byte) 4)}));
+ assertNotNull(c.findMethod("setCharacter", new Object[] { new Character('c')}));
+ assertNotNull(c.findMethod("setDouble", new Object[] { new Double(8.0) }));
+ assertNotNull(c.findMethod("setFloat", new Object[] { new Float(15.0) }));
+ assertNotNull(c.findMethod("setInteger", new Object[] { new Integer(16) }));
+ assertNotNull(c.findMethod("setLong", new Object[] { new Long(23) }));
+ assertNotNull(c.findMethod("setShort", new Object[] { new Short((short)42)}));
+ }
+
+ public static final class TestClassMap
+ {
+ public void setBoolean(boolean b)
+ {
+ }
+
+ public void setByte(byte b)
+ {
+ }
+
+ public void setCharacter(char c)
+ {
+ }
+
+ public void setDouble(double d)
+ {
+ }
+
+ public void setFloat(float f)
+ {
+ }
+
+ public void setInteger(int i)
+ {
+ }
+
+ public void setLong(long l)
+ {
+ }
+
+ public void setShort(short s)
+ {
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
new file mode 100644
index 00000000..c15b18fc
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/ConversionHandlerTestCase.java
@@ -0,0 +1,454 @@
+package org.apache.velocity.test.util.introspection;
+
+/*
+ * 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 junit.framework.TestSuite;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.app.event.MethodExceptionEventHandler;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+import org.apache.velocity.util.introspection.Converter;
+import org.apache.velocity.util.introspection.Info;
+import org.apache.velocity.util.introspection.IntrospectionUtils;
+import org.apache.velocity.util.introspection.TypeConversionHandler;
+import org.apache.velocity.util.introspection.TypeConversionHandlerImpl;
+import org.apache.velocity.util.introspection.Uberspect;
+import org.apache.velocity.util.introspection.UberspectImpl;
+
+import java.io.BufferedWriter;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Test case for conversion handler
+ */
+public class ConversionHandlerTestCase extends BaseTestCase
+{
+ private static final String RESULT_DIR = TEST_RESULT_DIR + "/conversion";
+
+ private static final String COMPARE_DIR = TEST_COMPARE_DIR + "/conversion/compare";
+
+ public ConversionHandlerTestCase(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void setUp()
+ throws Exception
+ {
+ super.setUp();
+ }
+
+ /**
+ * Test suite
+ * @return test suite
+ */
+ public static junit.framework.Test suite()
+ {
+ return new TestSuite(ConversionHandlerTestCase.class);
+ }
+
+ public void testConversionsWithoutHandler()
+ throws Exception
+ {
+ /*
+ * local scope, cache on
+ */
+ VelocityEngine ve = createEngine(false);
+
+ testConversions(ve, "test_conv.vtl", "test_conv_without_handler");
+ }
+
+ public void testConversionsWithHandler()
+ throws Exception
+ {
+ /*
+ * local scope, cache on
+ */
+ VelocityEngine ve = createEngine(true);
+
+ testConversions(ve, "test_conv.vtl", "test_conv_with_handler");
+ }
+
+ public void testConversionMatrix()
+ throws Exception
+ {
+ VelocityEngine ve = createEngine(true);
+ testConversions(ve, "matrix.vhtml", "matrix");
+ }
+
+ public void testCustomConverter()
+ {
+ RuntimeInstance ve = new RuntimeInstance();
+ ve.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve.setProperty(Velocity.RUNTIME_LOG_INSTANCE, log);
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TEST_COMPARE_DIR + "/conversion");
+ ve.init();
+ Uberspect uberspect = ve.getUberspect();
+ assertTrue(uberspect instanceof UberspectImpl);
+ UberspectImpl ui = (UberspectImpl)uberspect;
+ TypeConversionHandler ch = ui.getConversionHandler();
+ assertTrue(ch != null);
+ ch.addConverter(Float.class, Obj.class, new Converter<Float>()
+ {
+ @Override
+ public Float convert(Object o)
+ {
+ return 4.5f;
+ }
+ });
+ ch.addConverter(TypeUtils.parameterize(List.class, Integer.class), String.class, new Converter<List<Integer>>()
+ {
+ @Override
+ public List<Integer> convert(Object o)
+ {
+ return Arrays.<Integer>asList(1,2,3);
+ }
+ });
+ ch.addConverter(TypeUtils.parameterize(List.class, String.class), String.class, new Converter<List<String>>()
+ {
+ @Override
+ public List<String> convert(Object o)
+ {
+ return Arrays.<String>asList("a", "b", "c");
+ }
+ });
+ VelocityContext context = new VelocityContext();
+ context.put("obj", new Obj());
+ Writer writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.integralFloat($obj) / $obj.objectFloat($obj)");
+ assertEquals("float ok: 4.5 / Float ok: 4.5", writer.toString());
+ writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.iWantAStringList('anything')");
+ assertEquals("correct", writer.toString());
+ writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.iWantAnIntegerList('anything')");
+ assertEquals("correct", writer.toString());
+ }
+
+ /* converts *everything* to string "foo" */
+ public static class MyCustomConverter implements TypeConversionHandler
+ {
+ Converter<String> myCustomConverter = new Converter<String>()
+ {
+
+ @Override
+ public String convert(Object o)
+ {
+ return "foo";
+ }
+ };
+
+ @Override
+ public boolean isExplicitlyConvertible(Type formal, Class<?> actual, boolean possibleVarArg)
+ {
+ return true;
+ }
+
+ @Override
+ public Converter<?> getNeededConverter(Type formal, Class<?> actual)
+ {
+ return myCustomConverter;
+ }
+
+ @Override
+ public void addConverter(Type formal, Class<?> actual, Converter<?> converter)
+ {
+ throw new RuntimeException("not implemented");
+ }
+ }
+
+ public void testCustomConversionHandlerInstance()
+ {
+ RuntimeInstance ve = new RuntimeInstance();
+ ve.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve.setProperty(Velocity.RUNTIME_LOG_INSTANCE, log);
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TEST_COMPARE_DIR + "/conversion");
+ ve.setProperty(RuntimeConstants.CONVERSION_HANDLER_INSTANCE, new MyCustomConverter());
+ ve.init();
+ Uberspect uberspect = ve.getUberspect();
+ assertTrue(uberspect instanceof UberspectImpl);
+ UberspectImpl ui = (UberspectImpl)uberspect;
+ TypeConversionHandler ch = ui.getConversionHandler();
+ assertTrue(ch != null);
+ assertTrue(ch instanceof MyCustomConverter);
+ VelocityContext context = new VelocityContext();
+ context.put("obj", new Obj());
+ Writer writer = new StringWriter();
+ ve.evaluate(context, writer, "test", "$obj.objectString(1.0)");
+ assertEquals("String ok: foo", writer.toString());
+ }
+
+ /**
+ * Test conversions
+ * @param ve
+ * @param templateFile template
+ * @param outputBaseFileName
+ * @throws Exception
+ */
+ private void testConversions(VelocityEngine ve, String templateFile, String outputBaseFileName)
+ throws Exception
+ {
+ assureResultsDirectoryExists(RESULT_DIR);
+
+ FileOutputStream fos = new FileOutputStream (getFileName(
+ RESULT_DIR, outputBaseFileName, RESULT_FILE_EXT));
+
+ VelocityContext context = createContext();
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(fos));
+
+ log.setEnabledLevel(TestLogger.LOG_LEVEL_ERROR);
+
+ Template template = ve.getTemplate(templateFile);
+ template.merge(context, writer);
+
+ /*
+ * Write to the file
+ */
+ writer.flush();
+ writer.close();
+
+ if (!isMatch(RESULT_DIR, COMPARE_DIR, outputBaseFileName,
+ RESULT_FILE_EXT,CMP_FILE_EXT))
+ {
+ String result = getFileContents(RESULT_DIR, outputBaseFileName, RESULT_FILE_EXT);
+ String compare = getFileContents(COMPARE_DIR, outputBaseFileName, CMP_FILE_EXT);
+
+ String msg = "Processed template did not match expected output\n"+
+ "-----Result-----\n"+ result +
+ "----Expected----\n"+ compare +
+ "----------------";
+
+ fail(msg);
+ }
+ }
+
+ public void testOtherConversions() throws Exception
+ {
+ VelocityEngine ve = createEngine(false);
+ VelocityContext context = createContext();
+ StringWriter writer = new StringWriter();
+ ve.evaluate(context, writer,"test", "$strings.join(['foo', 'bar'], ',')");
+ assertEquals("foo,bar", writer.toString());
+ }
+
+ /**
+ * Return and initialize engine
+ * @return
+ */
+ private VelocityEngine createEngine(boolean withConversionsHandler)
+ throws Exception
+ {
+ VelocityEngine ve = new VelocityEngine();
+ ve.setProperty( Velocity.VM_PERM_INLINE_LOCAL, Boolean.TRUE);
+ ve.setProperty(Velocity.RUNTIME_LOG_INSTANCE, log);
+ ve.setProperty(RuntimeConstants.RESOURCE_LOADERS, "file");
+ ve.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, TEST_COMPARE_DIR + "/conversion");
+ if (withConversionsHandler)
+ {
+ ve.setProperty(RuntimeConstants.EVENTHANDLER_METHODEXCEPTION, PrintException.class.getName());
+ }
+ else
+ {
+ ve.setProperty(RuntimeConstants.CONVERSION_HANDLER_CLASS, "none");
+ }
+ ve.init();
+
+ return ve;
+ }
+
+ public static class PrintException implements MethodExceptionEventHandler
+ {
+ @Override
+ public Object methodException(Context context,
+ Class claz,
+ String method,
+ Exception e,
+ Info info)
+ {
+ // JDK 11+ changed the exception message for big decimal conversion exceptions,
+ // which breaks the (brittle) tests. Clearly, it would be preferred to fix this
+ // right by comparing the result according to the JDK version, this is just a
+ // quick fix to get the build to pass on JDK 11+
+ //
+ if (e.getClass() == NumberFormatException.class && e.getMessage() != null && e.getMessage().startsWith("Character"))
+ {
+ return method + " -> " + e.getClass().getSimpleName() + ": null"; // compatible with JDK8
+ }
+
+ return method + " -> " + e.getClass().getSimpleName() + ": " + e.getMessage();
+ }
+ }
+
+ private VelocityContext createContext()
+ {
+ VelocityContext context = new VelocityContext();
+ Map<String, Object> map = new TreeMap<>();
+ map.put("A. bool-true", true);
+ map.put("B. bool-false", false);
+ map.put("C. byte-0", (byte)0);
+ map.put("D. byte-1", (byte)1);
+ map.put("E. short", (short)125);
+ map.put("F. int", 24323);
+ map.put("G. long", 5235235L);
+ map.put("H. float", 34523.345f);
+ map.put("I. double", 54235.3253d);
+ map.put("J. char", '@');
+ map.put("K. object", new Obj());
+ map.put("L. enum", Obj.Color.GREEN);
+ map.put("M. string", new String("foo"));
+ map.put("M. string-green", new String("green"));
+ map.put("N. string-empty", new String());
+ map.put("O. string-false", new String("false"));
+ map.put("P. string-true", new String("true"));
+ map.put("Q. string-zero", new String("0"));
+ map.put("R. string-integral", new String("123"));
+ map.put("S. string-big-integral", new String("12345678"));
+ map.put("T. string-floating", new String("123.345"));
+ map.put("U. null", null);
+ map.put("V. locale", "fr_FR");
+ map.put("W. BigInteger zero", BigInteger.ZERO);
+ map.put("X. BigInteger one", BigInteger.ONE);
+ map.put("Y. BigInteger ten", BigInteger.TEN);
+ map.put("Y. BigInteger bigint", new BigInteger("12345678901234567890"));
+ map.put("Z. BigDecimal zero", BigDecimal.ZERO);
+ map.put("ZA. BigDecimal one", BigDecimal.ONE);
+ map.put("ZB. BigDecimal ten", BigDecimal.TEN);
+ map.put("ZC. BigDecimal bigdec", new BigDecimal("12345678901234567890.01234567890123456789"));
+ context.put("map", map);
+ context.put("target", new Obj());
+ Class[] types =
+ {
+ Boolean.TYPE,
+ Character.TYPE,
+ Byte.TYPE,
+ Short.TYPE,
+ Integer.TYPE,
+ Long.TYPE,
+ Float.TYPE,
+ Double.TYPE,
+ Boolean.class,
+ Character.class,
+ Byte.class,
+ Short.class,
+ Integer.class,
+ Long.class,
+ BigInteger.class,
+ Float.class,
+ Double.class,
+ BigDecimal.class,
+ Number.class,
+ String.class,
+ Object.class
+ };
+ context.put("types", types);
+ context.put("introspect", new Introspect());
+ context.put("strings", new StringUtils());
+ return context;
+ }
+
+ public static class Obj
+ {
+ public enum Color { RED, GREEN }
+
+ public String integralBoolean(boolean b) { return "boolean ok: " + b; }
+ public String integralByte(byte b) { return "byte ok: " + b; }
+ public String integralShort(short s) { return "short ok: " + s; }
+ public String integralInt(int i) { return "int ok: " + i; }
+ public String integralLong(long l) { return "long ok: " + l; }
+ public String integralFloat(float f) { return "float ok: " + f; }
+ public String integralDouble(double d) { return "double ok: " + d; }
+ public String integralChar(char c) { return "char ok: " + c; }
+ public String objectBoolean(Boolean b) { return "Boolean ok: " + b; }
+ public String objectByte(Byte b) { return "Byte ok: " + b; }
+ public String objectShort(Short s) { return "Short ok: " + s; }
+ public String objectInt(Integer i) { return "Integer ok: " + i; }
+ public String objectLong(Long l) { return "Long ok: " + l; }
+ public String objectBigInteger(BigInteger bi) { return "BigInteger ok: " + bi; }
+ public String objectFloat(Float f) { return "Float ok: " + f; }
+ public String objectDouble(Double d) { return "Double ok: " + d; }
+ public String objectBigDecimal(BigDecimal bd) { return "BigDecimal ok: " + bd; }
+ public String objectCharacter(Character c) { return "Character ok: " + c; }
+ public String objectNumber(Number b) { return "Number ok: " + b; }
+ public String objectObject(Object o) { return "Object ok: " + o; }
+ public String objectString(String s) { return "String ok: " + s; }
+ public String objectEnum(Color c) { return "Enum ok: " + c; }
+ public String locale(Locale loc) { return "Locale ok: " + loc; }
+
+ public String toString() { return "instance of Obj"; }
+
+ public String iWantAStringList(List<String> list)
+ {
+ if (list != null && list.size() == 3 && list.get(0).equals("a") && list.get(1).equals("b") && list.get(2).equals("c"))
+ return "correct";
+ else return "wrong";
+ }
+
+ public String iWantAnIntegerList(List<Integer> list)
+ {
+ if (list != null && list.size() == 3 && list.get(0).equals(1) && list.get(1).equals(2) && list.get(2).equals(3))
+ return "correct";
+ else return "wrong";
+ }
+ }
+
+ public static class Introspect
+ {
+ private TypeConversionHandler handler;
+ public Introspect()
+ {
+ handler = new TypeConversionHandlerImpl();
+ }
+ public boolean isStrictlyConvertible(Class expected, Class provided)
+ {
+ return IntrospectionUtils.isStrictMethodInvocationConvertible(expected, provided, false);
+ }
+ public boolean isImplicitlyConvertible(Class expected, Class provided)
+ {
+ return IntrospectionUtils.isMethodInvocationConvertible(expected, provided, false);
+ }
+ public boolean isExplicitlyConvertible(Class expected, Class provided)
+ {
+ return handler.isExplicitlyConvertible(expected, provided, false);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/DeprecatedCheckUberspectorsTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/DeprecatedCheckUberspectorsTestCase.java
new file mode 100644
index 00000000..6b5b8857
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/DeprecatedCheckUberspectorsTestCase.java
@@ -0,0 +1,108 @@
+package org.apache.velocity.test.util.introspection;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+
+
+import java.io.StringWriter;
+
+/**
+ * Tests DeprecatedCheckUberspector
+ */
+public class DeprecatedCheckUberspectorsTestCase extends BaseTestCase {
+
+ public DeprecatedCheckUberspectorsTestCase(String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(DeprecatedCheckUberspectorsTestCase.class);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty(Velocity.RUNTIME_LOG_INSTANCE, new TestLogger(false, true));
+ engine.addProperty(Velocity.UBERSPECT_CLASSNAME, "org.apache.velocity.util.introspection.UberspectImpl");
+ engine.addProperty(Velocity.UBERSPECT_CLASSNAME, "org.apache.velocity.util.introspection.DeprecatedCheckUberspector");
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("obj1", new StandardObject());
+ context.put("obj2", new DeprecatedObject());
+ }
+
+ public void testDeprecatedCheck()
+ throws Exception
+ {
+ engine.init(); // make sure the engine is initialized, so that we get the logger we configured
+ TestLogger logger =(TestLogger)engine.getLog();
+ logger.startCapture(); // reset log capture
+ StringWriter writer = new StringWriter();
+ engine.evaluate(context, writer, "test", "$obj1.foo() $obj1.bar $obj2.foo() $obj2.bar");
+ String log = logger.getLog();
+ String lines[] = log.split("\\r?\\n");
+ assertEquals(lines[0], " [info] Deprecated usage of method [org.apache.velocity.test.util.introspection.DeprecatedCheckUberspectorsTestCase.StandardObject.foo] in test@1,7");
+ assertEquals(lines[1], " [info] Deprecated usage of getter [org.apache.velocity.test.util.introspection.DeprecatedCheckUberspectorsTestCase.StandardObject.getBar] in test@1,19");
+ assertEquals(lines[2], " [info] Deprecated usage of method [org.apache.velocity.test.util.introspection.DeprecatedCheckUberspectorsTestCase.DeprecatedObject.foo] in test@1,29");
+ assertEquals(lines[3], " [info] Deprecated usage of getter [org.apache.velocity.test.util.introspection.DeprecatedCheckUberspectorsTestCase.DeprecatedObject.getBar] in test@1,41");
+ }
+
+ public static class StandardObject
+ {
+ @Deprecated
+ public String foo()
+ {
+ return "foo";
+ }
+
+ @Deprecated
+ public String getBar()
+ {
+ return "bar";
+ }
+ }
+
+ @Deprecated
+ public static class DeprecatedObject
+ {
+ public String foo()
+ {
+ return "foo";
+ }
+
+ public String getBar()
+ {
+ return "bar";
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/EnumConstantConversionTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/EnumConstantConversionTestCase.java
new file mode 100644
index 00000000..3fbddf22
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/EnumConstantConversionTestCase.java
@@ -0,0 +1,77 @@
+package org.apache.velocity.test.util.introspection;
+
+/*
+ * 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 junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.test.BaseTestCase;
+
+/**
+ * Tests DeprecatedCheckUberspector
+ */
+public class EnumConstantConversionTestCase extends BaseTestCase {
+
+ public EnumConstantConversionTestCase(String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(EnumConstantConversionTestCase.class);
+ }
+
+ public static class Obj
+ {
+ public enum Color { RED, GREEN }
+
+ public String getAction(Color color)
+ {
+ switch (color)
+ {
+ case RED: return "Stop";
+ case GREEN: return "Go";
+ default: return "???";
+ }
+ }
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("obj", new Obj());
+ }
+
+ public void testStringToEnumConversion()
+ throws Exception
+ {
+ assertEvalEquals("Stop", "$obj.getAction('RED')");
+ assertEvalEquals("Go", "$obj.getAction('GREEN')");
+ try
+ {
+ String result = evaluate("$obj.getAction('BLUE')");
+ fail();
+ }
+ catch(MethodInvocationException mie) {}
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java
new file mode 100644
index 00000000..c074be5e
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/util/introspection/UberspectImplTestCase.java
@@ -0,0 +1,134 @@
+package org.apache.velocity.test.util.introspection;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+import org.apache.velocity.test.BaseTestCase;
+import org.apache.velocity.test.misc.TestLogger;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+/*
+ * 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.
+ */
+
+/**
+ * Tests the default uberspector.
+ */
+public class UberspectImplTestCase extends BaseTestCase
+{
+
+ public UberspectImplTestCase(String name)
+ throws Exception
+ {
+ super(name);
+ }
+
+ public static Test suite()
+ {
+ return new TestSuite(UberspectImplTestCase.class);
+ }
+
+ @Override
+ protected void setUpEngine(VelocityEngine engine)
+ {
+ engine.setProperty(RuntimeConstants.RUNTIME_LOG_INSTANCE, new TestLogger());
+ engine.addProperty(RuntimeConstants.UBERSPECT_CLASSNAME, "org.apache.velocity.util.introspection.UberspectImpl");
+ }
+
+ @Override
+ protected void setUpContext(VelocityContext context)
+ {
+ context.put("privateClass", new PrivateClass());
+ context.put("privateMethod", new PrivateMethod());
+ context.put("publicMethod", new PublicMethod());
+ context.put("iterable", new SomeIterable());
+ context.put("over", new OverloadedMethods());
+ }
+
+ public void testPrivateIterator()
+ throws Exception
+ {
+ assertEvalEquals("", "#foreach($i in $privateClass)$i#end");
+ assertEvalEquals("", "#foreach($i in $privateMethod)$i#end");
+ assertEvalEquals("123", "#foreach($i in $publicMethod)$i#end");
+ }
+
+ public void testIterableForeach()
+ {
+ assertEvalEquals("123", "#foreach($i in $iterable)$i#end");
+ }
+
+ private class PrivateClass
+ {
+ public Iterator iterator()
+ {
+ return Arrays.asList("X", "Y", "Z").iterator();
+ }
+ }
+
+ public class PrivateMethod
+ {
+ private Iterator iterator()
+ {
+ return Arrays.asList("A", "B", "C").iterator();
+ }
+ }
+
+ public class PublicMethod
+ {
+ public Iterator iterator()
+ {
+ return Arrays.asList("1", "2", "3").iterator();
+ }
+ }
+
+ public class SomeIterable implements Iterable
+ {
+ @Override
+ public Iterator iterator()
+ {
+ return Arrays.asList("1", "2", "3").iterator();
+ }
+ }
+
+ public class OverloadedMethods
+ {
+ public String foo() { return "foo0"; }
+ public String foo(String arg1) { return "foo1"; }
+ public String foo(String arg1, String arg2) { return "foo2"; }
+
+ public String bar(Number n, int i) { return "bar1"; }
+ public String bar(Number n, String s) { return "bar2"; }
+ }
+
+ public void testOverloadedMethods()
+ {
+ assertEvalEquals("foo0", "$over.foo()");
+ assertEvalEquals("foo1", "$over.foo('a')");
+ assertEvalEquals("foo1", "$over.foo($null)");
+ assertEvalEquals("foo2", "$over.foo('a', 'b')");
+ assertEvalEquals("foo2", "$over.foo('a', $null)");
+ assertEvalEquals("bar1", "$over.bar(1,1)");
+ assertEvalEquals("$over.bar(1,1.1)", "$over.bar(1,1.1)"); // this one is definitely ambiguous
+ assertEvalEquals("bar2", "$over.bar(1,'1.1')");
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/test/view/TemplateNodeView.java b/velocity-engine-core/src/test/java/org/apache/velocity/test/view/TemplateNodeView.java
new file mode 100644
index 00000000..2fcf411a
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/test/view/TemplateNodeView.java
@@ -0,0 +1,85 @@
+package org.apache.velocity.test.view;
+
+/*
+ * 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.RuntimeSingleton;
+import org.apache.velocity.runtime.parser.node.SimpleNode;
+import org.apache.velocity.runtime.visitor.NodeViewMode;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+
+/**
+ * Simple class for dumping the AST for a template.
+ * Good for debugging and writing new directives.
+ */
+public class TemplateNodeView
+{
+ /**
+ * Root of the AST node structure that results from
+ * parsing a template.
+ */
+ private SimpleNode document;
+
+ /**
+ * Visitor used to traverse the AST node structure
+ * and produce a visual representation of the
+ * node structure. Very good for debugging and
+ * writing new directives.
+ */
+ private NodeViewMode visitor;
+
+ /**
+ * Default constructor: sets up the Velocity
+ * Runtime, creates the visitor for traversing
+ * the node structure and then produces the
+ * visual representation by the visitation.
+ */
+ public TemplateNodeView(String templateFile)
+ {
+ try
+ {
+ RuntimeSingleton.init("velocity.properties");
+
+ InputStreamReader isr = new InputStreamReader(
+ new FileInputStream(templateFile),
+ RuntimeSingleton.getString(RuntimeSingleton.INPUT_ENCODING));
+
+ BufferedReader br = new BufferedReader( isr );
+
+ Template tmpl = new Template();
+ tmpl.setName(templateFile);
+ document = RuntimeSingleton.parse( br, tmpl);
+
+ visitor = new NodeViewMode();
+ visitor.setContext(null);
+ visitor.setWriter(new PrintWriter(System.out));
+ document.jjtAccept(visitor, null);
+ }
+ catch (Exception e)
+ {
+ System.out.println(e);
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/java/org/apache/velocity/util/SimplePoolTestCase.java b/velocity-engine-core/src/test/java/org/apache/velocity/util/SimplePoolTestCase.java
new file mode 100644
index 00000000..87a52b68
--- /dev/null
+++ b/velocity-engine-core/src/test/java/org/apache/velocity/util/SimplePoolTestCase.java
@@ -0,0 +1,67 @@
+package org.apache.velocity.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 junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Simpletest for the SimplePool
+ *
+ * @version $Id$
+ */
+public class SimplePoolTestCase extends TestCase
+{
+ public static Test suite()
+ {
+ return new TestSuite(SimplePoolTestCase.class);
+ }
+
+ public SimplePoolTestCase(String testName)
+ {
+ super(testName);
+ }
+
+ public void testPool()
+ throws Exception
+ {
+ SimplePool sp = new SimplePool(10);
+
+ for (int i=0; i<10; i++)
+ {
+ sp.put(i);
+ }
+
+ for (int i=9; i>=0; i--)
+ {
+ Integer obj = (Integer) sp.get();
+
+ assertTrue(i == obj);
+ }
+
+ Object[] pool = sp.getPool();
+
+ for (int i=0; i<10; i++)
+ {
+ assertTrue("Pool not empty", pool[i] == null);
+ }
+ }
+}
diff --git a/velocity-engine-core/src/test/resources/absolute/absolute.vm b/velocity-engine-core/src/test/resources/absolute/absolute.vm
new file mode 100644
index 00000000..206add38
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/absolute/absolute.vm
@@ -0,0 +1,12 @@
+#*
+
+@test absolute.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+I am absolute.vm
diff --git a/velocity-engine-core/src/test/resources/absolute/compare/absolute.cmp b/velocity-engine-core/src/test/resources/absolute/compare/absolute.cmp
new file mode 100644
index 00000000..d34753b0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/absolute/compare/absolute.cmp
@@ -0,0 +1,3 @@
+
+
+I am absolute.vm
diff --git a/velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_disabled b/velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_disabled
new file mode 100644
index 00000000..59d87824
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_disabled
@@ -0,0 +1,40 @@
+
+
+
+
+A) Null Values
+1. missing argument
+foo=$foo => disp miss [foo=$foo] => foo=$foo
+foo=$foo => setn miss [foo=$foo] => foo=$foo
+foo=$foo => setv miss [foo=inn] => foo=inn
+2. null argument
+foo=$foo => disp null [foo=$foo] => foo=$foo
+foo=$foo => setn null [foo=$foo] => foo=$foo
+foo=$foo => setv null [foo=inn] => foo=inn
+3. non-colliding argument
+foo=$foo => disp ncol [foo=$foo] => foo=$foo
+foo=$foo => setn ncol [foo=$foo] => foo=$foo
+foo=$foo => setv ncol [foo=inn] => foo=inn
+4. colliding argument
+foo=$foo => disp coll [foo=$foo] => foo=$foo
+foo=$foo => setn coll [foo=$foo] => foo=$foo
+foo=$foo => setv coll [foo=inn] => foo=inn
+
+B) Non-null Values
+1. missing argument
+foo=foo => disp miss [foo=$foo] => foo=foo
+foo=foo => setn miss [foo=$foo] => foo=foo
+foo=foo => setv miss [foo=inn] => foo=inn
+2. null argument
+foo=foo => disp null [foo=$foo] => foo=foo
+foo=foo => setn null [foo=$foo] => foo=foo
+foo=foo => setv null [foo=inn] => foo=inn
+3. non-colliding argument
+foo=foo => disp ncol [foo=bar] => foo=foo
+foo=foo => setn ncol [foo=$foo] => foo=$foo
+foo=foo => setv ncol [foo=bar] => foo=foo
+4. colliding argument
+foo=foo => disp coll [foo=foo] => foo=foo
+foo=foo => setn coll [foo=$foo] => foo=$foo
+foo=foo => setv coll [foo=foo] => foo=foo
+
diff --git a/velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_enabled b/velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_enabled
new file mode 100644
index 00000000..934d659e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/bc_mode/compare/test_bc_mode.bc_mode_enabled
@@ -0,0 +1,40 @@
+
+
+
+
+A) Null Values
+1. missing argument
+foo=$foo => disp miss [foo=$foo] => foo=$foo
+foo=$foo => setn miss [foo=$foo] => foo=$foo
+foo=$foo => setv miss [foo=inn] => foo=inn
+2. null argument
+foo=$foo => disp null [foo=$null] => foo=$foo
+foo=$foo => setn null [foo=$foo] => foo=$foo
+foo=$foo => setv null [foo=inn] => foo=inn
+3. non-colliding argument
+foo=$foo => disp ncol [foo=$bar] => foo=$foo
+foo=$foo => setn ncol [foo=$foo] => foo=$foo
+foo=$foo => setv ncol [foo=inn] => foo=inn
+4. colliding argument
+foo=$foo => disp coll [foo=$foo] => foo=$foo
+foo=$foo => setn coll [foo=$foo] => foo=$foo
+foo=$foo => setv coll [foo=inn] => foo=inn
+
+B) Non-null Values
+1. missing argument
+foo=foo => disp miss [foo=foo] => foo=foo
+foo=foo => setn miss [foo=$foo] => foo=$foo
+foo=foo => setv miss [foo=foo] => foo=foo
+2. null argument
+foo=foo => disp null [foo=$null] => foo=foo
+foo=foo => setn null [foo=$foo] => foo=foo
+foo=foo => setv null [foo=inn] => foo=inn
+3. non-colliding argument
+foo=foo => disp ncol [foo=bar] => foo=foo
+foo=foo => setn ncol [foo=$foo] => foo=$foo
+foo=foo => setv ncol [foo=bar] => foo=foo
+4. colliding argument
+foo=foo => disp coll [foo=foo] => foo=foo
+foo=foo => setn coll [foo=$foo] => foo=$foo
+foo=foo => setv coll [foo=foo] => foo=foo
+
diff --git a/velocity-engine-core/src/test/resources/bc_mode/test_bc_mode.vtl b/velocity-engine-core/src/test/resources/bc_mode/test_bc_mode.vtl
new file mode 100644
index 00000000..8ab1050b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/bc_mode/test_bc_mode.vtl
@@ -0,0 +1,39 @@
+#macro(store)#set($foo_ = $foo)#set($bar_ = $bar)#end##
+#macro(reset)#set($foo = $foo_)#set($bar = $bar_)#end##
+#macro(state)foo=$foo#end##
+
+#macro(disp $foo)[foo=$foo]#end
+#macro(setn $foo)#set($foo=$null)#disp($foo)#end
+#macro(setv $foo)#if(!$foo)#set($foo='inn')#end#disp($foo)#end
+#macro(sub)#set($foo='sub')#end
+
+#macro(test)
+1. missing argument
+#store#state => disp miss #disp() => #state#reset
+#store#state => setn miss #setn() => #state#reset
+#store#state => setv miss #setv() => #state#reset
+2. null argument
+#store#state => disp null #disp($null) => #state#reset
+#store#state => setn null #setn($null) => #state#reset
+#store#state => setv null #setv($null) => #state#reset
+3. non-colliding argument
+#store#state => disp ncol #disp($bar) => #state#reset
+#store#state => setn ncol #setn($bar) => #state#reset
+#store#state => setv ncol #setv($bar) => #state#reset
+4. colliding argument
+#store#state => disp coll #disp($foo) => #state#reset
+#store#state => setn coll #setn($foo) => #state#reset
+#store#state => setv coll #setv($foo) => #state#reset
+#end
+
+
+A) Null Values
+#set($foo = $null)
+#set($bar = $null)
+#test()
+
+B) Non-null Values
+#set($foo = 'foo')
+#set($bar = 'bar')
+#test()
+
diff --git a/velocity-engine-core/src/test/resources/configuration/compare/output.cmp b/velocity-engine-core/src/test/resources/configuration/compare/output.cmp
new file mode 100644
index 00000000..80c03d5e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/configuration/compare/output.cmp
@@ -0,0 +1,123 @@
+--------------------------------------------------
+Testing order of keys ...
+--------------------------------------------------
+
+01
+02
+03
+04
+05
+06
+07
+08
+09
+10
+resource.loaders
+resource.loader.file.class
+resource.loader.file.description
+resource.loader.file.path
+resource.loader.classpath.class
+resource.loader.classpath.description
+resource.loader.datasource.class
+resource.loader.datasource.description
+logger.type
+config.string.value
+config.boolean.value
+config.byte.value
+config.short.value
+config.int.value
+config.long.value
+config.float.value
+config.double.value
+escape.comma1
+escape.comma2
+include1.property
+include2.property
+
+--------------------------------------------------
+Testing retrieval of CSV values ...
+--------------------------------------------------
+
+file
+classpath
+datasource
+
+--------------------------------------------------
+Testing subset(prefix).getKeys() ...
+--------------------------------------------------
+
+class
+description
+path
+
+--------------------------------------------------
+Testing getVector(prefix) ...
+--------------------------------------------------
+
+/path01
+/path02
+/path03
+
+--------------------------------------------------
+Testing getString(key) ...
+--------------------------------------------------
+
+string
+
+--------------------------------------------------
+Testing getBoolean(key) ...
+--------------------------------------------------
+
+true
+
+--------------------------------------------------
+Testing getByte(key) ...
+--------------------------------------------------
+
+1
+
+--------------------------------------------------
+Testing getShort(key) ...
+--------------------------------------------------
+
+1
+
+--------------------------------------------------
+Testing getInt(key) ...
+--------------------------------------------------
+
+30000
+
+--------------------------------------------------
+Testing getLong(key) ...
+--------------------------------------------------
+
+1000000
+
+--------------------------------------------------
+Testing getFloat(key) ...
+--------------------------------------------------
+
+3.14
+
+--------------------------------------------------
+Testing getDouble(key) ...
+--------------------------------------------------
+
+3.14159265358793
+
+--------------------------------------------------
+Testing escaped-comma scalar...
+--------------------------------------------------
+
+foo,
+
+--------------------------------------------------
+Testing escaped-comma vector...
+--------------------------------------------------
+
+bar,lala
+woogie,bjork!
+
+
+
diff --git a/velocity-engine-core/src/test/resources/configuration/include1.properties b/velocity-engine-core/src/test/resources/configuration/include1.properties
new file mode 100644
index 00000000..c2e691ae
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/configuration/include1.properties
@@ -0,0 +1,17 @@
+# 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.
+include1.property = somnambulance
diff --git a/velocity-engine-core/src/test/resources/configuration/include2.properties b/velocity-engine-core/src/test/resources/configuration/include2.properties
new file mode 100644
index 00000000..d683a3c5
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/configuration/include2.properties
@@ -0,0 +1,17 @@
+# 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.
+include2.property = insomnia
diff --git a/velocity-engine-core/src/test/resources/configuration/test-config.properties b/velocity-engine-core/src/test/resources/configuration/test-config.properties
new file mode 100644
index 00000000..a3bcd560
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/configuration/test-config.properties
@@ -0,0 +1,99 @@
+# 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.
+01 = 01
+02 = 02
+03 = 03
+04 = 04
+05 = 05
+06 = 06
+07 = 07
+08 = 08
+09 = 09
+10 = 10
+
+# ---------------------------------------------------------
+# Test CSV properties
+# ---------------------------------------------------------
+resource.loader = file, classpath, datasource
+
+file.resource.loader.class = FileResourceLoader
+file.resource.loader.description = File Resource Loader
+file.resource.loader.path = /path01
+file.resource.loader.path = /path02
+file.resource.loader.path = /path03
+
+classpath.resource.loader.class = ClasspathResourceLoader
+classpath.resource.loader.description = Classpath Resource Loader
+
+datasource.resource.loader.class = DataSourceResourceLoader
+datasource.resource.loader.description = Datasource Resource Loader
+
+# ---------------------------------------------------------
+# Test multi same value keys
+# ---------------------------------------------------------
+logger.type = file
+logger.type = console
+logger.type = db
+
+# ---------------------------------------------------------
+# Testing String retrieval
+# ---------------------------------------------------------
+config.string.value = string
+
+# ---------------------------------------------------------
+# Testing boolean retrieval
+# ---------------------------------------------------------
+config.boolean.value = true
+
+# ---------------------------------------------------------
+# Testing byte retrieval
+# ---------------------------------------------------------
+config.byte.value = 1
+
+# ---------------------------------------------------------
+# Testing short retrieval
+# ---------------------------------------------------------
+config.short.value = 1
+
+# ---------------------------------------------------------
+# Testing integer retrieval
+# ---------------------------------------------------------
+config.int.value = 30000
+
+# ---------------------------------------------------------
+# Testing long retrieval
+# ---------------------------------------------------------
+config.long.value = 1000000
+
+# ---------------------------------------------------------
+# Testing float retrieval
+# ---------------------------------------------------------
+config.float.value = 3.14
+
+# ---------------------------------------------------------
+# Testing double retrieval
+# ---------------------------------------------------------
+config.double.value = 3.14159265358793
+
+# ---------------------------------------------------------
+# Testing escaped commas
+# ---------------------------------------------------------
+escape.comma1 = foo\,
+escape.comma2 = bar\,lala,woogie\,,bjork!
+
+include = include1.properties
+include = ./include2.properties
diff --git a/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp b/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp
new file mode 100644
index 00000000..00b7047b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/compare/matrix.cmp
@@ -0,0 +1,600 @@
+<!doctype html>
+
+
+
+
+<html>
+ <head>
+ <style type="text/css">
+ table
+ {
+ border: solid 1px black;
+ border-collapse: collapse;
+ }
+ td, th
+ {
+ border: solid 1px black;
+ }
+ .strict
+ {
+ color: green;
+ }
+ .implicit
+ {
+ color: blue;
+ }
+ .explicit
+ {
+ color: magenta;
+ }
+ .none
+ {
+ color: red;
+ }
+
+ </style>
+ </head>
+ <body>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ provided &rarr;<br/>
+ expected &darr;
+ </th>
+ <th>boolean</th>
+ <th>char</th>
+ <th>byte</th>
+ <th>short</th>
+ <th>int</th>
+ <th>long</th>
+ <th>float</th>
+ <th>double</th>
+ <th>Boolean</th>
+ <th>Character</th>
+ <th>Byte</th>
+ <th>Short</th>
+ <th>Integer</th>
+ <th>Long</th>
+ <th>BigInteger</th>
+ <th>Float</th>
+ <th>Double</th>
+ <th>BigDecimal</th>
+ <th>Number</th>
+ <th>String</th>
+ <th>Object</th>
+ <th>null</th>
+ </tr>
+ <tr>
+ <th>boolean</th>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>char</th>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>byte</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>short</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>int</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>long</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>float</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>double</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ </tr>
+ <tr>
+ <th>Boolean</th>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Character</th>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Byte</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Short</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Integer</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Long</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>BigInteger</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Float</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Double</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>BigDecimal</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Number</th>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="none">none</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>String</th>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="explicit">explicit</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ <tr>
+ <th>Object</th>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="implicit">implicit</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ <td><span class="strict">strict</span></td>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </body>
+</html>
+
+
diff --git a/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp
new file mode 100644
index 00000000..a5ac7d0e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_with_handler.cmp
@@ -0,0 +1,992 @@
+A. bool-true Value java.lang.Boolean true
+ boolean ok: true
+ byte ok: 1
+ short ok: 1
+ int ok: 1
+ long ok: 1
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 1
+ Short ok: 1
+ Integer ok: 1
+ Long ok: 1
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: true
+ $target.objectEnum($value)
+ String ok: true
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+B. bool-false Value java.lang.Boolean false
+ boolean ok: false
+ byte ok: 0
+ short ok: 0
+ int ok: 0
+ long ok: 0
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ Boolean ok: false
+ Byte ok: 0
+ Short ok: 0
+ Integer ok: 0
+ Long ok: 0
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: false
+ $target.objectEnum($value)
+ String ok: false
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+C. byte-0 Value java.lang.Byte 0
+ boolean ok: false
+ byte ok: 0
+ short ok: 0
+ int ok: 0
+ long ok: 0
+ float ok: 0.0
+ double ok: 0.0
+ $target.integralChar($value)
+ Boolean ok: false
+ Byte ok: 0
+ Short ok: 0
+ Integer ok: 0
+ Long ok: 0
+ BigInteger ok: 0
+ Float ok: 0.0
+ Double ok: 0.0
+ BigDecimal ok: 0
+ $target.objectCharacter($value)
+ Number ok: 0
+ Object ok: 0
+ $target.objectEnum($value)
+ String ok: 0
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+D. byte-1 Value java.lang.Byte 1
+ boolean ok: true
+ byte ok: 1
+ short ok: 1
+ int ok: 1
+ long ok: 1
+ float ok: 1.0
+ double ok: 1.0
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 1
+ Short ok: 1
+ Integer ok: 1
+ Long ok: 1
+ BigInteger ok: 1
+ Float ok: 1.0
+ Double ok: 1.0
+ BigDecimal ok: 1
+ $target.objectCharacter($value)
+ Number ok: 1
+ Object ok: 1
+ $target.objectEnum($value)
+ String ok: 1
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+E. short Value java.lang.Short 125
+ boolean ok: true
+ byte ok: 125
+ short ok: 125
+ int ok: 125
+ long ok: 125
+ float ok: 125.0
+ double ok: 125.0
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 125
+ Short ok: 125
+ Integer ok: 125
+ Long ok: 125
+ BigInteger ok: 125
+ Float ok: 125.0
+ Double ok: 125.0
+ BigDecimal ok: 125
+ $target.objectCharacter($value)
+ Number ok: 125
+ Object ok: 125
+ $target.objectEnum($value)
+ String ok: 125
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+F. int Value java.lang.Integer 24323
+ boolean ok: true
+ integralByte -> NumberFormatException: value out of range: 24323
+ short ok: 24323
+ int ok: 24323
+ long ok: 24323
+ float ok: 24323.0
+ double ok: 24323.0
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> NumberFormatException: value out of range: 24323
+ Short ok: 24323
+ Integer ok: 24323
+ Long ok: 24323
+ BigInteger ok: 24323
+ Float ok: 24323.0
+ Double ok: 24323.0
+ BigDecimal ok: 24323
+ $target.objectCharacter($value)
+ Number ok: 24323
+ Object ok: 24323
+ $target.objectEnum($value)
+ String ok: 24323
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+G. long Value java.lang.Long 5235235
+ boolean ok: true
+ integralByte -> NumberFormatException: value out of range: 5235235
+ integralShort -> NumberFormatException: value out of range: 5235235
+ int ok: 5235235
+ long ok: 5235235
+ float ok: 5235235.0
+ double ok: 5235235.0
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> NumberFormatException: value out of range: 5235235
+ objectShort -> NumberFormatException: value out of range: 5235235
+ Integer ok: 5235235
+ Long ok: 5235235
+ BigInteger ok: 5235235
+ Float ok: 5235235.0
+ Double ok: 5235235.0
+ BigDecimal ok: 5235235
+ $target.objectCharacter($value)
+ Number ok: 5235235
+ Object ok: 5235235
+ $target.objectEnum($value)
+ String ok: 5235235
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+H. float Value java.lang.Float 34523.344
+ boolean ok: true
+ integralByte -> NumberFormatException: value out of range: 34523.344
+ integralShort -> NumberFormatException: value out of range: 34523.344
+ int ok: 34523
+ long ok: 34523
+ float ok: 34523.344
+ double ok: 34523.34375
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> NumberFormatException: value out of range: 34523.344
+ objectShort -> NumberFormatException: value out of range: 34523.344
+ Integer ok: 34523
+ Long ok: 34523
+ $target.objectBigInteger($value)
+ Float ok: 34523.344
+ Double ok: 34523.34375
+ BigDecimal ok: 34523.34375
+ $target.objectCharacter($value)
+ Number ok: 34523.344
+ Object ok: 34523.344
+ $target.objectEnum($value)
+ String ok: 34523.344
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+I. double Value java.lang.Double 54235.3253
+ boolean ok: true
+ integralByte -> NumberFormatException: value out of range: 54235.3253
+ integralShort -> NumberFormatException: value out of range: 54235.3253
+ int ok: 54235
+ long ok: 54235
+ float ok: 54235.324
+ double ok: 54235.3253
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> NumberFormatException: value out of range: 54235.3253
+ objectShort -> NumberFormatException: value out of range: 54235.3253
+ Integer ok: 54235
+ Long ok: 54235
+ $target.objectBigInteger($value)
+ Float ok: 54235.324
+ Double ok: 54235.3253
+ BigDecimal ok: 54235.3253
+ $target.objectCharacter($value)
+ Number ok: 54235.3253
+ Object ok: 54235.3253
+ $target.objectEnum($value)
+ String ok: 54235.3253
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+J. char Value java.lang.Character @
+ boolean ok: true
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ char ok: @
+ Boolean ok: true
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ Character ok: @
+ $target.objectNumber($value)
+ Object ok: @
+ $target.objectEnum($value)
+ String ok: @
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+K. object Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj instance of Obj
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: instance of Obj
+ $target.objectEnum($value)
+ String ok: instance of Obj
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+L. enum Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj$Color GREEN
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: GREEN
+ Enum ok: GREEN
+ String ok: GREEN
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+M. string Value java.lang.String foo
+ boolean ok: false
+ integralByte -> NumberFormatException: For input string: "foo"
+ integralShort -> NumberFormatException: For input string: "foo"
+ integralInt -> NumberFormatException: For input string: "foo"
+ integralLong -> NumberFormatException: For input string: "foo"
+ integralFloat -> NumberFormatException: For input string: "foo"
+ integralDouble -> NumberFormatException: For input string: "foo"
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: For input string: "foo"
+ objectShort -> NumberFormatException: For input string: "foo"
+ objectInt -> NumberFormatException: For input string: "foo"
+ objectLong -> NumberFormatException: For input string: "foo"
+ objectBigInteger -> NumberFormatException: For input string: "foo"
+ objectFloat -> NumberFormatException: For input string: "foo"
+ objectDouble -> NumberFormatException: For input string: "foo"
+ objectBigDecimal -> NumberFormatException: null
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: foo
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.foo
+ String ok: foo
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ Locale ok: foo
+M. string-green Value java.lang.String green
+ boolean ok: false
+ integralByte -> NumberFormatException: For input string: "green"
+ integralShort -> NumberFormatException: For input string: "green"
+ integralInt -> NumberFormatException: For input string: "green"
+ integralLong -> NumberFormatException: For input string: "green"
+ integralFloat -> NumberFormatException: For input string: "green"
+ integralDouble -> NumberFormatException: For input string: "green"
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: For input string: "green"
+ objectShort -> NumberFormatException: For input string: "green"
+ objectInt -> NumberFormatException: For input string: "green"
+ objectLong -> NumberFormatException: For input string: "green"
+ objectBigInteger -> NumberFormatException: For input string: "green"
+ objectFloat -> NumberFormatException: For input string: "green"
+ objectDouble -> NumberFormatException: For input string: "green"
+ objectBigDecimal -> NumberFormatException: null
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: green
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.green
+ String ok: green
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: green
+N. string-empty Value java.lang.String
+ boolean ok: false
+ integralByte -> NumberFormatException: For input string: ""
+ integralShort -> NumberFormatException: For input string: ""
+ integralInt -> NumberFormatException: For input string: ""
+ integralLong -> NumberFormatException: For input string: ""
+ integralFloat -> NumberFormatException: empty String
+ integralDouble -> NumberFormatException: empty String
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: For input string: ""
+ objectShort -> NumberFormatException: For input string: ""
+ objectInt -> NumberFormatException: For input string: ""
+ objectLong -> NumberFormatException: For input string: ""
+ objectBigInteger -> NumberFormatException: Zero length BigInteger
+ objectFloat -> NumberFormatException: empty String
+ objectDouble -> NumberFormatException: empty String
+ objectBigDecimal -> NumberFormatException: null
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok:
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.
+ String ok:
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ Locale ok:
+O. string-false Value java.lang.String false
+ boolean ok: false
+ integralByte -> NumberFormatException: For input string: "false"
+ integralShort -> NumberFormatException: For input string: "false"
+ integralInt -> NumberFormatException: For input string: "false"
+ integralLong -> NumberFormatException: For input string: "false"
+ integralFloat -> NumberFormatException: For input string: "false"
+ integralDouble -> NumberFormatException: For input string: "false"
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: For input string: "false"
+ objectShort -> NumberFormatException: For input string: "false"
+ objectInt -> NumberFormatException: For input string: "false"
+ objectLong -> NumberFormatException: For input string: "false"
+ objectBigInteger -> NumberFormatException: For input string: "false"
+ objectFloat -> NumberFormatException: For input string: "false"
+ objectDouble -> NumberFormatException: For input string: "false"
+ objectBigDecimal -> NumberFormatException: null
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: false
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.false
+ String ok: false
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: false
+P. string-true Value java.lang.String true
+ boolean ok: true
+ integralByte -> NumberFormatException: For input string: "true"
+ integralShort -> NumberFormatException: For input string: "true"
+ integralInt -> NumberFormatException: For input string: "true"
+ integralLong -> NumberFormatException: For input string: "true"
+ integralFloat -> NumberFormatException: For input string: "true"
+ integralDouble -> NumberFormatException: For input string: "true"
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> NumberFormatException: For input string: "true"
+ objectShort -> NumberFormatException: For input string: "true"
+ objectInt -> NumberFormatException: For input string: "true"
+ objectLong -> NumberFormatException: For input string: "true"
+ objectBigInteger -> NumberFormatException: For input string: "true"
+ objectFloat -> NumberFormatException: For input string: "true"
+ objectDouble -> NumberFormatException: For input string: "true"
+ objectBigDecimal -> NumberFormatException: null
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: true
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.true
+ String ok: true
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: true
+Q. string-zero Value java.lang.String 0
+ boolean ok: false
+ byte ok: 0
+ short ok: 0
+ int ok: 0
+ long ok: 0
+ float ok: 0.0
+ double ok: 0.0
+ $target.integralChar($value)
+ Boolean ok: false
+ Byte ok: 0
+ Short ok: 0
+ Integer ok: 0
+ Long ok: 0
+ BigInteger ok: 0
+ Float ok: 0.0
+ Double ok: 0.0
+ BigDecimal ok: 0
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 0
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.0
+ String ok: 0
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: 0
+R. string-integral Value java.lang.String 123
+ boolean ok: false
+ byte ok: 123
+ short ok: 123
+ int ok: 123
+ long ok: 123
+ float ok: 123.0
+ double ok: 123.0
+ $target.integralChar($value)
+ Boolean ok: false
+ Byte ok: 123
+ Short ok: 123
+ Integer ok: 123
+ Long ok: 123
+ BigInteger ok: 123
+ Float ok: 123.0
+ Double ok: 123.0
+ BigDecimal ok: 123
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 123
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.123
+ String ok: 123
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: 123
+S. string-big-integral Value java.lang.String 12345678
+ boolean ok: false
+ integralByte -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+ integralShort -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+ int ok: 12345678
+ long ok: 12345678
+ float ok: 1.2345678E7
+ double ok: 1.2345678E7
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+ objectShort -> NumberFormatException: Value out of range. Value:"12345678" Radix:10
+ Integer ok: 12345678
+ Long ok: 12345678
+ BigInteger ok: 12345678
+ Float ok: 1.2345678E7
+ Double ok: 1.2345678E7
+ BigDecimal ok: 12345678
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 12345678
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.12345678
+ String ok: 12345678
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: 12345678
+T. string-floating Value java.lang.String 123.345
+ boolean ok: false
+ integralByte -> NumberFormatException: For input string: "123.345"
+ integralShort -> NumberFormatException: For input string: "123.345"
+ integralInt -> NumberFormatException: For input string: "123.345"
+ integralLong -> NumberFormatException: For input string: "123.345"
+ float ok: 123.345
+ double ok: 123.345
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: For input string: "123.345"
+ objectShort -> NumberFormatException: For input string: "123.345"
+ objectInt -> NumberFormatException: For input string: "123.345"
+ objectLong -> NumberFormatException: For input string: "123.345"
+ objectBigInteger -> NumberFormatException: For input string: "123.345"
+ Float ok: 123.345
+ Double ok: 123.345
+ BigDecimal ok: 123.345
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 123.345
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.123.345
+ String ok: 123.345
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ locale -> IllegalArgumentException: Invalid locale format: 123.345
+U. null Value $value.class.name $value
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ Boolean ok: null
+ Byte ok: null
+ Short ok: null
+ Integer ok: null
+ Long ok: null
+ BigInteger ok: null
+ Float ok: null
+ Double ok: null
+ BigDecimal ok: null
+ Character ok: null
+ Number ok: null
+ Object ok: null
+ Enum ok: null
+ String ok: null
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ Locale ok: null
+V. locale Value java.lang.String fr_FR
+ boolean ok: false
+ integralByte -> NumberFormatException: For input string: "fr_FR"
+ integralShort -> NumberFormatException: For input string: "fr_FR"
+ integralInt -> NumberFormatException: For input string: "fr_FR"
+ integralLong -> NumberFormatException: For input string: "fr_FR"
+ integralFloat -> NumberFormatException: For input string: "fr_FR"
+ integralDouble -> NumberFormatException: For input string: "fr_FR"
+ $target.integralChar($value)
+ Boolean ok: false
+ objectByte -> NumberFormatException: For input string: "fr_FR"
+ objectShort -> NumberFormatException: For input string: "fr_FR"
+ objectInt -> NumberFormatException: For input string: "fr_FR"
+ objectLong -> NumberFormatException: For input string: "fr_FR"
+ objectBigInteger -> NumberFormatException: For input string: "fr_FR"
+ objectFloat -> NumberFormatException: For input string: "fr_FR"
+ objectDouble -> NumberFormatException: For input string: "fr_FR"
+ objectBigDecimal -> NumberFormatException: null
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: fr_FR
+ objectEnum -> IllegalArgumentException: No enum constant org.apache.velocity.test.util.introspection.ConversionHandlerTestCase.Obj.Color.fr_FR
+ String ok: fr_FR
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ Locale ok: fr_FR
+W. BigInteger zero Value java.math.BigInteger 0
+ boolean ok: false
+ byte ok: 0
+ short ok: 0
+ int ok: 0
+ long ok: 0
+ float ok: 0.0
+ double ok: 0.0
+ $target.integralChar($value)
+ Boolean ok: false
+ Byte ok: 0
+ Short ok: 0
+ Integer ok: 0
+ Long ok: 0
+ BigInteger ok: 0
+ Float ok: 0.0
+ Double ok: 0.0
+ BigDecimal ok: 0
+ $target.objectCharacter($value)
+ Number ok: 0
+ Object ok: 0
+ $target.objectEnum($value)
+ String ok: 0
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+X. BigInteger one Value java.math.BigInteger 1
+ boolean ok: true
+ byte ok: 1
+ short ok: 1
+ int ok: 1
+ long ok: 1
+ float ok: 1.0
+ double ok: 1.0
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 1
+ Short ok: 1
+ Integer ok: 1
+ Long ok: 1
+ BigInteger ok: 1
+ Float ok: 1.0
+ Double ok: 1.0
+ BigDecimal ok: 1
+ $target.objectCharacter($value)
+ Number ok: 1
+ Object ok: 1
+ $target.objectEnum($value)
+ String ok: 1
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Y. BigInteger bigint Value java.math.BigInteger 12345678901234567890
+ boolean ok: true
+ integralByte -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of byte range
+ integralShort -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of short range
+ integralInt -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of int range
+ integralLong -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of long range
+ float ok: 1.2345679E19
+ double ok: 1.2345678901234567E19
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of byte range
+ objectShort -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of short range
+ objectInt -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of int range
+ objectLong -> IllegalArgumentException: java.lang.ArithmeticException: BigInteger out of long range
+ BigInteger ok: 12345678901234567890
+ Float ok: 1.2345679E19
+ Double ok: 1.2345678901234567E19
+ BigDecimal ok: 12345678901234567890
+ $target.objectCharacter($value)
+ Number ok: 12345678901234567890
+ Object ok: 12345678901234567890
+ $target.objectEnum($value)
+ String ok: 12345678901234567890
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Y. BigInteger ten Value java.math.BigInteger 10
+ boolean ok: true
+ byte ok: 10
+ short ok: 10
+ int ok: 10
+ long ok: 10
+ float ok: 10.0
+ double ok: 10.0
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 10
+ Short ok: 10
+ Integer ok: 10
+ Long ok: 10
+ BigInteger ok: 10
+ Float ok: 10.0
+ Double ok: 10.0
+ BigDecimal ok: 10
+ $target.objectCharacter($value)
+ Number ok: 10
+ Object ok: 10
+ $target.objectEnum($value)
+ String ok: 10
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Z. BigDecimal zero Value java.math.BigDecimal 0
+ boolean ok: false
+ byte ok: 0
+ short ok: 0
+ int ok: 0
+ long ok: 0
+ float ok: 0.0
+ double ok: 0.0
+ $target.integralChar($value)
+ Boolean ok: false
+ Byte ok: 0
+ Short ok: 0
+ Integer ok: 0
+ Long ok: 0
+ BigInteger ok: 0
+ Float ok: 0.0
+ Double ok: 0.0
+ BigDecimal ok: 0
+ $target.objectCharacter($value)
+ Number ok: 0
+ Object ok: 0
+ $target.objectEnum($value)
+ String ok: 0
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+ZA. BigDecimal one Value java.math.BigDecimal 1
+ boolean ok: true
+ byte ok: 1
+ short ok: 1
+ int ok: 1
+ long ok: 1
+ float ok: 1.0
+ double ok: 1.0
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 1
+ Short ok: 1
+ Integer ok: 1
+ Long ok: 1
+ BigInteger ok: 1
+ Float ok: 1.0
+ Double ok: 1.0
+ BigDecimal ok: 1
+ $target.objectCharacter($value)
+ Number ok: 1
+ Object ok: 1
+ $target.objectEnum($value)
+ String ok: 1
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+ZB. BigDecimal ten Value java.math.BigDecimal 10
+ boolean ok: true
+ byte ok: 10
+ short ok: 10
+ int ok: 10
+ long ok: 10
+ float ok: 10.0
+ double ok: 10.0
+ $target.integralChar($value)
+ Boolean ok: true
+ Byte ok: 10
+ Short ok: 10
+ Integer ok: 10
+ Long ok: 10
+ BigInteger ok: 10
+ Float ok: 10.0
+ Double ok: 10.0
+ BigDecimal ok: 10
+ $target.objectCharacter($value)
+ Number ok: 10
+ Object ok: 10
+ $target.objectEnum($value)
+ String ok: 10
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+ZC. BigDecimal bigdec Value java.math.BigDecimal 12345678901234567890.01234567890123456789
+ boolean ok: true
+ integralByte -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ integralShort -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ integralInt -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ integralLong -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ float ok: 1.2345679E19
+ double ok: 1.2345678901234567E19
+ $target.integralChar($value)
+ Boolean ok: true
+ objectByte -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ objectShort -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ objectInt -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ objectLong -> IllegalArgumentException: java.lang.ArithmeticException: Overflow
+ objectBigInteger -> IllegalArgumentException: java.lang.ArithmeticException: Rounding necessary
+ Float ok: 1.2345679E19
+ Double ok: 1.2345678901234567E19
+ BigDecimal ok: 12345678901234567890.01234567890123456789
+ $target.objectCharacter($value)
+ Number ok: 12345678901234567890.01234567890123456789
+ Object ok: 12345678901234567890.01234567890123456789
+ $target.objectEnum($value)
+ String ok: 12345678901234567890.01234567890123456789
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
diff --git a/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp
new file mode 100644
index 00000000..81b400d6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/compare/test_conv_without_handler.cmp
@@ -0,0 +1,992 @@
+A. bool-true Value java.lang.Boolean true
+ boolean ok: true
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ Boolean ok: true
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: true
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+B. bool-false Value java.lang.Boolean false
+ boolean ok: false
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ Boolean ok: false
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: false
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+C. byte-0 Value java.lang.Byte 0
+ $target.integralBoolean($value)
+ byte ok: 0
+ short ok: 0
+ int ok: 0
+ long ok: 0
+ float ok: 0.0
+ double ok: 0.0
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ Byte ok: 0
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 0
+ Object ok: 0
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+D. byte-1 Value java.lang.Byte 1
+ $target.integralBoolean($value)
+ byte ok: 1
+ short ok: 1
+ int ok: 1
+ long ok: 1
+ float ok: 1.0
+ double ok: 1.0
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ Byte ok: 1
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 1
+ Object ok: 1
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+E. short Value java.lang.Short 125
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ short ok: 125
+ int ok: 125
+ long ok: 125
+ float ok: 125.0
+ double ok: 125.0
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ Short ok: 125
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 125
+ Object ok: 125
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+F. int Value java.lang.Integer 24323
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ int ok: 24323
+ long ok: 24323
+ float ok: 24323.0
+ double ok: 24323.0
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ Integer ok: 24323
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 24323
+ Object ok: 24323
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+G. long Value java.lang.Long 5235235
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ long ok: 5235235
+ float ok: 5235235.0
+ double ok: 5235235.0
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ Long ok: 5235235
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 5235235
+ Object ok: 5235235
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+H. float Value java.lang.Float 34523.344
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ float ok: 34523.344
+ double ok: 34523.34375
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ Float ok: 34523.344
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 34523.344
+ Object ok: 34523.344
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+I. double Value java.lang.Double 54235.3253
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ double ok: 54235.3253
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ Double ok: 54235.3253
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 54235.3253
+ Object ok: 54235.3253
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+J. char Value java.lang.Character @
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ char ok: @
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ Character ok: @
+ $target.objectNumber($value)
+ Object ok: @
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+K. object Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj instance of Obj
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: instance of Obj
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+L. enum Value org.apache.velocity.test.util.introspection.ConversionHandlerTestCase$Obj$Color GREEN
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: GREEN
+ Enum ok: GREEN
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+M. string Value java.lang.String foo
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: foo
+ $target.objectEnum($value)
+ String ok: foo
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+M. string-green Value java.lang.String green
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: green
+ $target.objectEnum($value)
+ String ok: green
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+N. string-empty Value java.lang.String
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok:
+ $target.objectEnum($value)
+ String ok:
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+O. string-false Value java.lang.String false
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: false
+ $target.objectEnum($value)
+ String ok: false
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+P. string-true Value java.lang.String true
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: true
+ $target.objectEnum($value)
+ String ok: true
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Q. string-zero Value java.lang.String 0
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 0
+ $target.objectEnum($value)
+ String ok: 0
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+R. string-integral Value java.lang.String 123
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 123
+ $target.objectEnum($value)
+ String ok: 123
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+S. string-big-integral Value java.lang.String 12345678
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 12345678
+ $target.objectEnum($value)
+ String ok: 12345678
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+T. string-floating Value java.lang.String 123.345
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: 123.345
+ $target.objectEnum($value)
+ String ok: 123.345
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+U. null Value $value.class.name $value
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ Boolean ok: null
+ Byte ok: null
+ Short ok: null
+ Integer ok: null
+ Long ok: null
+ BigInteger ok: null
+ Float ok: null
+ Double ok: null
+ BigDecimal ok: null
+ Character ok: null
+ Number ok: null
+ Object ok: null
+ Enum ok: null
+ String ok: null
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ Locale ok: null
+V. locale Value java.lang.String fr_FR
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ Object ok: fr_FR
+ $target.objectEnum($value)
+ String ok: fr_FR
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+W. BigInteger zero Value java.math.BigInteger 0
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ BigInteger ok: 0
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 0
+ Object ok: 0
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+X. BigInteger one Value java.math.BigInteger 1
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ BigInteger ok: 1
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 1
+ Object ok: 1
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Y. BigInteger bigint Value java.math.BigInteger 12345678901234567890
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ BigInteger ok: 12345678901234567890
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 12345678901234567890
+ Object ok: 12345678901234567890
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Y. BigInteger ten Value java.math.BigInteger 10
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ BigInteger ok: 10
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ Number ok: 10
+ Object ok: 10
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+Z. BigDecimal zero Value java.math.BigDecimal 0
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ BigDecimal ok: 0
+ $target.objectCharacter($value)
+ Number ok: 0
+ Object ok: 0
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+ZA. BigDecimal one Value java.math.BigDecimal 1
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ BigDecimal ok: 1
+ $target.objectCharacter($value)
+ Number ok: 1
+ Object ok: 1
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+ZB. BigDecimal ten Value java.math.BigDecimal 10
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ BigDecimal ok: 10
+ $target.objectCharacter($value)
+ Number ok: 10
+ Object ok: 10
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+ZC. BigDecimal bigdec Value java.math.BigDecimal 12345678901234567890.01234567890123456789
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ BigDecimal ok: 12345678901234567890.01234567890123456789
+ $target.objectCharacter($value)
+ Number ok: 12345678901234567890.01234567890123456789
+ Object ok: 12345678901234567890.01234567890123456789
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
diff --git a/velocity-engine-core/src/test/resources/conversion/matrix.vhtml b/velocity-engine-core/src/test/resources/conversion/matrix.vhtml
new file mode 100644
index 00000000..ee7ae5b7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/matrix.vhtml
@@ -0,0 +1,85 @@
+<!doctype html>
+
+#macro(print,$type)
+ #set($string = "$type")
+ #if($string.startsWith("class java.lang.") || $string.startsWith("class java.math."))
+ #set($string = $string.substring(16))
+ #end
+$string##
+#end
+
+#macro(cell,$type)<span class="$type">$type</span>#end
+
+#macro(check,$row,$col)
+ #if($introspect.isStrictlyConvertible($row, $col)) #cell('strict')
+ #elseif($introspect.isImplicitlyConvertible($row, $col)) #cell('implicit')
+ #elseif($introspect.isExplicitlyConvertible($row, $col)) #cell('explicit')
+ #else #cell('none')
+ #end
+#end
+
+<html>
+ <head>
+ <style type="text/css">
+ table
+ {
+ border: solid 1px black;
+ border-collapse: collapse;
+ }
+ td, th
+ {
+ border: solid 1px black;
+ }
+ .strict
+ {
+ color: green;
+ }
+ .implicit
+ {
+ color: blue;
+ }
+ .explicit
+ {
+ color: magenta;
+ }
+ .none
+ {
+ color: red;
+ }
+
+ </style>
+ </head>
+ <body>
+ <table>
+ <thead>
+ <tr>
+ <th>
+ provided &rarr;<br/>
+ expected &darr;
+ </th>
+#foreach($col in $types)
+ <th>#print($col)</th>
+#end
+ <th>null</th>
+ </tr>
+#foreach($row in $types)
+ <tr>
+ <th>#print($row)</th>
+ #foreach($col in $types)
+ <td>#check($row,$col)</td>
+ #end
+ #if($row.isPrimitive())
+ <td>#cell('none')</td>
+ #else
+ <td>#check($row, $null)</td>
+ #end
+ </tr>
+#end
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </body>
+</html>
+
+
diff --git a/velocity-engine-core/src/test/resources/conversion/test_conv.vtl b/velocity-engine-core/src/test/resources/conversion/test_conv.vtl
new file mode 100644
index 00000000..6045dc44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/conversion/test_conv.vtl
@@ -0,0 +1,35 @@
+#foreach($key in $map.keySet())##
+#set($value = $map[$key])##
+$key Value $value.class.name $value
+ $target.integralBoolean($value)
+ $target.integralByte($value)
+ $target.integralShort($value)
+ $target.integralInt($value)
+ $target.integralLong($value)
+ $target.integralFloat($value)
+ $target.integralDouble($value)
+ $target.integralChar($value)
+ $target.objectBoolean($value)
+ $target.objectByte($value)
+ $target.objectShort($value)
+ $target.objectInt($value)
+ $target.objectLong($value)
+ $target.objectBigInteger($value)
+ $target.objectFloat($value)
+ $target.objectDouble($value)
+ $target.objectBigDecimal($value)
+ $target.objectCharacter($value)
+ $target.objectNumber($value)
+ $target.objectObject($value)
+ $target.objectEnum($value)
+ $target.objectString($value)
+ $target.valueOfBoolean($value)
+ $target.valueOfShort($value)
+ $target.valueOfByte($value)
+ $target.valueOfInt($value)
+ $target.valueOfLong($value)
+ $target.valueOfFloat($value)
+ $target.valueOfDouble($value)
+ $target.valueOfString($value)
+ $target.locale($value)
+#end## \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/cpload/compare/test1.cmp b/velocity-engine-core/src/test/resources/cpload/compare/test1.cmp
new file mode 100644
index 00000000..4010ee26
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/cpload/compare/test1.cmp
@@ -0,0 +1,3 @@
+Test File 1
+include file a
+parse file a \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/cpload/compare/test2.cmp b/velocity-engine-core/src/test/resources/cpload/compare/test2.cmp
new file mode 100644
index 00000000..8f04079f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/cpload/compare/test2.cmp
@@ -0,0 +1,3 @@
+
+
+this is a template for test2.jar
diff --git a/velocity-engine-core/src/test/resources/cpload/test1.jar b/velocity-engine-core/src/test/resources/cpload/test1.jar
new file mode 100644
index 00000000..72a392f9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/cpload/test1.jar
Binary files differ
diff --git a/velocity-engine-core/src/test/resources/cpload/test2.jar b/velocity-engine-core/src/test/resources/cpload/test2.jar
new file mode 100644
index 00000000..9c102d11
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/cpload/test2.jar
Binary files differ
diff --git a/velocity-engine-core/src/test/resources/ds/create-db.sql b/velocity-engine-core/src/test/resources/ds/create-db.sql
new file mode 100644
index 00000000..bdc46d79
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/create-db.sql
@@ -0,0 +1,54 @@
+-- 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.
+
+drop table if exists velocity_template_varchar;
+
+create table velocity_template_varchar
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def VARCHAR(255) not null
+);
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate1', current_timestamp, 'I am a test through the data loader');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate2', current_timestamp, '$tool.message $tool.add(23, 19)');
+
+insert into velocity_template_varchar (vt_id, vt_def) VALUES
+ ( 'testTemplate3', 'This is a template with a null timestamp');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate4', current_timestamp, '#testMacro("foo")');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'VM_global_library.vm', current_timestamp, '#macro (testMacro $param) I am a macro using $param #end');
+
+
+-- same templates as clob
+
+drop table if exists velocity_template_clob;
+
+create table velocity_template_clob
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def CLOB not null
+);
+
+insert into velocity_template_clob select * from velocity_template_varchar;
diff --git a/velocity-engine-core/src/test/resources/ds/create-db.sql.derby b/velocity-engine-core/src/test/resources/ds/create-db.sql.derby
new file mode 100644
index 00000000..2f598bb7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/create-db.sql.derby
@@ -0,0 +1,54 @@
+-- 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.
+
+-- drop table if exists velocity_template_varchar;
+
+create table velocity_template_varchar
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def VARCHAR(255) not null
+);
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate1', current_timestamp, 'I am a test through the data loader');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate2', current_timestamp, '$tool.message $tool.add(23, 19)');
+
+insert into velocity_template_varchar (vt_id, vt_def) VALUES
+ ( 'testTemplate3', 'This is a template with a null timestamp');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate4', current_timestamp, '#testMacro("foo")');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'VM_global_library.vm', current_timestamp, '#macro (testMacro $param) I am a macro using $param #end');
+
+
+-- same templates as clob
+
+-- drop table if exists velocity_template_clob;
+
+create table velocity_template_clob
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def CLOB not null
+);
+
+insert into velocity_template_clob select * from velocity_template_varchar;
diff --git a/velocity-engine-core/src/test/resources/ds/create-db.sql.mysql b/velocity-engine-core/src/test/resources/ds/create-db.sql.mysql
new file mode 100644
index 00000000..099d5034
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/create-db.sql.mysql
@@ -0,0 +1,54 @@
+-- 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.
+
+drop table if exists velocity_template_varchar;
+
+create table velocity_template_varchar
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp DATETIME,
+ vt_def VARCHAR(255) not null
+);
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate1', current_timestamp, 'I am a test through the data loader');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate2', current_timestamp, '$tool.message $tool.add(23, 19)');
+
+insert into velocity_template_varchar (vt_id, vt_def) VALUES
+ ( 'testTemplate3', 'This is a template with a null timestamp');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate4', current_timestamp, '#testMacro("foo")');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'VM_global_library.vm', current_timestamp, '#macro (testMacro $param) I am a macro using $param #end');
+
+
+-- same templates as clob
+
+drop table if exists velocity_template_clob;
+
+create table velocity_template_clob
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp DATETIME,
+ vt_def TEXT not null
+);
+
+insert into velocity_template_clob select * from velocity_template_varchar;
diff --git a/velocity-engine-core/src/test/resources/ds/create-db.sql.oracle b/velocity-engine-core/src/test/resources/ds/create-db.sql.oracle
new file mode 100644
index 00000000..0c7c0807
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/create-db.sql.oracle
@@ -0,0 +1,51 @@
+BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE velocity_template_varchar';
+EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+END;
+
+create table velocity_template_varchar
+(
+ vt_id VARCHAR(64) not null,
+ vt_timestamp TIMESTAMP,
+ vt_def VARCHAR(255) not null
+);
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate1', current_timestamp, 'I am a test through the data loader');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate2', current_timestamp, '$tool.message $tool.add(23, 19)');
+
+insert into velocity_template_varchar (vt_id, vt_def) VALUES
+ ( 'testTemplate3', 'This is a template with a null timestamp');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate4', current_timestamp, '#testMacro("foo")');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'VM_global_library.vm', current_timestamp, '#macro (testMacro $param) I am a macro using $param #end');
+
+
+-- same templates as clob
+
+BEGIN
+ EXECUTE IMMEDIATE 'DROP TABLE velocity_template_clob';
+EXCEPTION
+ WHEN OTHERS THEN
+ IF SQLCODE != -942 THEN
+ RAISE;
+ END IF;
+END;
+
+create table velocity_template_clob
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def CLOB not null
+);
+
+insert into velocity_template_clob select * from velocity_template_varchar;
diff --git a/velocity-engine-core/src/test/resources/ds/create-db.sql.postgresql b/velocity-engine-core/src/test/resources/ds/create-db.sql.postgresql
new file mode 100644
index 00000000..c778d2b3
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/create-db.sql.postgresql
@@ -0,0 +1,54 @@
+-- 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.
+
+drop table if exists velocity_template_varchar;
+
+create table velocity_template_varchar
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def VARCHAR(255) not null
+);
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate1', current_timestamp, 'I am a test through the data loader');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate2', current_timestamp, '$tool.message $tool.add(23, 19)');
+
+insert into velocity_template_varchar (vt_id, vt_def) VALUES
+ ( 'testTemplate3', 'This is a template with a null timestamp');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate4', current_timestamp, '#testMacro("foo")');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'VM_global_library.vm', current_timestamp, '#macro (testMacro $param) I am a macro using $param #end');
+
+
+-- same templates as clob
+
+drop table if exists velocity_template_clob;
+
+create table velocity_template_clob
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp TIMESTAMP,
+ vt_def TEXT not null
+);
+
+insert into velocity_template_clob select * from velocity_template_varchar;
diff --git a/velocity-engine-core/src/test/resources/ds/create-db.sql.sqlserver b/velocity-engine-core/src/test/resources/ds/create-db.sql.sqlserver
new file mode 100644
index 00000000..099d5034
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/create-db.sql.sqlserver
@@ -0,0 +1,54 @@
+-- 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.
+
+drop table if exists velocity_template_varchar;
+
+create table velocity_template_varchar
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp DATETIME,
+ vt_def VARCHAR(255) not null
+);
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate1', current_timestamp, 'I am a test through the data loader');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate2', current_timestamp, '$tool.message $tool.add(23, 19)');
+
+insert into velocity_template_varchar (vt_id, vt_def) VALUES
+ ( 'testTemplate3', 'This is a template with a null timestamp');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'testTemplate4', current_timestamp, '#testMacro("foo")');
+
+insert into velocity_template_varchar (vt_id, vt_timestamp, vt_def) VALUES
+ ( 'VM_global_library.vm', current_timestamp, '#macro (testMacro $param) I am a macro using $param #end');
+
+
+-- same templates as clob
+
+drop table if exists velocity_template_clob;
+
+create table velocity_template_clob
+(
+ vt_id VARCHAR(64) not null primary key,
+ vt_timestamp DATETIME,
+ vt_def TEXT not null
+);
+
+insert into velocity_template_clob select * from velocity_template_varchar;
diff --git a/velocity-engine-core/src/test/resources/ds/templates/testTemplate1.cmp b/velocity-engine-core/src/test/resources/ds/templates/testTemplate1.cmp
new file mode 100644
index 00000000..bd9339e7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/templates/testTemplate1.cmp
@@ -0,0 +1 @@
+I am a test through the data loader \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/ds/templates/testTemplate2.cmp b/velocity-engine-core/src/test/resources/ds/templates/testTemplate2.cmp
new file mode 100644
index 00000000..f261000d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/templates/testTemplate2.cmp
@@ -0,0 +1 @@
+And the result is: 42 \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/ds/templates/testTemplate3.cmp b/velocity-engine-core/src/test/resources/ds/templates/testTemplate3.cmp
new file mode 100644
index 00000000..8ed49311
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/templates/testTemplate3.cmp
@@ -0,0 +1 @@
+This is a template with a null timestamp \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/ds/templates/testTemplate4.cmp b/velocity-engine-core/src/test/resources/ds/templates/testTemplate4.cmp
new file mode 100644
index 00000000..8da18f1a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/ds/templates/testTemplate4.cmp
@@ -0,0 +1 @@
+ I am a macro using foo \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/evaluate/compare/eval1.cmp b/velocity-engine-core/src/test/resources/evaluate/compare/eval1.cmp
new file mode 100644
index 00000000..b1875f0b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/evaluate/compare/eval1.cmp
@@ -0,0 +1,8 @@
+
+basic string
+
+embedded reference zz
+zz
+
+reference zz changes to xx
+test1: xx
diff --git a/velocity-engine-core/src/test/resources/evaluate/compare/eval2.cmp b/velocity-engine-core/src/test/resources/evaluate/compare/eval2.cmp
new file mode 100644
index 00000000..9e87f0ed
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/evaluate/compare/eval2.cmp
@@ -0,0 +1,9 @@
+test 1: a: 12
+basic string
+test 2: a: 22
+
+test 3: inner eval: 33
+
+basic string
+test 4: inner eval: 44
+
diff --git a/velocity-engine-core/src/test/resources/evaluate/compare/evalvmcontext.cmp b/velocity-engine-core/src/test/resources/evaluate/compare/evalvmcontext.cmp
new file mode 100644
index 00000000..711b225b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/evaluate/compare/evalvmcontext.cmp
@@ -0,0 +1,3 @@
+
+
+value is : val1;value is : val2; \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/evaluate/eval1.vm b/velocity-engine-core/src/test/resources/evaluate/eval1.vm
new file mode 100644
index 00000000..889b0e02
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/evaluate/eval1.vm
@@ -0,0 +1,20 @@
+## Testing the evaluate directive
+
+#evaluate("basic string")
+
+#set($test1 = "zz")
+#set($test2 = '$test1')
+
+#evaluate("embedded reference $test2")
+
+#evaluate($test2)
+
+## Now check that #evaluate does change context
+## Need to use single quote to surround #set to prevent premature evaluation
+
+#set($teststring = "reference $test2 changes to" +
+ '#set($test1 = "xx") $test1')
+#evaluate($teststring)
+
+## Check that test1 has changed
+test1: $test1
diff --git a/velocity-engine-core/src/test/resources/evaluate/eval2.vm b/velocity-engine-core/src/test/resources/evaluate/eval2.vm
new file mode 100644
index 00000000..59a363cb
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/evaluate/eval2.vm
@@ -0,0 +1,23 @@
+##
+## Test evaluate preserves macros
+##
+#macro (test $a)
+a: $a##
+#end
+test 1: #test(12)
+#evaluate("basic string")
+
+test 2: #test(22)
+
+##
+## Test again while doing evaluate within macro
+##
+#macro (test2 $a)
+#evaluate("inner eval: $a")
+#end
+test 3: #test2(33)
+
+#evaluate("basic string")
+
+test 4: #test2(44)
+
diff --git a/velocity-engine-core/src/test/resources/evaluate/evalvmcontext.vm b/velocity-engine-core/src/test/resources/evaluate/evalvmcontext.vm
new file mode 100644
index 00000000..c27e229e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/evaluate/evalvmcontext.vm
@@ -0,0 +1,7 @@
+## test of evaluate in a macro context whith local refs (foreach refs)
+
+#macro(testEval $expr)
+#foreach($value in ["val1", "val2"])value is : #evaluate( $expr );#end
+#end
+
+#testEval( "${value}" )
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.BC
new file mode 100644
index 00000000..77499bfa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.BC
@@ -0,0 +1,14 @@
+ empty
+
+~~~~ empty ~~~~
+
+<table>
+ <tbody>
+ <tr>
+ <td>
+ nobody
+ </td>
+ </tr>
+ </tbody>
+</table>
+
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.LINES
new file mode 100644
index 00000000..3a233d91
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.LINES
@@ -0,0 +1,14 @@
+ empty
+
+~~~~ empty ~~~~
+
+<table>
+ <tbody>
+ <tr>
+ <td>
+ nobody
+ </td>
+ </tr>
+ </tbody>
+</table>
+
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.NONE
new file mode 100644
index 00000000..fde55f4d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.NONE
@@ -0,0 +1,20 @@
+
+ empty
+
+
+~~~~ empty ~~~~
+
+<table>
+ <tbody>
+
+ <tr>
+
+ <td>
+ nobody
+ </td>
+
+ </tr>
+
+ </tbody>
+</table>
+
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.STRUCTURED
new file mode 100644
index 00000000..84dc126e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_empty.vtl.STRUCTURED
@@ -0,0 +1,14 @@
+ empty
+
+~~~~ empty ~~~~
+
+<table>
+ <tbody>
+<tr>
+ <td>
+nobody
+ </td>
+</tr>
+ </tbody>
+</table>
+
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.BC
new file mode 100644
index 00000000..6a46ff9d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.BC
@@ -0,0 +1,20 @@
+<table>
+ <tbody>
+ <tr>
+ <td>
+ first cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ <tr>
+ <td>
+ middle cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.LINES
new file mode 100644
index 00000000..4a1186bb
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.LINES
@@ -0,0 +1,20 @@
+<table>
+ <tbody>
+ <tr>
+ <td>
+ first cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ <tr>
+ <td>
+ middle cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.NONE
new file mode 100644
index 00000000..47800f57
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.NONE
@@ -0,0 +1,37 @@
+<table>
+ <tbody>
+
+ <tr>
+
+ <td>
+
+ first cell
+
+ </td>
+
+ <td>
+
+ middle cell
+
+ </td>
+
+ </tr>
+
+ <tr>
+
+ <td>
+
+ middle cell
+
+ </td>
+
+ <td>
+
+ middle cell
+
+ </td>
+
+ </tr>
+
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.STRUCTURED
new file mode 100644
index 00000000..2a223af4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_smart.vtl.STRUCTURED
@@ -0,0 +1,20 @@
+<table>
+ <tbody>
+<tr>
+ <td>
+first cell
+ </td>
+ <td>
+middle cell
+ </td>
+</tr>
+<tr>
+ <td>
+middle cell
+ </td>
+ <td>
+middle cell
+ </td>
+</tr>
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.BC
new file mode 100644
index 00000000..9d5c7daa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.BC
@@ -0,0 +1,20 @@
+<table>
+ <tbody>
+ <tr>
+ <td>
+ first cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ <tr>
+ <td>
+ middle cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.LINES
new file mode 100644
index 00000000..d8acef9e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.LINES
@@ -0,0 +1,20 @@
+<table>
+ <tbody>
+ <tr>
+ <td>
+ first cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ <tr>
+ <td>
+ middle cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.NONE
new file mode 100644
index 00000000..c3d6749d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.NONE
@@ -0,0 +1,37 @@
+<table>
+ <tbody>
+
+ <tr>
+
+ <td>
+
+ first cell
+
+ </td>
+
+ <td>
+
+ middle cell
+
+ </td>
+
+ </tr>
+
+ <tr>
+
+ <td>
+
+ middle cell
+
+ </td>
+
+ <td>
+
+ middle cell
+
+ </td>
+
+ </tr>
+
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.STRUCTURED
new file mode 100644
index 00000000..4a1186bb
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/foreach_structured.vtl.STRUCTURED
@@ -0,0 +1,20 @@
+<table>
+ <tbody>
+ <tr>
+ <td>
+ first cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ <tr>
+ <td>
+ middle cell
+ </td>
+ <td>
+ middle cell
+ </td>
+ </tr>
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.BC
new file mode 100644
index 00000000..1dffcd61
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.BC
@@ -0,0 +1,60 @@
+y yyy
+ y
+ y
+ y
+ y
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+
+ y
+ y
+ y
+ y
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+
+line1 foo line2
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.LINES
new file mode 100644
index 00000000..ebd065cb
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.LINES
@@ -0,0 +1,61 @@
+y yyy
+ y
+ y
+ y
+ y
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+
+ y
+ y
+ y
+ y
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+
+line1 foo
+line2
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.NONE
new file mode 100644
index 00000000..cfc96874
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.NONE
@@ -0,0 +1,243 @@
+
+
+y
+ y
+y
+y
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ y
+
+
+ y
+
+
+ y
+
+
+ y
+
+
+
+
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+
+
+
+
+
+
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+
+
+
+ y
+
+
+ y
+
+
+ y
+
+
+ y
+
+
+
+
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+ z
+
+
+
+
+
+
+
+
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+ t
+
+
+line1 foo
+line2
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.STRUCTURED
new file mode 100644
index 00000000..66e3c349
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/if.vtl.STRUCTURED
@@ -0,0 +1,61 @@
+y yyy
+y
+y
+y
+y
+z
+z
+z
+z
+z
+z
+z
+z
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+t
+
+ y
+ y
+ y
+ y
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ z
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+ t
+
+line1 foo
+line2
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.BC
new file mode 100644
index 00000000..228a3c78
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.BC
@@ -0,0 +1,20 @@
+
+
+
+
+
+<a>
+ <b>
+ line1 line2 line3
+ line4
+ block foo
+ </b>
+</a>
+
+<a>
+ <b>
+ line1 line2 line3
+ line4
+ block foo
+ </b>
+</a>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.LINES
new file mode 100644
index 00000000..611be323
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.LINES
@@ -0,0 +1,20 @@
+
+
+
+
+
+<a>
+ <b>
+line1line2line3
+line4
+block foo
+ </b>
+</a>
+
+<a>
+ <b>
+line1line2line3
+line4
+block foo
+ </b>
+</a>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.NONE
new file mode 100644
index 00000000..53d42e98
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.NONE
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+<a>
+ <b>
+
+ line1
+
+line2
+ line3
+
+
+line4
+
+
+block foo
+
+
+ </b>
+</a>
+
+<a>
+ <b>
+
+ line1
+
+line2
+ line3
+
+
+line4
+
+
+block foo
+
+
+ </b>
+</a>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.STRUCTURED
new file mode 100644
index 00000000..611be323
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro.vtl.STRUCTURED
@@ -0,0 +1,20 @@
+
+
+
+
+
+<a>
+ <b>
+line1line2line3
+line4
+block foo
+ </b>
+</a>
+
+<a>
+ <b>
+line1line2line3
+line4
+block foo
+ </b>
+</a>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.BC
new file mode 100644
index 00000000..b9dd4b84
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.BC
@@ -0,0 +1,6 @@
+-----
+value
+-----
+
+-----
+value-----
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.LINES
new file mode 100644
index 00000000..856047a4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.LINES
@@ -0,0 +1,5 @@
+-----
+value-----
+
+-----
+value-----
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.NONE
new file mode 100644
index 00000000..e66cde7a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.NONE
@@ -0,0 +1,8 @@
+
+-----
+value
+-----
+
+-----
+value
+-----
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.STRUCTURED
new file mode 100644
index 00000000..856047a4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/macro2.vtl.STRUCTURED
@@ -0,0 +1,5 @@
+-----
+value-----
+
+-----
+value-----
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.BC
new file mode 100644
index 00000000..b145fc57
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.BC
@@ -0,0 +1,3 @@
+ postfix
+ postfix
+prefix prefix postfix
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.LINES
new file mode 100644
index 00000000..af63dd07
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.LINES
@@ -0,0 +1,4 @@
+ postfix
+ postfix
+prefix
+prefix postfix
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.NONE
new file mode 100644
index 00000000..790084ee
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.NONE
@@ -0,0 +1,6 @@
+
+
+ postfix
+ postfix
+prefix
+prefix postfix
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.STRUCTURED
new file mode 100644
index 00000000..af63dd07
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/set.vtl.STRUCTURED
@@ -0,0 +1,4 @@
+ postfix
+ postfix
+prefix
+prefix postfix
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.BC
new file mode 100644
index 00000000..0de05107
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.BC
@@ -0,0 +1,7 @@
+<table>
+ <tr>
+ <td>
+ blabla
+ </td>
+ </tr>
+ </table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.LINES
new file mode 100644
index 00000000..2fec8562
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.LINES
@@ -0,0 +1,7 @@
+<table>
+ <tr>
+ <td>
+ blabla
+ </td>
+ </tr>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.NONE
new file mode 100644
index 00000000..d305dcee
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.NONE
@@ -0,0 +1,11 @@
+<table>
+
+ <tr>
+
+ <td>
+ blabla
+ </td>
+
+ </tr>
+
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.STRUCTURED
new file mode 100644
index 00000000..1e48ceb9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/structured.vtl.STRUCTURED
@@ -0,0 +1,7 @@
+<table>
+ <tr>
+ <td>
+ blabla
+ </td>
+ </tr>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.BC
new file mode 100644
index 00000000..81618918
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.BC
@@ -0,0 +1,18 @@
+---
+ $
+ #
+ $$foo$
+ $$foo#
+ $#foo
+ $#foo$
+ $#foo#
+ $#foo
+ #$foo$
+ #$foo#
+ #$foo
+ $#foo()$
+ $#foo()#
+ $#@foo()hop#end$
+ $#@foo()hop#end#
+ $#@foo()hop#end
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.LINES
new file mode 100644
index 00000000..81618918
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.LINES
@@ -0,0 +1,18 @@
+---
+ $
+ #
+ $$foo$
+ $$foo#
+ $#foo
+ $#foo$
+ $#foo#
+ $#foo
+ #$foo$
+ #$foo#
+ #$foo
+ $#foo()$
+ $#foo()#
+ $#@foo()hop#end$
+ $#@foo()hop#end#
+ $#@foo()hop#end
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.NONE
new file mode 100644
index 00000000..81618918
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.NONE
@@ -0,0 +1,18 @@
+---
+ $
+ #
+ $$foo$
+ $$foo#
+ $#foo
+ $#foo$
+ $#foo#
+ $#foo
+ #$foo$
+ #$foo#
+ #$foo
+ $#foo()$
+ $#foo()#
+ $#@foo()hop#end$
+ $#@foo()hop#end#
+ $#@foo()hop#end
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.STRUCTURED
new file mode 100644
index 00000000..81618918
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly1.vtl.STRUCTURED
@@ -0,0 +1,18 @@
+---
+ $
+ #
+ $$foo$
+ $$foo#
+ $#foo
+ $#foo$
+ $#foo#
+ $#foo
+ #$foo$
+ #$foo#
+ #$foo
+ $#foo()$
+ $#foo()#
+ $#@foo()hop#end$
+ $#@foo()hop#end#
+ $#@foo()hop#end
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.BC b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.BC
new file mode 100644
index 00000000..43fd698f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.BC
@@ -0,0 +1,20 @@
+$$
+$$
+$true$$
+$true$$
+---
+ $
+ #
+ $bar$
+ $bar#
+ $bar
+ $bar$
+ $bar#
+ $bar
+ #bar$
+ #bar#
+ #bar
+ $bar$
+ $bar#
+ $bar
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.LINES b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.LINES
new file mode 100644
index 00000000..43fd698f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.LINES
@@ -0,0 +1,20 @@
+$$
+$$
+$true$$
+$true$$
+---
+ $
+ #
+ $bar$
+ $bar#
+ $bar
+ $bar$
+ $bar#
+ $bar
+ #bar$
+ #bar#
+ #bar
+ $bar$
+ $bar#
+ $bar
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.NONE b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.NONE
new file mode 100644
index 00000000..43fd698f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.NONE
@@ -0,0 +1,20 @@
+$$
+$$
+$true$$
+$true$$
+---
+ $
+ #
+ $bar$
+ $bar#
+ $bar
+ $bar$
+ $bar#
+ $bar
+ #bar$
+ #bar#
+ #bar
+ $bar$
+ $bar#
+ $bar
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.STRUCTURED b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.STRUCTURED
new file mode 100644
index 00000000..43fd698f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/compare/ugly2.vtl.STRUCTURED
@@ -0,0 +1,20 @@
+$$
+$$
+$true$$
+$true$$
+---
+ $
+ #
+ $bar$
+ $bar#
+ $bar
+ $bar$
+ $bar#
+ $bar
+ #bar$
+ #bar#
+ #bar
+ $bar$
+ $bar#
+ $bar
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/foreach_empty.vtl b/velocity-engine-core/src/test/resources/gobbling/foreach_empty.vtl
new file mode 100644
index 00000000..d2c81d17
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/foreach_empty.vtl
@@ -0,0 +1,26 @@
+ #foreach( $i in [])
+ $i
+ #else
+ empty
+ #end
+
+~~~~ #foreach($i in []) #else empty #end ~~~~
+
+<table>
+ <tbody>
+#foreach($row in [1..1])
+ <tr>
+ #foreach($col in [])
+ <td>
+ ghost
+ </td>
+ #else
+ <td>
+ nobody
+ </td>
+ #end
+ </tr>
+#end
+ </tbody>
+</table>
+
diff --git a/velocity-engine-core/src/test/resources/gobbling/foreach_smart.vtl b/velocity-engine-core/src/test/resources/gobbling/foreach_smart.vtl
new file mode 100644
index 00000000..26c0894f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/foreach_smart.vtl
@@ -0,0 +1,19 @@
+<table>
+ <tbody>
+#foreach($row in [1..2])
+ <tr>
+ #foreach($col in [1..2])
+ <td>
+ #if( $row == 1 && $col == 1)
+ first cell
+ #elseif( $row == 2 && $coll == 2)
+ last cell
+ #else
+ middle cell
+ #end
+ </td>
+ #end
+ </tr>
+#end
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/foreach_structured.vtl b/velocity-engine-core/src/test/resources/gobbling/foreach_structured.vtl
new file mode 100644
index 00000000..b6fe77fd
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/foreach_structured.vtl
@@ -0,0 +1,19 @@
+<table>
+ <tbody>
+ #foreach($row in [1..2])
+ <tr>
+ #foreach($col in [1..2])
+ <td>
+ #if( $row == 1 && $col == 1)
+ first cell
+ #elseif( $row == 2 && $coll == 2)
+ last cell
+ #else
+ middle cell
+ #end
+ </td>
+ #end
+ </tr>
+ #end
+ </tbody>
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/if.vtl b/velocity-engine-core/src/test/resources/gobbling/if.vtl
new file mode 100644
index 00000000..8d1f8e33
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/if.vtl
@@ -0,0 +1,651 @@
+#if($bogus == "bar")x#end
+#if($bogus == "bar")x #end
+#if($bogus == "bar")x#{else}y#end
+#if($bogus == "bar")x#else y#end
+#if($bogus == "bar")x #{else}y#end
+#if($bogus == "bar")x #{else}y #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#{else}#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#{else} #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #{else}#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #{else} #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#{else}#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#{else} #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #{else}#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #{else} #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#elseif($bogus == "schmoo")#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#elseif($bogus == "schmoo") #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #elseif($bogus == "schmoo")#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #elseif($bogus == "schmoo") #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#elseif($bogus == "schmoo")#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#elseif($bogus == "schmoo") #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #elseif($bogus == "schmoo")#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #elseif($bogus == "schmoo") #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#elseif($bogus == "schmoo")#{else}#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#elseif($bogus == "schmoo")#{else} #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#elseif($bogus == "schmoo") #{else}#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y#elseif($bogus == "schmoo") #{else} #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #elseif($bogus == "schmoo")#{else}#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #elseif($bogus == "schmoo")#{else} #end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #elseif($bogus == "schmoo") #{else}#end
+#if($bogus == "bar")x#elseif($bogus == "foo")y #elseif($bogus == "schmoo") #{else} #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#elseif($bogus == "schmoo")#{else}#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#elseif($bogus == "schmoo")#{else} #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#elseif($bogus == "schmoo") #{else}#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y#elseif($bogus == "schmoo") #{else} #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #elseif($bogus == "schmoo")#{else}#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #elseif($bogus == "schmoo")#{else} #end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #elseif($bogus == "schmoo") #{else}#end
+#if($bogus == "bar")x #elseif($bogus == "foo")y #elseif($bogus == "schmoo") #{else} #end
+
+#if($bogus == "bar")
+ x
+#end
+#if($bogus == "bar")
+ x
+#end
+#if($bogus == "bar")
+ x
+#else
+ y
+#end
+#if($bogus == "bar")
+ x
+#{else}
+ y
+#end
+#if($bogus == "bar")
+ x
+#else
+ y
+#end
+#if($bogus == "bar")
+ x
+#{else}
+ y
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#else
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#{else}
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#else
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#{else}
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#else
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#{else}
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#else
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#{else}
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#else
+ t
+#end
+#if($bogus == "bar")
+ x
+#elseif($bogus == "foo")
+ y
+#elseif($bogus == "schmoo")
+ z
+#{else}
+ t
+#end
+
+ #if($bogus == "bar")
+ x
+ #end
+ #if($bogus == "bar")
+ x
+ #end
+ #if($bogus == "bar")
+ x
+ #else
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #{else}
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #else
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #{else}
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #else
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #{else}
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #else
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #{else}
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #else
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #{else}
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #else
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #{else}
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #else
+ t
+ #end
+ #if($bogus == "bar")
+ x
+ #elseif($bogus == "foo")
+ y
+ #elseif($bogus == "schmoo")
+ z
+ #{else}
+ t
+ #end
+
+line1 #if(true) foo #else bar #end
+line2
diff --git a/velocity-engine-core/src/test/resources/gobbling/macro.vtl b/velocity-engine-core/src/test/resources/gobbling/macro.vtl
new file mode 100644
index 00000000..e3f0186c
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/macro.vtl
@@ -0,0 +1,41 @@
+#macro(line1)line1#end
+
+#macro(line2)
+line2#end
+
+#macro(line3)line3
+#end
+
+#macro(line4)
+line4
+#end
+
+#macro(block)
+block $bodyContent
+#end
+
+## lines
+<a>
+ <b>
+#if(true)
+ #line1()
+ #line2()
+ #line3()
+ #line4()
+ #@block()foo#end
+#end
+ </b>
+</a>
+
+## structured
+<a>
+ <b>
+ #if(true)
+ #line1()
+ #line2()
+ #line3()
+ #line4()
+ #@block()foo#end
+ #end
+ </b>
+</a>
diff --git a/velocity-engine-core/src/test/resources/gobbling/macro2.vtl b/velocity-engine-core/src/test/resources/gobbling/macro2.vtl
new file mode 100644
index 00000000..3d9f7da1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/macro2.vtl
@@ -0,0 +1,8 @@
+#macro(test)value#end
+-----
+#test
+-----
+
+-----
+#test()
+-----
diff --git a/velocity-engine-core/src/test/resources/gobbling/set.vtl b/velocity-engine-core/src/test/resources/gobbling/set.vtl
new file mode 100644
index 00000000..8ec6098a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/set.vtl
@@ -0,0 +1,6 @@
+#set($foo = 'foo')
+ #set($foo = 'foo')
+#set($foo = 'foo') postfix
+ #set($foo = 'foo') postfix
+prefix #set($foo = 'foo')
+prefix #set($foo = 'foo') postfix
diff --git a/velocity-engine-core/src/test/resources/gobbling/structured.vtl b/velocity-engine-core/src/test/resources/gobbling/structured.vtl
new file mode 100644
index 00000000..83342129
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/structured.vtl
@@ -0,0 +1,11 @@
+<table>
+ #foreach($a in [1..1])
+ <tr>
+ #foreach($b in [1..1])
+ <td>
+ blabla
+ </td>
+ #end
+ </tr>
+ #end
+</table>
diff --git a/velocity-engine-core/src/test/resources/gobbling/ugly1.vtl b/velocity-engine-core/src/test/resources/gobbling/ugly1.vtl
new file mode 100644
index 00000000..81618918
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/ugly1.vtl
@@ -0,0 +1,18 @@
+---
+ $
+ #
+ $$foo$
+ $$foo#
+ $#foo
+ $#foo$
+ $#foo#
+ $#foo
+ #$foo$
+ #$foo#
+ #$foo
+ $#foo()$
+ $#foo()#
+ $#@foo()hop#end$
+ $#@foo()hop#end#
+ $#@foo()hop#end
+---
diff --git a/velocity-engine-core/src/test/resources/gobbling/ugly2.vtl b/velocity-engine-core/src/test/resources/gobbling/ugly2.vtl
new file mode 100644
index 00000000..062da10c
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/gobbling/ugly2.vtl
@@ -0,0 +1,20 @@
+$#macro(foo)bar#end$
+$#set($foo = 'bar')$
+$#if(true)true$#{else}false$#end$
+$#if(false)false$#{else}true$#end$
+---
+ $
+ #
+ $$foo$
+ $$foo#
+ $#foo
+ $#foo$
+ $#foo#
+ $#foo
+ #$foo$
+ #$foo#
+ #$foo
+ $#@foo()hop#end$
+ $#@foo()hop#end#
+ $#@foo()hop#end
+---
diff --git a/velocity-engine-core/src/test/resources/includeerror/haserror.txt b/velocity-engine-core/src/test/resources/includeerror/haserror.txt
new file mode 100644
index 00000000..3cbb379f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeerror/haserror.txt
@@ -0,0 +1,7 @@
+## This file has a Velocity error.
+## It's intentionally not saved with a 'vm' suffix
+## to avoid errors in IDE
+
+#foreach($i in (1..10)
+
+$i \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeerror/haserror2.txt b/velocity-engine-core/src/test/resources/includeerror/haserror2.txt
new file mode 100644
index 00000000..b495c4fb
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeerror/haserror2.txt
@@ -0,0 +1,10 @@
+## This file has a Velocity error.
+## It's intentionally not saved with a 'vm' suffix
+## to avoid errors in IDE
+
+## Note: text directly from VELOCITY-96
+
+#macro (myMacro $arg1 $list)
+This is text from velPTest2.vm
+#myMacro('name', ['apples', 'oranges']
+More text
diff --git a/velocity-engine-core/src/test/resources/includeerror/missinginclude.vm b/velocity-engine-core/src/test/resources/includeerror/missinginclude.vm
new file mode 100644
index 00000000..fd9c8c85
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeerror/missinginclude.vm
@@ -0,0 +1,8 @@
+## tests to see if
+## missing include throws an error
+
+text
+
+#include("doesntexist.vm")
+
+text \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeerror/missingparse.vm b/velocity-engine-core/src/test/resources/includeerror/missingparse.vm
new file mode 100644
index 00000000..ba8229f2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeerror/missingparse.vm
@@ -0,0 +1,8 @@
+## tests to see if
+## missing parse throws an error
+
+text
+
+#parse("doesntexist.vm")
+
+text \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeerror/parsemain.vm b/velocity-engine-core/src/test/resources/includeerror/parsemain.vm
new file mode 100644
index 00000000..e3410331
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeerror/parsemain.vm
@@ -0,0 +1,8 @@
+## tests to see if
+## ParseException in parsed file is caught
+
+text
+
+#parse("haserror.txt")
+
+text \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeerror/parsemain2.vm b/velocity-engine-core/src/test/resources/includeerror/parsemain2.vm
new file mode 100644
index 00000000..ebcd20e7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeerror/parsemain2.vm
@@ -0,0 +1,8 @@
+## tests to see if
+## ParseException in parsed file is caught
+
+text
+
+#parse("haserror2.txt")
+
+text \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/compare/test1.cmp b/velocity-engine-core/src/test/resources/includeevent/compare/test1.cmp
new file mode 100644
index 00000000..4010ee26
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/compare/test1.cmp
@@ -0,0 +1,3 @@
+Test File 1
+include file a
+parse file a \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/compare/test2.cmp b/velocity-engine-core/src/test/resources/includeevent/compare/test2.cmp
new file mode 100644
index 00000000..41fadfd3
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/compare/test2.cmp
@@ -0,0 +1,5 @@
+Test File 2
+Good include file b
+Good parse file b
+Good include file c
+Good parse file c
diff --git a/velocity-engine-core/src/test/resources/includeevent/compare/test3.cmp b/velocity-engine-core/src/test/resources/includeevent/compare/test3.cmp
new file mode 100644
index 00000000..70dcdb2f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/compare/test3.cmp
@@ -0,0 +1,2 @@
+Test File 3
+
diff --git a/velocity-engine-core/src/test/resources/includeevent/compare/test4.cmp b/velocity-engine-core/src/test/resources/includeevent/compare/test4.cmp
new file mode 100644
index 00000000..983a5603
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/compare/test4.cmp
@@ -0,0 +1,2 @@
+Test File 4
+page not found (subdir) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/compare/test5.cmp b/velocity-engine-core/src/test/resources/includeevent/compare/test5.cmp
new file mode 100644
index 00000000..6bfe621c
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/compare/test5.cmp
@@ -0,0 +1,2 @@
+Test File 5
+page not found \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/compare/test6.cmp b/velocity-engine-core/src/test/resources/includeevent/compare/test6.cmp
new file mode 100644
index 00000000..6552b9d8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/compare/test6.cmp
@@ -0,0 +1,2 @@
+Bad include follows
+page not foundAfter the bad include
diff --git a/velocity-engine-core/src/test/resources/includeevent/include-a.vm b/velocity-engine-core/src/test/resources/includeevent/include-a.vm
new file mode 100644
index 00000000..de182c2b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/include-a.vm
@@ -0,0 +1 @@
+include file a \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/include-b.vm b/velocity-engine-core/src/test/resources/includeevent/include-b.vm
new file mode 100644
index 00000000..dbd487c6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/include-b.vm
@@ -0,0 +1 @@
+BAD include file b \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/include-c.vm b/velocity-engine-core/src/test/resources/includeevent/include-c.vm
new file mode 100644
index 00000000..1cea0302
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/include-c.vm
@@ -0,0 +1 @@
+Good include file c \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/include4.vm b/velocity-engine-core/src/test/resources/includeevent/include4.vm
new file mode 100644
index 00000000..53cfa46d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/include4.vm
@@ -0,0 +1 @@
+should not be included \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/include5.vm b/velocity-engine-core/src/test/resources/includeevent/include5.vm
new file mode 100644
index 00000000..53cfa46d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/include5.vm
@@ -0,0 +1 @@
+should not be included \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/notfound.vm b/velocity-engine-core/src/test/resources/includeevent/notfound.vm
new file mode 100644
index 00000000..b808213a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/notfound.vm
@@ -0,0 +1 @@
+page not found \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/parse-a.vm b/velocity-engine-core/src/test/resources/includeevent/parse-a.vm
new file mode 100644
index 00000000..04dce3ed
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/parse-a.vm
@@ -0,0 +1 @@
+parse file a \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/parse-b.vm b/velocity-engine-core/src/test/resources/includeevent/parse-b.vm
new file mode 100644
index 00000000..788116e8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/parse-b.vm
@@ -0,0 +1 @@
+BAD parse file b \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/parse-c.vm b/velocity-engine-core/src/test/resources/includeevent/parse-c.vm
new file mode 100644
index 00000000..5618329e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/parse-c.vm
@@ -0,0 +1 @@
+Good parse file c
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/include-b.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/include-b.vm
new file mode 100644
index 00000000..0600a4a3
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/include-b.vm
@@ -0,0 +1 @@
+Good include file b \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/include-c.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/include-c.vm
new file mode 100644
index 00000000..4b34bf1e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/include-c.vm
@@ -0,0 +1 @@
+BAD include file c
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/include4.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/include4.vm
new file mode 100644
index 00000000..5f9c3eb7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/include4.vm
@@ -0,0 +1 @@
+should not be included (subdir) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/include5.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/include5.vm
new file mode 100644
index 00000000..5f9c3eb7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/include5.vm
@@ -0,0 +1 @@
+should not be included (subdir) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/notfound.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/notfound.vm
new file mode 100644
index 00000000..8305207a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/notfound.vm
@@ -0,0 +1 @@
+page not found (subdir) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/parse-b.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/parse-b.vm
new file mode 100644
index 00000000..91e04250
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/parse-b.vm
@@ -0,0 +1 @@
+Good parse file b \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/parse-c.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/parse-c.vm
new file mode 100644
index 00000000..b4b19c29
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/parse-c.vm
@@ -0,0 +1 @@
+BAD parse file c
diff --git a/velocity-engine-core/src/test/resources/includeevent/subdir/test2.vm b/velocity-engine-core/src/test/resources/includeevent/subdir/test2.vm
new file mode 100644
index 00000000..30e685ca
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/subdir/test2.vm
@@ -0,0 +1,8 @@
+Test File 2
+#include("include-b.vm")
+
+#parse("parse-b.vm")
+
+#include("/include-c.vm")
+
+#parse("/parse-c.vm")
diff --git a/velocity-engine-core/src/test/resources/includeevent/test1-cp.vm b/velocity-engine-core/src/test/resources/includeevent/test1-cp.vm
new file mode 100644
index 00000000..c8184c4d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/test1-cp.vm
@@ -0,0 +1,4 @@
+Test File 1
+#include("/includeevent/include-a.vm")
+
+#parse("/includeevent/parse-a.vm")
diff --git a/velocity-engine-core/src/test/resources/includeevent/test1.vm b/velocity-engine-core/src/test/resources/includeevent/test1.vm
new file mode 100644
index 00000000..588acb6c
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/test1.vm
@@ -0,0 +1,4 @@
+Test File 1
+#include("include-a.vm")
+
+#parse("parse-a.vm")
diff --git a/velocity-engine-core/src/test/resources/includeevent/test3.vm b/velocity-engine-core/src/test/resources/includeevent/test3.vm
new file mode 100644
index 00000000..af8328aa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/test3.vm
@@ -0,0 +1,4 @@
+Test File 3
+#include("include-a.vm")
+
+#parse("parse-a.vm")
diff --git a/velocity-engine-core/src/test/resources/includeevent/test4.vm b/velocity-engine-core/src/test/resources/includeevent/test4.vm
new file mode 100644
index 00000000..1b56eb4a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/test4.vm
@@ -0,0 +1,2 @@
+Test File 4
+#include("include4.vm")
diff --git a/velocity-engine-core/src/test/resources/includeevent/test5.vm b/velocity-engine-core/src/test/resources/includeevent/test5.vm
new file mode 100644
index 00000000..ccba063a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/test5.vm
@@ -0,0 +1,2 @@
+Test File 5
+#include("include5.vm")
diff --git a/velocity-engine-core/src/test/resources/includeevent/test6.vm b/velocity-engine-core/src/test/resources/includeevent/test6.vm
new file mode 100644
index 00000000..546b57d8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/includeevent/test6.vm
@@ -0,0 +1,3 @@
+Bad include follows
+#include("badfile.vm")
+After the bad include
diff --git a/velocity-engine-core/src/test/resources/info/info1.vm b/velocity-engine-core/src/test/resources/info/info1.vm
new file mode 100644
index 00000000..e465cc22
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/info/info1.vm
@@ -0,0 +1 @@
+$main.unknownField \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/info/info2.vm b/velocity-engine-core/src/test/resources/info/info2.vm
new file mode 100644
index 00000000..c680b264
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/info/info2.vm
@@ -0,0 +1 @@
+$main.unknownMethod() \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537.vm.cmp b/velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537.vm.cmp
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537.vm.cmp
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537b.vm.cmp b/velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537b.vm.cmp
new file mode 100644
index 00000000..63d8dbd4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-537/compare/velocity537b.vm.cmp
@@ -0,0 +1 @@
+b \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537.vm b/velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537.vm
new file mode 100755
index 00000000..94ad353c
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537.vm
@@ -0,0 +1,3 @@
+#macro( test )#*
+*##end
+#test() \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537b.vm b/velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537b.vm
new file mode 100644
index 00000000..0db30029
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-537/templates/velocity537b.vm
@@ -0,0 +1,3 @@
+#macro( test )#*
+a*#b#end
+#test() \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-580/compare/velocity580.vm.cmp b/velocity-engine-core/src/test/resources/issues/velocity-580/compare/velocity580.vm.cmp
new file mode 100755
index 00000000..a10b7591
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-580/compare/velocity580.vm.cmp
@@ -0,0 +1 @@
+beforeafter
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-580/templates/velocity580.vm b/velocity-engine-core/src/test/resources/issues/velocity-580/templates/velocity580.vm
new file mode 100755
index 00000000..ba0f4321
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-580/templates/velocity580.vm
@@ -0,0 +1,5 @@
+#macro(someMacro )
+before#*
+ *#after
+#end
+#someMacro()
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-747/one.vm b/velocity-engine-core/src/test/resources/issues/velocity-747/one.vm
new file mode 100644
index 00000000..fe929cfd
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-747/one.vm
@@ -0,0 +1 @@
+#macro ( Test1 )This is from Test1 macro of one.vm#end#Test1 \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-747/two.vm b/velocity-engine-core/src/test/resources/issues/velocity-747/two.vm
new file mode 100644
index 00000000..fe6a7ec6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-747/two.vm
@@ -0,0 +1 @@
+#macro ( Test1 )This is from Test1 macro of two.vm#end#Test1 \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/issues/velocity-747/vel.props b/velocity-engine-core/src/test/resources/issues/velocity-747/vel.props
new file mode 100644
index 00000000..d456c55e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/issues/velocity-747/vel.props
@@ -0,0 +1,5 @@
+file.resource.loader.cache = true
+file.resource.loader.modificationCheckInterval = -1
+
+velocimacro.permissions.allow.inline.local.scope = true
+velocimacro.max.depth = -1
diff --git a/velocity-engine-core/src/test/resources/macroforwarddefine/compare/velocity.log.cmp b/velocity-engine-core/src/test/resources/macroforwarddefine/compare/velocity.log.cmp
new file mode 100644
index 00000000..9ed76dce
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macroforwarddefine/compare/velocity.log.cmp
@@ -0,0 +1,10 @@
+ [debug] VM #test1: too few arguments to macro. Wanted 1 got 0
+ [debug] VM #test2: too few arguments to macro. Wanted 1 got 0
+ [debug] VM #test3: too few arguments to macro. Wanted 1 got 0
+ [debug] VM #test4: too few arguments to macro. Wanted 1 got 0
+ [debug] VM #test1: too many arguments to macro. Wanted 1 got 2
+ [debug] VM #test2: too many arguments to macro. Wanted 1 got 2
+ [debug] VM #test3: too many arguments to macro. Wanted 1 got 2
+ [debug] VM #test4: too many arguments to macro. Wanted 1 got 2
+ [debug] VM #test1: too many arguments to macro. Wanted 1 got 2
+ [debug] VM #test1: too few arguments to macro. Wanted 1 got 0
diff --git a/velocity-engine-core/src/test/resources/macroforwarddefine/macros.vm b/velocity-engine-core/src/test/resources/macroforwarddefine/macros.vm
new file mode 100644
index 00000000..ae2469e0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macroforwarddefine/macros.vm
@@ -0,0 +1,40 @@
+#macro(test1 $p)
+ #test2($p)
+#end
+
+#macro(test1b $p)
+ #foreach($i in $foo)
+ #test2($p)
+ #end
+#end
+
+#macro(test2 $p)
+ $!p
+#end
+
+#macro(test3 $p)
+ #test1($p)
+#end
+
+#macro(test4 $p)
+ #test2($p)
+#end
+
+#test1("foo")
+#test2("foo")
+#test3("foo")
+#test4("foo")
+
+#test1()
+#test2()
+#test3()
+#test4()
+
+#test1("foo", "bar")
+#test2("foo", "bar")
+#test3("foo", "bar")
+#test4("foo", "bar")
+
+#@test1("foo", "bar")#end
+#@test1("foo")#end
+#@test1()#end
diff --git a/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library.cmp b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library.cmp
new file mode 100644
index 00000000..50d6ce44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library.cmp
@@ -0,0 +1,8 @@
+This is a test file for loading macro libs programatically
+
+call foo
+#foo(1)
+#bar(2)
+
+no macro definition
+#abc(2)
diff --git a/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_duplicate.cmp b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_duplicate.cmp
new file mode 100644
index 00000000..c9336d3a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_duplicate.cmp
@@ -0,0 +1,6 @@
+This is a test file for loading macro libs programatically
+
+call foo
+86
+no macro definition
+#abc(2)
diff --git a/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_global.cmp b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_global.cmp
new file mode 100644
index 00000000..35441498
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_global.cmp
@@ -0,0 +1,11 @@
+This is a test file for loading macro libs programatically
+
+call foo
+24
+no macro definition
+#abc(2) This is a test file for loading macro libs programatically
+
+call foo
+86
+no macro definition
+#abc(2) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_local.cmp b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_local.cmp
new file mode 100644
index 00000000..f148f9fc
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/compare/vm_library_local.cmp
@@ -0,0 +1,18 @@
+This is a test file for loading macro libs programatically
+
+call foo
+24
+no macro definition
+#abc(2) This is a test file for loading macro libs programatically
+
+call foo
+86
+no macro definition
+#abc(2) This is a test file for loading macro libs programatically
+
+call foo
+#foo(1)
+#bar(2)
+
+no macro definition
+#abc(2) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/macrolibs/vm_library.vm b/velocity-engine-core/src/test/resources/macrolibs/vm_library.vm
new file mode 100644
index 00000000..50d6ce44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/vm_library.vm
@@ -0,0 +1,8 @@
+This is a test file for loading macro libs programatically
+
+call foo
+#foo(1)
+#bar(2)
+
+no macro definition
+#abc(2)
diff --git a/velocity-engine-core/src/test/resources/macrolibs/vm_library1.vm b/velocity-engine-core/src/test/resources/macrolibs/vm_library1.vm
new file mode 100644
index 00000000..4736c5e6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/vm_library1.vm
@@ -0,0 +1,7 @@
+#macro(bar $a)
+#if($a)#set($a = $a + $a)$a#end
+#end
+
+#macro(foo $a)
+#if($a)#set($a = $a + 1)$a#end
+#end
diff --git a/velocity-engine-core/src/test/resources/macrolibs/vm_library2.vm b/velocity-engine-core/src/test/resources/macrolibs/vm_library2.vm
new file mode 100644
index 00000000..be45f763
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/vm_library2.vm
@@ -0,0 +1,7 @@
+#macro( bar $a)
+#if($a)#set($a = $a + $a + $a)$a#end
+#end
+
+#macro( foo $a)
+#if($a)#set($a = $a * 8)$a#end
+#end
diff --git a/velocity-engine-core/src/test/resources/macrolibs/vm_library_global.vm b/velocity-engine-core/src/test/resources/macrolibs/vm_library_global.vm
new file mode 100644
index 00000000..0e714570
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/vm_library_global.vm
@@ -0,0 +1,8 @@
+This is a test file for loading macro libs programatically
+
+call foo
+#foo(1)
+#bar(2)
+
+no macro definition
+#abc(2) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/macrolibs/vm_library_local.vm b/velocity-engine-core/src/test/resources/macrolibs/vm_library_local.vm
new file mode 100644
index 00000000..0e714570
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/macrolibs/vm_library_local.vm
@@ -0,0 +1,8 @@
+This is a test file for loading macro libs programatically
+
+call foo
+#foo(1)
+#bar(2)
+
+no macro definition
+#abc(2) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/methodoverloading/compare/main.cmp b/velocity-engine-core/src/test/resources/methodoverloading/compare/main.cmp
new file mode 100644
index 00000000..fe6b3de2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/methodoverloading/compare/main.cmp
@@ -0,0 +1,3 @@
+$test.overloadedMethod2( $param )
+String
+Integer
diff --git a/velocity-engine-core/src/test/resources/methodoverloading/compare/single.cmp b/velocity-engine-core/src/test/resources/methodoverloading/compare/single.cmp
new file mode 100644
index 00000000..5cb5d0fa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/methodoverloading/compare/single.cmp
@@ -0,0 +1,5 @@
+$test.overloadedMethod($nullValue)
+
+$test.overloadedMethod2( $nullValue )
+String
+Integer \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/methodoverloading/main.vm b/velocity-engine-core/src/test/resources/methodoverloading/main.vm
new file mode 100644
index 00000000..2f78b221
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/methodoverloading/main.vm
@@ -0,0 +1,6 @@
+#set($param = $nullObject)
+#parse ( "sub.vm" )
+#set($param = "string")
+#parse ( "sub.vm" )
+#set($param = 1234)
+#parse ( "sub.vm" )
diff --git a/velocity-engine-core/src/test/resources/methodoverloading/single.vm b/velocity-engine-core/src/test/resources/methodoverloading/single.vm
new file mode 100644
index 00000000..4463e083
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/methodoverloading/single.vm
@@ -0,0 +1,7 @@
+## ambiguous method -- should not be processed
+$test.overloadedMethod($nullValue)
+
+## is the correct method cached after null parameter
+$test.overloadedMethod2( $nullValue )
+$test.overloadedMethod2( "a string" )
+$test.overloadedMethod2( 1234 ) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/methodoverloading/sub.vm b/velocity-engine-core/src/test/resources/methodoverloading/sub.vm
new file mode 100644
index 00000000..e7c10006
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/methodoverloading/sub.vm
@@ -0,0 +1,2 @@
+## is the correct method cached after null parameter
+$test.overloadedMethod2( $param )
diff --git a/velocity-engine-core/src/test/resources/misc/README.txt b/velocity-engine-core/src/test/resources/misc/README.txt
new file mode 100644
index 00000000..ed067c63
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/misc/README.txt
@@ -0,0 +1,18 @@
+$Id$
+
+This directory contains some misc tests for you to ponder over.
+
+compile.sh: This script will compile a .vm file into a .class file.
+ Note: at the current time, this code is not working.
+ Usage: ./compile.sh ../templates/test.vm
+
+dump.sh: This script will dump out a text representation of the AST.
+ Usage: ./dump.sh ../templates/test.vm
+
+test.sh: This script is used for command line testing of .vm files.
+ Note: this script is not a replacement for the engine testing
+ suite. It is simply a convinence script/class for the developers.
+
+thanks!
+
+- The Velocity Team
diff --git a/velocity-engine-core/src/test/resources/misc/compile.sh b/velocity-engine-core/src/test/resources/misc/compile.sh
new file mode 100755
index 00000000..7654976b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/misc/compile.sh
@@ -0,0 +1,27 @@
+# !/bin/sh
+
+# 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.
+
+CLASSPATH=.:../../bin/classes
+
+for jar in ../../build/lib/*.jar
+do
+ CLASSPATH=${CLASSPATH}:${jar}
+done
+
+java -cp ${CLASSPATH} org.apache.velocity.runtime.compiler.Compiler $1
diff --git a/velocity-engine-core/src/test/resources/misc/dump.sh b/velocity-engine-core/src/test/resources/misc/dump.sh
new file mode 100755
index 00000000..f6663036
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/misc/dump.sh
@@ -0,0 +1,27 @@
+# !/bin/sh
+
+# 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.
+
+CLASSPATH=.:../../bin/classes
+
+for jar in ../../build/lib/*.jar
+do
+ CLASSPATH=${CLASSPATH}:${jar}
+done
+
+java -cp ${CLASSPATH} org.apache.velocity.test.view.TemplateNodeView $1 > output.dump
diff --git a/velocity-engine-core/src/test/resources/misc/test.sh b/velocity-engine-core/src/test/resources/misc/test.sh
new file mode 100755
index 00000000..aa951143
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/misc/test.sh
@@ -0,0 +1,27 @@
+# !/bin/sh
+
+# 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.
+
+CLASSPATH=.:../../bin/classes
+
+for jar in ../../build/lib/*.jar
+do
+ CLASSPATH=${CLASSPATH}:${jar}
+done
+
+java -cp ${CLASSPATH} org.apache.velocity.test.misc.Test $1 $2 > output 2>&1
diff --git a/velocity-engine-core/src/test/resources/multi/compare/path1.cmp b/velocity-engine-core/src/test/resources/multi/compare/path1.cmp
new file mode 100644
index 00000000..0bb7abda
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multi/compare/path1.cmp
@@ -0,0 +1,3 @@
+
+
+I am path1.vm
diff --git a/velocity-engine-core/src/test/resources/multi/compare/path2.cmp b/velocity-engine-core/src/test/resources/multi/compare/path2.cmp
new file mode 100644
index 00000000..9e9955a9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multi/compare/path2.cmp
@@ -0,0 +1,3 @@
+
+
+I am path2.vm
diff --git a/velocity-engine-core/src/test/resources/multi/path1/path1.vm b/velocity-engine-core/src/test/resources/multi/path1/path1.vm
new file mode 100644
index 00000000..06eafa40
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multi/path1/path1.vm
@@ -0,0 +1,12 @@
+#*
+
+@test path1.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+I am path1.vm
diff --git a/velocity-engine-core/src/test/resources/multi/path2/path2.vm b/velocity-engine-core/src/test/resources/multi/path2/path2.vm
new file mode 100644
index 00000000..c9ad1c1b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multi/path2/path2.vm
@@ -0,0 +1,12 @@
+#*
+
+@test path2.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+I am path2.vm
diff --git a/velocity-engine-core/src/test/resources/multiloader/compare/path1.cmp b/velocity-engine-core/src/test/resources/multiloader/compare/path1.cmp
new file mode 100644
index 00000000..0bb7abda
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multiloader/compare/path1.cmp
@@ -0,0 +1,3 @@
+
+
+I am path1.vm
diff --git a/velocity-engine-core/src/test/resources/multiloader/compare/test2.cmp b/velocity-engine-core/src/test/resources/multiloader/compare/test2.cmp
new file mode 100644
index 00000000..4010ee26
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multiloader/compare/test2.cmp
@@ -0,0 +1,3 @@
+Test File 1
+include file a
+parse file a \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/multiloader/compare/test3.cmp b/velocity-engine-core/src/test/resources/multiloader/compare/test3.cmp
new file mode 100644
index 00000000..8f04079f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multiloader/compare/test3.cmp
@@ -0,0 +1,3 @@
+
+
+this is a template for test2.jar
diff --git a/velocity-engine-core/src/test/resources/multiloader/path1.vm b/velocity-engine-core/src/test/resources/multiloader/path1.vm
new file mode 100644
index 00000000..06eafa40
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multiloader/path1.vm
@@ -0,0 +1,12 @@
+#*
+
+@test path1.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+I am path1.vm
diff --git a/velocity-engine-core/src/test/resources/multiloader/test1.jar b/velocity-engine-core/src/test/resources/multiloader/test1.jar
new file mode 100644
index 00000000..72a392f9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multiloader/test1.jar
Binary files differ
diff --git a/velocity-engine-core/src/test/resources/multiloader/test2.jar b/velocity-engine-core/src/test/resources/multiloader/test2.jar
new file mode 100644
index 00000000..eca67bf0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/multiloader/test2.jar
Binary files differ
diff --git a/velocity-engine-core/src/test/resources/oldproperties/velocity.properties b/velocity-engine-core/src/test/resources/oldproperties/velocity.properties
new file mode 100644
index 00000000..ce487db0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/oldproperties/velocity.properties
@@ -0,0 +1,264 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# ----------------------------------------------------------------------------
+# This controls whether invalid references are logged.
+# ----------------------------------------------------------------------------
+
+runtime.log.invalid.references = true
+
+# ----------------------------------------------------------------------------
+# T E M P L A T E E N C O D I N G
+# ----------------------------------------------------------------------------
+
+input.encoding=UTF-8
+
+# ----------------------------------------------------------------------------
+# Strings interning
+# ----------------------------------------------------------------------------
+# Set to true to optimize memory, to false to optimize speed
+
+runtime.string.interning = true
+
+# ----------------------------------------------------------------------------
+# F O R E A C H P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# This property controls how many loops #foreach can execute. The default
+# is -1, which means there is no limit.
+# ----------------------------------------------------------------------------
+
+directive.foreach.maxloops = -1
+
+# ----------------------------------------------------------------------------
+# I F P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# This property controls whether empty strings and collections,
+# as long as zero numbers, do evaluate to false.
+# ----------------------------------------------------------------------------
+
+directive.if.emptycheck = true
+
+# ----------------------------------------------------------------------------
+# I N C L U D E P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# These are the properties that governed the way #include'd content
+# is governed.
+# ----------------------------------------------------------------------------
+
+directive.include.output.errormsg.start = <!-- include error :
+directive.include.output.errormsg.end = see error log -->
+
+# ----------------------------------------------------------------------------
+# P A R S E P R O P E R T I E S
+# ----------------------------------------------------------------------------
+
+directive.parse.max.depth = 10
+
+# ----------------------------------------------------------------------------
+# S C O P E P R O P E R T I E S
+# ----------------------------------------------------------------------------
+# These are the properties that govern whether or not a Scope object
+# is automatically provided for each of the given scopes to serve as a
+# scope-safe reference namespace and "label" for #break calls. The default
+# for most of these is false. Note that <bodymacroname> should be replaced by
+# name of macros that take bodies for which you want to suppress the scope.
+# ----------------------------------------------------------------------------
+# template.provide.scope.control = false
+# evaluate.provide.scope.control = false
+foreach.provide.scope.control = true
+# macro.provide.scope.control = false
+# define.provide.scope.control = false
+# <bodymacroname>.provide.scope.control = false
+
+# ----------------------------------------------------------------------------
+# T E M P L A T E L O A D E R S
+# ----------------------------------------------------------------------------
+#
+#
+# ----------------------------------------------------------------------------
+
+resource.loader = file
+
+file.resource.loader.description = Velocity File Resource Loader
+file.resource.loader.class = org.apache.velocity.runtime.resource.loader.FileResourceLoader
+file.resource.loader.path = .
+file.resource.loader.cache = false
+file.resource.loader.modificationCheckInterval = 2
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO PROPERTIES
+# ----------------------------------------------------------------------------
+# global : name of default global library. It is expected to be in the regular
+# template path. You may remove it (either the file or this property) if
+# you wish with no harm.
+# ----------------------------------------------------------------------------
+# velocimacro.library = VM_global_library.vm
+
+velocimacro.permissions.allow.inline = true
+velocimacro.permissions.allow.inline.to.replace.global = false
+velocimacro.permissions.allow.inline.local.scope = false
+velocimacro.max.depth = 20
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO STRICT MODE
+# ----------------------------------------------------------------------------
+# if true, will throw an exception for incorrect number
+# of arguments. false by default (for backwards compatibility)
+# but this option will eventually be removed and will always
+# act as if true
+# ----------------------------------------------------------------------------
+velocimacro.arguments.strict = false
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO BODY REFERENCE
+# ----------------------------------------------------------------------------
+# Defines name of the reference that can be used to render the AST block passed to
+# block macro call as an argument inside a macro.
+# ----------------------------------------------------------------------------
+velocimacro.body.reference=bodyContent
+
+# ----------------------------------------------------------------------------
+# VELOCIMACRO PRESERVE ARGUMENTS LITERALS
+# ----------------------------------------------------------------------------
+# if true, when a macro has to render a null or invalid argument reference
+# which is not quiet, it will print the provided literal reference instead
+# of the one found in the body of the macro
+# ----------------------------------------------------------------------------
+velocimacro.preserve.arguments.literals = false
+
+
+# ----------------------------------------------------------------------------
+# STRICT REFERENCE MODE
+# ----------------------------------------------------------------------------
+# if true, will throw a MethodInvocationException for references
+# that are not defined in the context, or have not been defined
+# with a #set directive. This setting will also throw an exception
+# if an attempt is made to call a non-existing property on an object
+# or if the object is null. When this property is true then property
+# 'directive.set.null.allowed' is also set to true.
+# ----------------------------------------------------------------------------
+runtime.references.strict = false
+
+# ----------------------------------------------------------------------------
+# INTERPOLATION
+# ----------------------------------------------------------------------------
+# turn off and on interpolation of references and directives in string
+# literals. ON by default :)
+# ----------------------------------------------------------------------------
+runtime.interpolate.string.literals = true
+
+
+# ----------------------------------------------------------------------------
+# RESOURCE MANAGEMENT
+# ----------------------------------------------------------------------------
+# Allows alternative ResourceManager and ResourceCache implementations
+# to be plugged in.
+# ----------------------------------------------------------------------------
+resource.manager.class = org.apache.velocity.runtime.resource.ResourceManagerImpl
+resource.manager.cache.class = org.apache.velocity.runtime.resource.ResourceCacheImpl
+
+# ----------------------------------------------------------------------------
+# PARSER POOL
+# ----------------------------------------------------------------------------
+# Selects a custom factory class for the parser pool. Must implement
+# ParserPool. parser.pool.size is used by the default implementation
+# ParserPoolImpl
+# ----------------------------------------------------------------------------
+
+parser.pool.class = org.apache.velocity.runtime.ParserPoolImpl
+parser.pool.size = 20
+
+
+# ----------------------------------------------------------------------------
+# EVENT HANDLER
+# ----------------------------------------------------------------------------
+# Allows alternative event handlers to be plugged in. Note that each
+# class property is actually a comma-separated list of classes (which will
+# be called in order).
+# ----------------------------------------------------------------------------
+# eventhandler.referenceinsertion.class =
+# eventhandler.nullset.class =
+# eventhandler.methodexception.class =
+# eventhandler.include.class =
+
+
+# ----------------------------------------------------------------------------
+# PLUGGABLE INTROSPECTOR
+# ----------------------------------------------------------------------------
+# Allows alternative introspection and all that can of worms brings.
+# ----------------------------------------------------------------------------
+
+runtime.introspector.uberspect = org.apache.velocity.util.introspection.UberspectImpl
+
+# ----------------------------------------------------------------------------
+# CONVERSION HANDLER
+# ----------------------------------------------------------------------------
+# Sets the data types Conversion Handler used by the default uberspector
+# ----------------------------------------------------------------------------
+
+runtime.conversion.handler.class = org.apache.velocity.util.introspection.TypeConversionHandlerImpl
+
+
+# ----------------------------------------------------------------------------
+# SECURE INTROSPECTOR
+# ----------------------------------------------------------------------------
+# If selected, prohibits methods in certain classes and packages from being
+# accessed.
+# ----------------------------------------------------------------------------
+
+# Prohibit reflection
+introspector.restrict.packages = java.lang.reflect
+
+# ClassLoader, Thread, and subclasses disabled by default in SecureIntrospectorImpl
+
+# Restrict these system classes. Note that anything in this list is matched exactly.
+# (Subclasses must be explicitly named to be included).
+
+introspector.restrict.classes = java.lang.Class
+introspector.restrict.classes = java.lang.Compiler
+introspector.restrict.classes = java.lang.InheritableThreadLocal
+introspector.restrict.classes = java.lang.Package
+introspector.restrict.classes = java.lang.Process
+introspector.restrict.classes = java.lang.Runtime
+introspector.restrict.classes = java.lang.RuntimePermission
+introspector.restrict.classes = java.lang.SecurityManager
+introspector.restrict.classes = java.lang.System
+introspector.restrict.classes = java.lang.ThreadGroup
+introspector.restrict.classes = java.lang.ThreadLocal
+
+# Restrict instance managers for common servlet containers (Tomcat, JBoss, Jetty)
+
+introspector.restrict.classes = org.apache.catalina.core.DefaultInstanceManager
+introspector.restrict.classes = org.apache.tomcat.SimpleInstanceManager
+introspector.restrict.classes = org.wildfly.extension.undertow.deployment.UndertowJSPInstanceManager
+introspector.restrict.classes = org.eclipse.jetty.util.DecoratedObjectFactory
+
+# ----------------------------------------------------------------------------
+# SPACE GOBBLING
+# ----------------------------------------------------------------------------
+# Possible values: none, bc (aka Backward Compatible), lines, structured
+# ----------------------------------------------------------------------------
+
+space.gobbling = lines
+
+# ----------------------------------------------------------------------------
+# HYPHEN IN IDENTIFIERS
+# ----------------------------------------------------------------------------
+# Set to true to allow '-' in reference identifiers (backward compatibility option)
+# ----------------------------------------------------------------------------
+
+parser.allow_hyphen_in_identifiers = false
diff --git a/velocity-engine-core/src/test/resources/parseexception/badtemplate.vm b/velocity-engine-core/src/test/resources/parseexception/badtemplate.vm
new file mode 100644
index 00000000..d19970ec
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parseexception/badtemplate.vm
@@ -0,0 +1,7 @@
+ok
+ok
+ok
+ok
+ #set($s)
+ok
+ok \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1.cmp
new file mode 100644
index 00000000..db1daaa8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1.cmp
@@ -0,0 +1,4 @@
+
+2 4
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1b.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1b.cmp
new file mode 100644
index 00000000..b9e5d73b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_1b.cmp
@@ -0,0 +1,4 @@
+
+8 6
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2.cmp
new file mode 100644
index 00000000..db1daaa8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2.cmp
@@ -0,0 +1,4 @@
+
+2 4
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2b.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2b.cmp
new file mode 100644
index 00000000..b9e5d73b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_2b.cmp
@@ -0,0 +1,4 @@
+
+8 6
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3.cmp
new file mode 100644
index 00000000..db1daaa8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3.cmp
@@ -0,0 +1,4 @@
+
+2 4
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3b.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3b.cmp
new file mode 100644
index 00000000..b9e5d73b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_3b.cmp
@@ -0,0 +1,4 @@
+
+8 6
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4.cmp
new file mode 100644
index 00000000..db1daaa8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4.cmp
@@ -0,0 +1,4 @@
+
+2 4
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4b.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4b.cmp
new file mode 100644
index 00000000..b9e5d73b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro1_4b.cmp
@@ -0,0 +1,4 @@
+
+8 6
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro2.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro2.cmp
new file mode 100644
index 00000000..02f7ca5f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro2.cmp
@@ -0,0 +1,3 @@
+#foo(1) #bar(2)
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro3.cmp b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro3.cmp
new file mode 100644
index 00000000..b3e64925
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/compare/parseMacro3.cmp
@@ -0,0 +1,4 @@
+
+
+8 6
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/parseMacro1.vm b/velocity-engine-core/src/test/resources/parsemacros/parseMacro1.vm
new file mode 100644
index 00000000..ca041235
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/parseMacro1.vm
@@ -0,0 +1,4 @@
+#parse($includefile)
+#foo(1) #bar(2)
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/parseMacro2.vm b/velocity-engine-core/src/test/resources/parsemacros/parseMacro2.vm
new file mode 100644
index 00000000..02f7ca5f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/parseMacro2.vm
@@ -0,0 +1,3 @@
+#foo(1) #bar(2)
+
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/parseMacro3.vm b/velocity-engine-core/src/test/resources/parsemacros/parseMacro3.vm
new file mode 100644
index 00000000..e6e3c5a7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/parseMacro3.vm
@@ -0,0 +1,4 @@
+#parse("vm_library1.vm")
+#parse("vm_library2.vm")
+#foo(1) #bar(2)
+
diff --git a/velocity-engine-core/src/test/resources/parsemacros/vm_library1.vm b/velocity-engine-core/src/test/resources/parsemacros/vm_library1.vm
new file mode 100644
index 00000000..4736c5e6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/vm_library1.vm
@@ -0,0 +1,7 @@
+#macro(bar $a)
+#if($a)#set($a = $a + $a)$a#end
+#end
+
+#macro(foo $a)
+#if($a)#set($a = $a + 1)$a#end
+#end
diff --git a/velocity-engine-core/src/test/resources/parsemacros/vm_library2.vm b/velocity-engine-core/src/test/resources/parsemacros/vm_library2.vm
new file mode 100644
index 00000000..be45f763
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/parsemacros/vm_library2.vm
@@ -0,0 +1,7 @@
+#macro( bar $a)
+#if($a)#set($a = $a + $a + $a)$a#end
+#end
+
+#macro( foo $a)
+#if($a)#set($a = $a * 8)$a#end
+#end
diff --git a/velocity-engine-core/src/test/resources/reload/foo.vtl b/velocity-engine-core/src/test/resources/reload/foo.vtl
new file mode 100644
index 00000000..c6e874ad
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/reload/foo.vtl
@@ -0,0 +1 @@
+#foo('hip') \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/reload/macros.vtl b/velocity-engine-core/src/test/resources/reload/macros.vtl
new file mode 100644
index 00000000..b3f7684b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/reload/macros.vtl
@@ -0,0 +1 @@
+#macro(foo $txt)hop_$txt#{end}
diff --git a/velocity-engine-core/src/test/resources/resourcecaching/include/include1.vm b/velocity-engine-core/src/test/resources/resourcecaching/include/include1.vm
new file mode 100644
index 00000000..9daeafb9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/resourcecaching/include/include1.vm
@@ -0,0 +1 @@
+test
diff --git a/velocity-engine-core/src/test/resources/resourcecaching/testincludeparse.vm b/velocity-engine-core/src/test/resources/resourcecaching/testincludeparse.vm
new file mode 100644
index 00000000..47e91a00
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/resourcecaching/testincludeparse.vm
@@ -0,0 +1,5 @@
+line 1
+#include("include/include1.vm")
+line 2
+#parse("include/include1.vm")
+line 3 \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/resourceexists/testfile.vm b/velocity-engine-core/src/test/resources/resourceexists/testfile.vm
new file mode 100755
index 00000000..871c17a1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/resourceexists/testfile.vm
@@ -0,0 +1 @@
+we're just testing that this exists.
diff --git a/velocity-engine-core/src/test/resources/resourceinstance/compare/testfile.cmp b/velocity-engine-core/src/test/resources/resourceinstance/compare/testfile.cmp
new file mode 100644
index 00000000..e238363d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/resourceinstance/compare/testfile.cmp
@@ -0,0 +1,3 @@
+
+
+I am testfile.vm
diff --git a/velocity-engine-core/src/test/resources/resourceinstance/testfile.vm b/velocity-engine-core/src/test/resources/resourceinstance/testfile.vm
new file mode 100644
index 00000000..15a5ef09
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/resourceinstance/testfile.vm
@@ -0,0 +1,12 @@
+#*
+
+@test testfile.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+I am testfile.vm
diff --git a/velocity-engine-core/src/test/resources/set/compare/set1.cmp b/velocity-engine-core/src/test/resources/set/compare/set1.cmp
new file mode 100644
index 00000000..33fe58cf
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/set/compare/set1.cmp
@@ -0,0 +1,9 @@
+set1
+
+123
+$abc
+
+foo
+bar
+foo
+$map.bar
diff --git a/velocity-engine-core/src/test/resources/set/compare/set2.cmp b/velocity-engine-core/src/test/resources/set/compare/set2.cmp
new file mode 100644
index 00000000..a0616434
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/set/compare/set2.cmp
@@ -0,0 +1,13 @@
+set2
+
+123
+$abc
+
+foo
+bar
+foo
+$map.bar
+
+
+test
+$test
diff --git a/velocity-engine-core/src/test/resources/set/set1.vm b/velocity-engine-core/src/test/resources/set/set1.vm
new file mode 100644
index 00000000..b8735277
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/set/set1.vm
@@ -0,0 +1,17 @@
+## This template is used for the case in which #set with a null
+## is not accepted
+set1
+
+#set ($abc = "123")
+$abc
+#set ($abc = $boohoo)
+$abc
+
+#set ($map = {})
+#set($map.foo = "foo")
+#set($map.bar = "bar")
+$map.foo
+$map.bar
+#set($map.bar = $boohoo)
+$map.foo
+$map.bar
diff --git a/velocity-engine-core/src/test/resources/set/set2.vm b/velocity-engine-core/src/test/resources/set/set2.vm
new file mode 100644
index 00000000..358a58a4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/set/set2.vm
@@ -0,0 +1,29 @@
+## This template is used for the case in which #set with a null
+## IS accepted
+set2
+
+#set ($abc = "123")
+$abc
+#set ($abc = $boohoo)
+$abc
+
+#set ($map = {})
+#set($map.foo = "foo")
+#set($map.bar = "bar")
+$map.foo
+$map.bar
+#set($map.bar = $boohoo)
+$map.foo
+$map.bar
+
+##
+## check a macro
+##
+
+#macro (test)
+#set ($test = "test")
+$test
+#set ($test = $null)
+$test
+#end
+#test()
diff --git a/velocity-engine-core/src/test/resources/stop/parse.vm b/velocity-engine-core/src/test/resources/stop/parse.vm
new file mode 100644
index 00000000..3c2a155f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stop/parse.vm
@@ -0,0 +1 @@
+blaa1#{stop}blaa2
diff --git a/velocity-engine-core/src/test/resources/stop/stop1.vm b/velocity-engine-core/src/test/resources/stop/stop1.vm
new file mode 100644
index 00000000..c524b825
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stop/stop1.vm
@@ -0,0 +1,2 @@
+Text 1#stop
+Text 2 ## We should not see this
diff --git a/velocity-engine-core/src/test/resources/stop/stop2.vm b/velocity-engine-core/src/test/resources/stop/stop2.vm
new file mode 100644
index 00000000..1fcdc7e4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stop/stop2.vm
@@ -0,0 +1,2 @@
+Text123#test1()
+stuff
diff --git a/velocity-engine-core/src/test/resources/stop/stop3.vm b/velocity-engine-core/src/test/resources/stop/stop3.vm
new file mode 100644
index 00000000..e7d13a2e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stop/stop3.vm
@@ -0,0 +1 @@
+text1#parse("parse.vm")text2
diff --git a/velocity-engine-core/src/test/resources/stop/vmlib1.vm b/velocity-engine-core/src/test/resources/stop/vmlib1.vm
new file mode 100644
index 00000000..e2c29826
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stop/vmlib1.vm
@@ -0,0 +1,3 @@
+#macro(test1)
+stuff1#{stop}stuff2
+#end
diff --git a/velocity-engine-core/src/test/resources/stringloader/compare/change1.cmp b/velocity-engine-core/src/test/resources/stringloader/compare/change1.cmp
new file mode 100644
index 00000000..9500cb44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stringloader/compare/change1.cmp
@@ -0,0 +1 @@
+I am the 1 template. \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/stringloader/compare/change2.cmp b/velocity-engine-core/src/test/resources/stringloader/compare/change2.cmp
new file mode 100644
index 00000000..9b834680
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stringloader/compare/change2.cmp
@@ -0,0 +1 @@
+I am the two template. \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/stringloader/compare/multi1.cmp b/velocity-engine-core/src/test/resources/stringloader/compare/multi1.cmp
new file mode 100644
index 00000000..9500cb44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stringloader/compare/multi1.cmp
@@ -0,0 +1 @@
+I am the 1 template. \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/stringloader/compare/multi2.cmp b/velocity-engine-core/src/test/resources/stringloader/compare/multi2.cmp
new file mode 100644
index 00000000..9b834680
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stringloader/compare/multi2.cmp
@@ -0,0 +1 @@
+I am the two template. \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/stringloader/compare/simpletemplate.cmp b/velocity-engine-core/src/test/resources/stringloader/compare/simpletemplate.cmp
new file mode 100644
index 00000000..f42c89e8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/stringloader/compare/simpletemplate.cmp
@@ -0,0 +1 @@
+This is a test for a foo object \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/VM_global_library.vm b/velocity-engine-core/src/test/resources/templates/VM_global_library.vm
new file mode 100644
index 00000000..5089cbc9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/VM_global_library.vm
@@ -0,0 +1,27 @@
+#macro( quietnull $a)
+#if($a)$a#end
+#end
+
+#macro( recurse $a )
+ global recurse $a
+ #set( $a = $a - 1)
+ #if ($a > 0)
+ #recurse( $a )
+ #end
+#end
+
+#macro( callrecurse )
+ #set( $count = 5)
+ #recurse( $count )
+#end
+
+#macro( testbool $b )
+ #if($b)
+ arg true
+ #end
+ #if( ! $b )
+ arg false
+ #end
+#end
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/arithmetic.vm b/velocity-engine-core/src/test/resources/templates/arithmetic.vm
new file mode 100644
index 00000000..de4366a6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/arithmetic.vm
@@ -0,0 +1,73 @@
+#**
+
+@test arithmetic.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($foo = 5)
+#set($foo = $foo + 1)
+$foo
+
+#set($foo = $foo - 1)
+$foo
+
+#set($foo = $foo * 2)
+$foo
+
+#set($foo = $foo / 2)
+$foo
+
+Check the decimal literals
+#set($bar = 4.5e3)
+$bar
+
+#set($bar = 4.5e+3)
+$bar
+
+#set($bar = 4.5e-3)
+$bar
+
+#set($bar = 4.5e055)
+$bar
+
+#set($bar = 4.5)
+$bar
+
+#set($bar = $bar + 1)
+$bar
+
+#set($tbar = $bar * 2)
+$tbar
+
+Check that the system can handle integers greater than Integer.MAX_INT
+#set($baba = 100000000000)
+$baba
+
+#set($baba = $baba + 1)
+$baba
+
+#set ($foo = $foo / 2)
+$foo
+
+## now lets try some string concatenation
+
+#set($stringy = "This is a very long string"
+ + " that we are breaking up into multiple"
+ + " lines for testing."
+)
+$stringy
+
+#set($stringy = "This is a string. The number 2 = " + 2)
+$stringy
+
+#set($three = 3)
+#set($stringy = "This is a string."
++ " The value = "
++ $three
+)
+$stringy
diff --git a/velocity-engine-core/src/test/resources/templates/array.vm b/velocity-engine-core/src/test/resources/templates/array.vm
new file mode 100644
index 00000000..15362e6d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/array.vm
@@ -0,0 +1,48 @@
+#*
+
+@test array.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set( $foo = [$this, $that, $woog.Bar, [$this, "this"] ])
+
+$provider.concat(["a", "b", "c"])
+
+#set($foo = "a")
+$provider.objConcat( [1..2] )
+$provider.objConcat( ["a","b"] )
+$provider.objConcat( [1..2 ] )
+$provider.objConcat( [$foo] )
+$provider.objConcat( [ $foo] )
+$provider.objConcat( [$foo ] )
+
+#macro( showme $array )
+ #foreach( $i in $array )
+ > $i <#end
+
+#end
+
+#set($woog = "a")
+#set($floog = "b")
+#showme( [ $woog] )
+#showme( [ $woog,$floog] )
+#showme( [1..2])
+#showme( [1 ..2 ])
+
+## and more....
+
+$p.m( [ $A.g(1), $title ])
+$pp.messageFormat( [ $Abc.get($sti), $title, $ti, $sti, 'bodytext' ], $subtopicTemplate)
+#set ($args = [ $pp.nQuestions, $pass, $units ] )
+
+## expression in index
+
+#set($arr = [ 0, 1, 2, 3 ])
+#set($index = 1)
+
+$arr[$index + $index * 2]
diff --git a/velocity-engine-core/src/test/resources/templates/block.vm b/velocity-engine-core/src/test/resources/templates/block.vm
new file mode 100644
index 00000000..a04f1676
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/block.vm
@@ -0,0 +1,90 @@
+#*
+
+@test block.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+First test : spacing between stuff. Note that spacing preceeding the directives counts!
+One blank line follows
+
+#set($foo = false)
+#if ($foo)
+ this is true
+#elseif ($bar)
+ this is false
+#elseif (true)
+ this should be followed by two blank lines
+#end
+
+
+#if (true)
+ this is the if statement. (followed by two blank lines)
+
+ #if (true)
+
+ this is great (followed by a blank line, 4 spaces on a line,and 2 more, yes there should be one after the 4 spaces)
+
+ #elseif (false)
+ this is also great.
+ #end
+
+#elseif (true)
+ this is the first elseif.
+#elseif (false)
+ this is the second elseif.
+#else
+ this is the else statement
+#end
+
+-- Second Test : no spacing between anything (1 blank line follows)
+
+#set($foo = false)
+#if ($foo)
+ this is true
+#elseif ($bar)
+ this is false
+#elseif (true)
+ this
+#end
+#if (true)
+ this is the if statement.
+ #if (true)
+ this is great (line w/ 4 spaces follows (from in front of the \#end) + another blank line)
+ #elseif (false)
+ this is also great.
+ #end
+#elseif (true)
+ this is the first elseif.
+#elseif (false)
+ this is the second elseif.
+#else
+ this is the else statement
+#end
+
+------------
+#if(false)
+False
+#else
+True
+#end
+-----------
+
+-- Third Test : tight tight tight.
+-- one blank line follows
+
+blargh #if(true)This follows blargh#end
+
+blargh#if(true)This immedately follows blargh with a following newline
+#end
+
+-- Fourth Test : different kind of tight. blank line follows
+
+blargh
+#if(true)
+blargh2
+#end
+blargh3
diff --git a/velocity-engine-core/src/test/resources/templates/commas.vm b/velocity-engine-core/src/test/resources/templates/commas.vm
new file mode 100644
index 00000000..4afa432e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/commas.vm
@@ -0,0 +1,15 @@
+#macro(test $a $b)
+ a: $a
+ b: $b
+#end
+#macro(test2 $a, $b,$c,$d)
+ a2: $a
+ b2: $b
+ c2: $c
+ d2: $d
+#end
+
+#test("1","2")
+#test("1" , "2")
+#test("1" "2")
+#test2("1","2","3","4")
diff --git a/velocity-engine-core/src/test/resources/templates/comment-eof.vm b/velocity-engine-core/src/test/resources/templates/comment-eof.vm
new file mode 100644
index 00000000..03168872
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/comment-eof.vm
@@ -0,0 +1,3 @@
+## Test to see if ##EOF gives an error
+test
+## \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/comment.vm b/velocity-engine-core/src/test/resources/templates/comment.vm
new file mode 100644
index 00000000..4b591150
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/comment.vm
@@ -0,0 +1,78 @@
+#*
+
+@test comment.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+## this is a single line comment
+
+#*
+
+this is a multi line comment
+
+#if (
+
+*#
+
+#**
+
+@author jason van zyl
+
+*#
+
+this is some text.
+
+The following is a 'Christoph Comment' ;)
+## ##
+foo
+
+We can now comment after the inline set :
+
+#set( $foo = 1 ) ## and this is a set statement
+
+
+## here is a Nathan Bubna bug :
+
+$bar##
+#set($foo = 'foo!' )
+$foo
+
+
+## here's one reported by Daniel Dekany
+
+$##
+there is a dollar before me
+
+
+Test of multiline/singleline combo
+## fix for bug 7697
+
+#*
+ multiline
+
+ ## embedded singline comment
+
+ stuff
+
+*#
+
+with some closing text
+
+##test
+
+
+Test for Velocity-783:
+
+#set( $map = { 'foo' : 'bar' })
+$map.size()## ok
+$map.foo## not ok before bugfix
+$map.foo ## ok with a space behind
+${map.foo}## ok with curly braces
+$map## ok
+schmoo## ok
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/arithmetic.cmp b/velocity-engine-core/src/test/resources/templates/compare/arithmetic.cmp
new file mode 100644
index 00000000..d29ad302
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/arithmetic.cmp
@@ -0,0 +1,38 @@
+
+
+6
+
+5
+
+10
+
+5
+
+Check the decimal literals
+4500.0
+
+4500.0
+
+0.0045
+
+4.5E55
+
+4.5
+
+5.5
+
+11.0
+
+Check that the system can handle integers greater than Integer.MAX_INT
+100000000000
+
+100000000001
+
+2
+
+
+This is a very long string that we are breaking up into multiple lines for testing.
+
+This is a string. The number 2 = 2
+
+This is a string. The value = 3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/array.cmp b/velocity-engine-core/src/test/resources/templates/compare/array.cmp
new file mode 100644
index 00000000..447d3387
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/array.cmp
@@ -0,0 +1,25 @@
+
+
+
+a b c
+
+1 2
+a b
+1 2
+a
+a
+a
+
+
+ > a <
+ > a < > b <
+ > 1 < > 2 <
+ > 1 < > 2 <
+
+
+$p.m( [ $A.g(1), $title ])
+$pp.messageFormat( [ $Abc.get($sti), $title, $ti, $sti, 'bodytext' ], $subtopicTemplate)
+
+
+
+3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/block.cmp b/velocity-engine-core/src/test/resources/templates/compare/block.cmp
new file mode 100644
index 00000000..d20dc8cc
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/block.cmp
@@ -0,0 +1,35 @@
+
+First test : spacing between stuff. Note that spacing preceeding the directives counts!
+One blank line follows
+
+ this should be followed by two blank lines
+
+
+ this is the if statement. (followed by two blank lines)
+
+
+ this is great (followed by a blank line, 4 spaces on a line,and 2 more, yes there should be one after the 4 spaces)
+
+
+
+-- Second Test : no spacing between anything (1 blank line follows)
+
+ this
+ this is the if statement.
+ this is great (line w/ 4 spaces follows (from in front of the #end) + another blank line)
+
+------------
+True
+-----------
+
+-- Third Test : tight tight tight.
+-- one blank line follows
+
+blargh This follows blargh
+blarghThis immedately follows blargh with a following newline
+
+-- Fourth Test : different kind of tight. blank line follows
+
+blargh
+blargh2
+blargh3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/commas.cmp b/velocity-engine-core/src/test/resources/templates/compare/commas.cmp
new file mode 100644
index 00000000..d104cd56
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/commas.cmp
@@ -0,0 +1,11 @@
+
+ a: 1
+ b: 2
+ a: 1
+ b: 2
+ a: 1
+ b: 2
+ a2: 1
+ b2: 2
+ c2: 3
+ d2: 4
diff --git a/velocity-engine-core/src/test/resources/templates/compare/comment-eof.cmp b/velocity-engine-core/src/test/resources/templates/compare/comment-eof.cmp
new file mode 100644
index 00000000..9daeafb9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/comment-eof.cmp
@@ -0,0 +1 @@
+test
diff --git a/velocity-engine-core/src/test/resources/templates/compare/comment.cmp b/velocity-engine-core/src/test/resources/templates/compare/comment.cmp
new file mode 100644
index 00000000..bd04890f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/comment.cmp
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+this is some text.
+
+The following is a 'Christoph Comment' ;)
+foo
+
+We can now comment after the inline set :
+
+
+
+
+$barfoo!
+
+
+
+$there is a dollar before me
+
+
+Test of multiline/singleline combo
+
+
+
+with some closing text
+
+
+
+Test for Velocity-783:
+
+1barbar bar{foo=bar}schmoo
diff --git a/velocity-engine-core/src/test/resources/templates/compare/context_safety1.cmp b/velocity-engine-core/src/test/resources/templates/compare/context_safety1.cmp
new file mode 100644
index 00000000..6b2c79aa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/context_safety1.cmp
@@ -0,0 +1,3 @@
+ vector hello 1
+ vector hello 2
+ vector hello 3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/context_safety2.cmp b/velocity-engine-core/src/test/resources/templates/compare/context_safety2.cmp
new file mode 100644
index 00000000..5265975a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/context_safety2.cmp
@@ -0,0 +1,3 @@
+ array hello 1
+ array hello 2
+ array hello 3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/curly-directive.cmp b/velocity-engine-core/src/test/resources/templates/compare/curly-directive.cmp
new file mode 100644
index 00000000..100e53f8
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/curly-directive.cmp
@@ -0,0 +1,16 @@
+
+
+ this is a test
+
+sometextand yet more
+
+
+
+ hello
+
+xxx
+
+#if
+#else
+#elseif
+#set
diff --git a/velocity-engine-core/src/test/resources/templates/compare/diabolical.cmp b/velocity-engine-core/src/test/resources/templates/compare/diabolical.cmp
new file mode 100644
index 00000000..2513a278
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/diabolical.cmp
@@ -0,0 +1,52 @@
+
+
+$f.
+$f
+$f. $f
+foo.
+foo
+foo. foo
+$thingy.dingy
+"#FFFFFF"
+"$ow"
+
+"$row","var"
+#333333
+#FFFFFF
+"#FFFFF
+#FFFFF"
+"#000000">
+"#ffffff"
+
+$strings.getVillageType($col.Type)
+$strings.getVillageType( $col.Type)
+$strings.getVillageType($col.Type )
+
+
+-#-# Inline loops #-#-
+
+blargh><first element><second element><blargh
+blargh> <first element> <second element><blargh
+blargh><first element> <second element> <blargh
+blargh> <first element> <second element> <blargh
+
+#"FFFF
+-#-#-#-#-#
+#-#-#-#
+$-$-$-$-
+#FF00FF00
+#'FF
+
+$tstrings.chop($generator.parse("sql/mysql/columns", "", "table", $tbl),1)
+string
+
+foobar
+
+#0F
+
+$nullToString
+$nullToString.toString()
+
+
+
+$$fooo$fooo.$fooo.bar$fooo.bar($fooo.bar() \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/compare/directive.cmp b/velocity-engine-core/src/test/resources/templates/compare/directive.cmp
new file mode 100644
index 00000000..eb3c37c9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/directive.cmp
@@ -0,0 +1,5 @@
+
+
+this is a dynamic directive!
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/encodingtest.cmp b/velocity-engine-core/src/test/resources/templates/compare/encodingtest.cmp
new file mode 100644
index 00000000..63a9e890
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/encodingtest.cmp
@@ -0,0 +1,4 @@
+Thanks to Kent Johnson for this example and the nudge.
+
+<p>Chinese: 网站登录</p>
+<p>Spanish: niño</p>
diff --git a/velocity-engine-core/src/test/resources/templates/compare/encodingtest2.cmp b/velocity-engine-core/src/test/resources/templates/compare/encodingtest2.cmp
new file mode 100644
index 00000000..5a645c00
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/encodingtest2.cmp
@@ -0,0 +1,3 @@
+This is an example of chinese code encoding:
+
+The chinese string is 上网, its length is 2
diff --git a/velocity-engine-core/src/test/resources/templates/compare/encodingtest3.cmp b/velocity-engine-core/src/test/resources/templates/compare/encodingtest3.cmp
new file mode 100644
index 00000000..e0d7e24e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/encodingtest3.cmp
@@ -0,0 +1,19 @@
+<table>
+<tr>
+<td>
+<strong>
+Chinese/GBK
+</strong>
+</td>
+<td>
+<p>
+50ÄêÇ°µÄ½ñÌ죬ÖйúÈËÃñÖ¾Ô¸¾üÐÛôñôñÆø°º°º£¬ÔÚÈýǧÀュɽÉÏ¿ªÊ¼¿¹ÃÀÔ®³¯£¬±£¼ÒÎÀ¹ú¡£ÔÚÄǶοɸè¿ÉÆüµÄËêÔÂÖУ¬¶àÉÙÖйúÈËΪǰ·½Ö¾Ô¸¾ü½«Ê¿µÄ¾«ÉñËù¹ÄÎ裬ÔÚ¼ÄÍÐÃÀºÃ×£Ô¸µÄͬʱ£¬Ò²½«¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±ÕâÁ½¸öÃû´ÊƵƵΪ×Ô¼º¸Õ³öÉúµÄ×ÓÅ®ÃüÃû¡£50ÄêÀ´£¬¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±ÃÇ´ø×ÅÕⳡսÕùµÄÀúÊ·ºÛ¼££¬Ò»¸ö¸öµ½Á˽«½ü°ë°ÙµÄÄêÁ䣬½ñÌ죬ËûÃǹ¤×÷¡¢Éú»îµÃÈçºÎ£¿ÄǶÎÀúÊ·ÔÚ½ñÌìµÄËûÃÇÑÛÀÓÖÊÇÔõÑùÒ»·¬¾°Ï󣿱¾±¨¼ÇÕß×òÌìÏÂÎçÆð·ÖÍ·×·×Ù£¬Ñ°·ÃÄϾ©´ó½ÖСÏïÖеġ°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¡£
+<p>
+×·×ÙÒÁʼ£¬ÎÒÃÇÊ×ÏȺÍÄϾ©Êй«°²¾ÖµÄ»§Õþ¹ÜÀí²¿ÃÅÈ¡µÃÁËÁªÏµ£¬Ï£ÍûÄÜͨ¹ýËûÃDz鵽ÄϾ©Êе½µ×ÓжàÉÙ¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¡£ÄϾ©ÊÐÇø250ÍòÈË¿Ú£¬¼ÓÉÏÎåÏس¬¹ý500ÍòÈË¿Ú£¬²éÒ»±éÈËÃûÆÄ·Ñʱ¼ä¡£ËûÃÇ´óÁ¦Ö§³Ö£¬ÔÚ¹¤×÷Öмû·ì²åÕëΪÎÒÃÇ°²ÅÅÁËÉÏ»ú²éѯ£¬ÎÒÃÇÏÈ´Ó¡°Ô®³¯¡±²éÆ𣬾­¹ý½üÁ½¸öСʱµÄµçÄÔ²éѯ£¬ÄϾ©ÊÐÇø¹²²éµ½ÓÐ233λÊÐÃñµÄÃû×ÖÖк¬ÓС°Ô®³¯¡±¡£²é¡°¿¹ÃÀ¡±ÎÒÃDz»¸ÒÔÙ¡°É§ÈÅ¡±ËûÃÇÈç´Ë·ÑÁ¦²éѯ£¬±ã½øÐÐÁ˳éÑù²éѯ£¬½á¹û²éµÃÄϾ©Ãû½Ð¡°ÕÅ¿¹ÃÀ¡±µÄÓÐ6¸ö£¬¡°Íõ¿¹ÃÀ¡±8¸ö£¬¡°ËÃÀ¡±3¸ö£¬¡°ÀÃÀ¡±2¸ö£¬±ÈÆð¡°Ô®³¯¡±£¬¡°¿¹ÃÀ¡±Ò²²»ÔÚÉÙÊý¡£
+<p>
+ÔÚ²éѯ¡°Ô®³¯¡±µÄ¹ý³ÌÖУ¬ÓÉÓÚÊÇûÓÐÉ趨±»²éѯÕßµÄÄêÁäµÄ£¬½á¹û233¸ö¡°Ô®³¯¡±ÖУ¬²¢²»ÊÇÎÒÃÇÏëÏñÖж¼ÊÇ1950ÄêÖÁ1954ÄêÕâ¶Îʱ¼äÄÚ³öÉúµÄ£¬ÓÐЩÈ˵ijöÉúÄê·ÝÊÇ1944Äê¡¢1945Ä꣬¸ÃÊй«°²¾Ö»§Õþ¿ÆµÄͬ־˵£¬ÕâЩÈ˺ܿÉÄÜÊÇÔÚÉÏСѧʱȡµÄÃû»ò¸ÄµÄÃû£¬ÄÇʱÕýÖµ³¯ÏÊÕ½ÕùÆڼ䣬¸¸Ä¸Ëæ×ŵ±Ê±³±Á÷Ò²¸øº¢×ÓÈ¡(¸Ä)ÁËÕâÑùµÄÃû¡£
+<p>
+²éѯ×ÊÁÏÏÔʾ£¬¾ø´ó¶àÊý¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¶¼ÊÇ50Äê´ú³õµÄ¼¸Äê¼ä³öÊÀ¡£»§Õþ¿ÆµÄһλŮͬ־»ØÒä˵£¬ÄÇ»á½Ð¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±ÕæÊÇÒ»ÖÖʱ´úµÄ³±Á÷£¬Ëý¼ÇµÃµ±Ê±ÁÚ¾Ó¼ÒÓÐ4¸öСº¢£¬ÒÀ´Î¾Í½Ð¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¡¢¡°±£¼Ò¡±¡¢¡°ÎÀ¹ú¡±£¬½ÐÒ»±é×Ô¼Òº¢×ÓµÄÃû×Ö£¬Õæ¸öÊÇÓÐÖÖ¡°ÐÛôñôñÆø°º°º¡±µÄ¸Ð¾õÔÚÐļ䡣
+<td>
+</tr>
+</table> \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/compare/encodingtest_KOI8-R.cmp b/velocity-engine-core/src/test/resources/templates/compare/encodingtest_KOI8-R.cmp
new file mode 100644
index 00000000..45f55a44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/encodingtest_KOI8-R.cmp
@@ -0,0 +1,104 @@
+From - Sat Jun 2 11:47:36 2001
+Return-Path: <velocity-user-return-1985-geirm=optonline.net@jakarta.apache.org>
+Received: from mta1.srv.hcvlny.cv.net (mta1.srv.hcvlny.cv.net [167.206.5.4])
+ by s1.optonline.net (8.10.2/8.10.2) with ESMTP id f4U9uVc26907
+ for <@mail.srv.nrwlct.cv.net:geirm@optonline.net>; Wed, 30 May 2001 05:56:31 -0400 (EDT)
+Received: from apache.org (h31.sny.collab.net [64.208.42.41])
+ by mta1.srv.hcvlny.cv.net
+ (iPlanet Messaging Server 5.0 Patch 2 (built Dec 14 2000))
+ with SMTP id <0GE5008U97M83H@mta1.srv.hcvlny.cv.net> for geirm@optonline.net
+ (ORCPT geirm@optonline.net); Wed, 30 May 2001 05:56:33 -0400 (EDT)
+Received: (qmail 19350 invoked by uid 500); Wed, 30 May 2001 09:56:20 +0000
+Received: (qmail 19153 invoked from network); Wed, 30 May 2001 09:56:18 +0000
+Date: Wed, 30 May 2001 13:57:17 +0400
+From: Vitaly Repetenko <vit@mtu.ru>
+Subject: Re: Russian Character Encoding
+To: velocity-user@jakarta.apache.org
+Reply-to: velocity-user@jakarta.apache.org
+Message-id: <3B14C3FD.3834AF02@mtu.ru>
+Organization: MTU-Intel
+MIME-version: 1.0
+X-Mailer: Mozilla 4.77 [en] (Windows NT 5.0; U)
+Content-type: multipart/mixed; boundary="Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)"
+X-Accept-Language: ru,en
+Precedence: bulk
+Delivered-to: mailing list velocity-user@jakarta.apache.org
+Mailing-List: contact velocity-user-help@jakarta.apache.org; run by ezmlm
+X-Recipient: velocity-user@jakarta.apache.org
+X-Spam-Rating: h31.sny.collab.net 1.6.2 0/1000/N
+References: <3B139D1A.E49723E2@mtu.ru> <3B139DB1.CD00E5E2@optonline.net>
+ <3B13A6B9.B02D04BF@mtu.ru> <3B13B5FB.57130BA9@optonline.net>
+ <3B149DF4.72383B1D@mtu.ru> <3B14BC65.A688FFFB@optonline.net>
+List-Post: <mailto:velocity-user@jakarta.apache.org>
+List-Subscribe: <mailto:velocity-user-subscribe@jakarta.apache.org>
+List-Unsubscribe: <mailto:velocity-user-unsubscribe@jakarta.apache.org>
+List-Help: <mailto:velocity-user-help@jakarta.apache.org>
+X-Mozilla-Status: 8011
+X-Mozilla-Status2: 00000000
+X-UIDL: a57ee51a80f6ed07173b135dbac0735e
+
+This is a multi-part message in MIME format.
+
+--Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)
+Content-type: text/plain; charset=koi8-r
+Content-transfer-encoding: 7BIT
+
+template
+
+"Geir Magnusson Jr." wrote:
+
+> Vitaly Repetenko wrote:
+> >
+> > Hi!
+> >
+> > Test is attached.
+> >
+> > Not only russian capital "U" is converted into space but russian capital "F" (ASCII
+> > code E6) also.
+> >
+>
+> Can you attach a test template? Or better yet, see if the latest in CVS
+> fixes it? It might have gone in later than the nightly snapshot, so you
+> may need to just get a dump from CVS.
+>
+> geir
+>
+> --
+> Geir Magnusson Jr. geirm@optonline.net
+> System and Software Consulting
+> Developing for the web? See http://jakarta.apache.org/velocity/
+> "still climbing up to the shoulders..."
+
+--Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)
+Content-type: text/html; charset=koi8-r; name=charset_test.vm
+Content-transfer-encoding: 8BIT
+Content-disposition: inline; filename=charset_test.vm
+
+<html>
+<head><title>Russian charset test</title></head>
+<body bgcolor="#ffffff">
+
+<pre>
+ABCDEFGHIJKLMNOPRSTUVWXYZ
+abcdefghijklmnoprstuvwxyz
+
+Russian alphabet:(32 chars) Displayed without codes 0xF5 0xE6 (code page KOI8-R)
+
+Â×ÞÚÄųÃßÊËÌÍÎÏÐÒÔÕÆÈÖÉÇÀÙÜÑÝÛÁÓ
+ ^^
+------------------>F5E6
+â÷þúäå£ãÿêëìíîïðòôõæèöéçàùüñýûáó
+
+alt="Èõíèúê"
+ ^
+---->F5
+alt="Öêúâ"
+ ^
+---->E6
+</pre>
+
+</html>
+
+
+--Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)--
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/equality.cmp b/velocity-engine-core/src/test/resources/templates/compare/equality.cmp
new file mode 100644
index 00000000..9410730f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/equality.cmp
@@ -0,0 +1,11 @@
+
+
+
+ the user jason is logged in!
+
+
+ the count is 5!
+
+ the user isn't logged in.
+
+ $count is not equal to 3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/escape.cmp b/velocity-engine-core/src/test/resources/templates/compare/escape.cmp
new file mode 100644
index 00000000..4dd1d76e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/escape.cmp
@@ -0,0 +1,44 @@
+
+
+\A
+
+#set($woo = "bar")
+
+$woo => bar
+
+The following should print 'as is' :
+$f\oo
+\a
+"\r"
+
+Now, test the built in directives. Note that $foo isn't in the context :
+#set($foo = $foo + 1)
+#set(\$foo = $foo + 1)
+#if($foo)
+#if ( $foo )
+#else
+#end
+#elseif(
+
+Now, a reference not in the context:
+\$foo -> $foo
+#if($foo)
+#if(\$foo)
+
+Put it in :
+$foo -> 1
+#if(1)
+#if($foo)
+
+This isn't in the context, so we get the full monty :
+ \$woobie.blagh()
+
+The following two are references :
+ $provider.Title = lunatic
+ $provider.getTitle() = lunatic
+
+Now, pluggable directives:
+
+\#notadirective
+#foreach
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/escape2.cmp b/velocity-engine-core/src/test/resources/templates/compare/escape2.cmp
new file mode 100644
index 00000000..562a714a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/escape2.cmp
@@ -0,0 +1,106 @@
+
+--- Schmoo ---
+
+These are not in the context, so they should render as they are here (schmoo).
+$foo
+\$foo
+\\$foo
+
+\#woogie
+\\#woogie
+\\\#woogie
+
+Now put $foo in the context :
+$foo = bar
+\$foo =\bar
+\\$foo =\\bar
+
+As we increase the number of \'s, we alternate renderings :
+bar
+$foo
+\bar
+\$foo
+\\bar
+
+--- Pluggable Directives ----
+
+We are doing an #include("test.txt"), starting with 0 '\' preceeding :
+
+--text--
+#include("test.txt")
+\--text--
+\#include("test.txt")
+\\--text--
+
+Now, foreach is a PD. Escape the first one, and then not the second so it
+renders. The third and fourth examples show the single 'unpleasantry' about this. The \
+is only an escape when 'touching' VTL, otherwise, it's just schmoo.
+
+#foreach(
+
+\ first element \ second element \
+\ first element \ \ second element \ \
+\first element\ \second element\ \
+
+--- Control Structures ----
+
+First should be escaped...
+#if(true) hi #end
+
+This isn't. Note then that it has to render the \\ as a \ because it's stuck to the VTL
+
+\ hi \
+\ hi
+And so forth...
+\#if(true) hi \#end
+
+\\ hi \\
+And more...
+
+#if(true)
+ hi
+#else
+ there
+#end
+
+\ hi
+\
+\#if(true)
+ hi
+\#else
+ there
+\#end
+
+\ there
+\
+\#if(false)
+ hi
+\#elseif(true)
+ there
+\#end
+
+
+#$foo1
+\#$foo1
+#${foo1}
+\#$${foo1}
+#C0C0C0
+\#C0C0C0
+#C0C0C0
+\#$C0C0C0
+#\$C0C0C0
+
+
+$(QUERY_STRING{forumid})
+\$(QUERY_STRING{forumid})
+\\$(QUERY_STRING{forumid})
+
+
+\
+\\
+\\\
+\\\\
+\\\\\
+\\\\\\
+\\\\\\\
+\\\\\\\\
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-array.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-array.cmp
new file mode 100644
index 00000000..8f325f34
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-array.cmp
@@ -0,0 +1,33 @@
+
+
+--Foreach with an array. 2 blank lines follow
+
+
+<table>
+ <tr>
+ <td>This is first element and it is the 1 item</td>
+ </tr>
+ <tr>
+ <td>This is second element and it is the 2 item</td>
+ </tr>
+</table>
+
+--Foreach with a null array. 1 blank line follows
+
+<table>
+</table>
+
+-- And when we declare the array in-template :
+
+Choose among :
+ red
+ blue
+ green
+
+ $foo : a
+
+ $foo2 : bar
+
+-- Empty array
+ empty
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-introspect.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-introspect.cmp
new file mode 100644
index 00000000..88f43d58
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-introspect.cmp
@@ -0,0 +1,14 @@
+
+
+ 21 1
+ 22 2
+ 23 3
+ 24 4
+ 25 5
+ 26 6
+ 27 7
+ 28 8
+ 29 9
+ 30 10
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-map.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-map.cmp
new file mode 100644
index 00000000..75cc5d04
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-map.cmp
@@ -0,0 +1,5 @@
+
+
+ value2
+ value1
+ value0
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-method.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-method.cmp
new file mode 100644
index 00000000..4424d0aa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-method.cmp
@@ -0,0 +1,8 @@
+
+
+Foreach with a method.
+
+ This is ArrayList element 1.
+ This is ArrayList element 2.
+ This is ArrayList element 3.
+ This is ArrayList element 4.
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-null-list.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-null-list.cmp
new file mode 100644
index 00000000..fb693428
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-null-list.cmp
@@ -0,0 +1,12 @@
+
+
+Foreach with a list that contains null.
+
+ This is a.
+ 1
+ This is b.
+ 2
+ This is $element.
+ 3
+ This is d.
+ 4
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-type.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-type.cmp
new file mode 100644
index 00000000..2dcc3474
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-type.cmp
@@ -0,0 +1,45 @@
+
+
+Using a Object []
+
+ a
+ b
+ c
+ d
+
+ empty
+
+Using a Map
+
+ this is from a hashtable!
+ this is from a hashtable too!
+
+ this map was empty
+
+Using a Collection
+
+ string1
+ string2
+
+Using an Iterator
+
+ string1
+ string2
+
+ empty
+
+Using an Enumeration
+
+ string1
+ string2
+
+
+Using an array of primitives
+
+ 10
+ 20
+ 30
+ 40
+ 50
+
+ empty
diff --git a/velocity-engine-core/src/test/resources/templates/compare/foreach-variable.cmp b/velocity-engine-core/src/test/resources/templates/compare/foreach-variable.cmp
new file mode 100644
index 00000000..7f95ce41
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/foreach-variable.cmp
@@ -0,0 +1,89 @@
+
+
+Foreach with a variable.
+
+ This is ArrayList element 1.
+ 1
+ This is ArrayList element 2.
+ 2
+ This is ArrayList element 3.
+ 3
+ This is ArrayList element 4.
+ 4
+
+ -- inner foreach --
+ This is ArrayList element 1.
+ 1
+ This is ArrayList element 2.
+ 2
+ This is ArrayList element 3.
+ 3
+ This is ArrayList element 4.
+ 4
+ -- inner foreach --
+
+ -- outer foreach --
+ This is ArrayList element 1.
+ 1
+ -- outer foreach --
+ -- inner foreach --
+ This is ArrayList element 1.
+ 1
+ This is ArrayList element 2.
+ 2
+ This is ArrayList element 3.
+ 3
+ This is ArrayList element 4.
+ 4
+ -- inner foreach --
+
+ -- outer foreach --
+ This is ArrayList element 2.
+ 2
+ -- outer foreach --
+ -- inner foreach --
+ This is ArrayList element 1.
+ 1
+ This is ArrayList element 2.
+ 2
+ This is ArrayList element 3.
+ 3
+ This is ArrayList element 4.
+ 4
+ -- inner foreach --
+
+ -- outer foreach --
+ This is ArrayList element 3.
+ 3
+ -- outer foreach --
+ -- inner foreach --
+ This is ArrayList element 1.
+ 1
+ This is ArrayList element 2.
+ 2
+ This is ArrayList element 3.
+ 3
+ This is ArrayList element 4.
+ 4
+ -- inner foreach --
+
+ -- outer foreach --
+ This is ArrayList element 4.
+ 4
+ -- outer foreach --
+
+ArrayList element 1 ArrayList element 1
+ArrayList element 2 ArrayList element 2
+ArrayList element 3 ArrayList element 3
+ArrayList element 4 ArrayList element 4
+
+ArrayList element 1 ArrayList element 1
+ArrayList element 2 ArrayList element 2
+ArrayList element 3 ArrayList element 3
+ArrayList element 4 ArrayList element 4
+
+ArrayList element 1 ArrayList element 1
+ArrayList element 2 ArrayList element 2
+ArrayList element 3 ArrayList element 3
+ArrayList element 4 ArrayList element 4
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/formal.cmp b/velocity-engine-core/src/test/resources/templates/compare/formal.cmp
new file mode 100644
index 00000000..9428e50d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/formal.cmp
@@ -0,0 +1,34 @@
+
+
+1. lunatic
+
+2. lunatic
+
+3. lunatic
+
+4. lunatic
+
+lunaticlunatic
+
+lunaticMapBuilder
+
+lunatic.map.lunaticMapBuilder
+
+
+thisthat
+
+lunatic
+
+value0
+value1
+value2
+$provider.getHashtable().get("floogie!")
+
+
+{lunatic
+lunatic}
+{lunatic}
+test provider}.Title
+test provider.Title
+test provider{.Title
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/get.cmp b/velocity-engine-core/src/test/resources/templates/compare/get.cmp
new file mode 100644
index 00000000..fd92745d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/get.cmp
@@ -0,0 +1,7 @@
+
+
+
+Muck
+Duck
+Truck
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/if.cmp b/velocity-engine-core/src/test/resources/templates/compare/if.cmp
new file mode 100644
index 00000000..e5988012
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/if.cmp
@@ -0,0 +1,5 @@
+
+
+ this is true
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/ifstatement.cmp b/velocity-engine-core/src/test/resources/templates/compare/ifstatement.cmp
new file mode 100644
index 00000000..8e3d6d39
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/ifstatement.cmp
@@ -0,0 +1,29 @@
+
+
+
+start :
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+done
diff --git a/velocity-engine-core/src/test/resources/templates/compare/include.cmp b/velocity-engine-core/src/test/resources/templates/compare/include.cmp
new file mode 100644
index 00000000..0c2a67a0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/include.cmp
@@ -0,0 +1,41 @@
+
+
+#*
+
+@test include.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#include("include.vm" "include.vm")
+
+#set($foo = "subdir/test.txt")
+
+#include($foo)
+
+#*
+
+@test include.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#include("include.vm" "include.vm")
+
+#set($foo = "subdir/test.txt")
+
+#include($foo)
+
+
+
+This is included text!
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/interpolation.cmp b/velocity-engine-core/src/test/resources/templates/compare/interpolation.cmp
new file mode 100644
index 00000000..6829c327
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/interpolation.cmp
@@ -0,0 +1,51 @@
+
+
+Start with simple string interpolation :
+
+it will cost you $10.00
+
+dog.jpg
+
+foobar.jpg
+
+foobar.jpg
+
+123
+1 2 3
+
+How about a directive? Sure :
+
+
+ >a< >b< >c<
+
+For our next trick, lets interpolate a.... VelociMacro!
+
+
+
+ Hi, I'm a VM!
+
+
+And now, for something completely different :
+
+
+ False
+
+Now, non interpolated stringlits :
+
+
+ False
+$code
+$!$\!code
+
+Now, check comments within strings. double quotes they should be removed.
+Single quotes, they should be kept literal.
+
+
+test
+test
+
+testtest
+test##test
+test#* hello *#test
+
+-- end --
diff --git a/velocity-engine-core/src/test/resources/templates/compare/logical.cmp b/velocity-engine-core/src/test/resources/templates/compare/logical.cmp
new file mode 100644
index 00000000..7f59f6fb
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/logical.cmp
@@ -0,0 +1,197 @@
+
+
+
+ $foo is greater then 1
+
+ $foo is less than 10
+
+ $foo is great than or equal to 5
+
+ $foo is less than or equal to 5
+
+
+ foo is false
+
+--
+
+ $foo is greater than $bar
+
+ $foo is greater than or equal to $bar
+
+ $bar is less than $foo
+
+ $bar is less than or equal to $foo
+
+--
+
+ $foo is greater than $bar
+
+ $foo is greater than or equal to $bar
+
+ $bar is less than $foo
+
+ $bar is less than or equal to $foo
+
+--
+
+ $foo is equal to $foo
+
+ $foo is not equal to $bar
+
+--
+
+ $templatenumber1 is greater than $foo
+
+ $templatenumber1 is greater than or equal to $foo
+
+ $foo is less than $templatenumber1
+
+ $foo is less than or equal to $templatenumber1
+
+--
+
+ $bar is greater than $templatenumber1
+
+ $bar is greater than or equal to $templatenumber1
+
+ $templatenumber1 is less than $bar
+
+ $templatenumber1 is less than or equal to $bar
+
+--
+
+ $abc is equal to $templatenumber1
+
+ $templatenumber1 is equal to $abc
+
+ $bar is not equal to $templatenumber1
+
+ $templatenumber1 is not equal to $bar
+
+--
+
+Logical OR :
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+Logical AND :
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+----------
+equivalence
+-----------
+
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+-----------
+comparisons
+-----------
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+----------------------
+goofy but legal stuff
+----------------------
+Should equal true : true
+
+Should equal true : true
+
+Should equal true : true
+
+Should equal true : true
+
+----------------------
+Compare String and StringBuffer
+----------------------
+This should be true: true
+
+This should be false: false
+
+This should be true: true
+
+
+right
+
+
+right
+
+Test to see if we can do logical assignment from any expression
+
+right
+
+right
+
+
+right
+
+
+right
+
+right
+
+right
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/logical2.cmp b/velocity-engine-core/src/test/resources/templates/compare/logical2.cmp
new file mode 100644
index 00000000..03828913
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/logical2.cmp
@@ -0,0 +1,133 @@
+
+
+
+ $foo is greater then 1
+
+ $foo is less than 10
+
+ $foo is great than or equal to 5
+
+ $foo is less than or equal to 5
+
+
+ foo is false
+
+--
+
+
+Logical OR :
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+Logical AND :
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+ right
+
+----------
+equivalence
+-----------
+
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+-----------
+comparisons
+-----------
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+right
+
+----------------------
+goofy but legal stuff
+----------------------
+Should equal true : true
+
+Should equal true : true
+
+Should equal true : true
+
+Should equal true : true
+
+
+
+right
+
+
+right
+
+Test to see if we can do logical assignment from any expression
+
+right
+
+right
+
+
+right
+
+
+right
+
+right
+
+right
diff --git a/velocity-engine-core/src/test/resources/templates/compare/loop.cmp b/velocity-engine-core/src/test/resources/templates/compare/loop.cmp
new file mode 100644
index 00000000..ec294ac1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/loop.cmp
@@ -0,0 +1,6 @@
+
+
+ hello< ArrayList element 1 >
+ hello< ArrayList element 2 >
+ hello< ArrayList element 3 >
+ hello< ArrayList element 4 >
diff --git a/velocity-engine-core/src/test/resources/templates/compare/map.cmp b/velocity-engine-core/src/test/resources/templates/compare/map.cmp
new file mode 100644
index 00000000..fb55c01f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/map.cmp
@@ -0,0 +1,36 @@
+
+
+
+this is from a hashtable!
+
+
+foovalue
+foovalue
+
+foovalue
+foovalue
+
+
+
+booboo
+aval
+aval
+bval
+bval
+2
+value
+foovalue
+$mymap.map.foo
+{aa=aaa}
+
+
+0
+
+0
+
+
+aa
+bb
+bb
+aa
+aa
diff --git a/velocity-engine-core/src/test/resources/templates/compare/math.cmp b/velocity-engine-core/src/test/resources/templates/compare/math.cmp
new file mode 100644
index 00000000..c2eee4e7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/math.cmp
@@ -0,0 +1,47 @@
+
+
+Addition and subtraction :
+1 + 1 = 2
+2 - 1 = 1
+
+Multiplication, division, and modulus :
+5 % 2 = 1
+5 % 0 = $rem2
+7 % 2 = 1
+5 / 2 = 2
+5 / 0 = $rem4
+5 * 2 = 10
+
+5 * -1 = -5
+5 * -2 = -10
+5 * -2 = -10
+
+And now null nodes to make sure it doesn't throw an NPE :
+
+Some test for the new number-handling
+1000 + 10000000000 = 10000001000
+1000 - 10000000000 = -9999999000
+1000 * 10000000000 = 10000000000000
+1000 / 10000000000 = 0
+1000 % 10000000000 = 1000
+
+1000 + 1000.1234 = 2000.1234
+1000 - 1000.1234 = -0.123413086
+1000 * 1000.1234 = 1000123.44
+1000 / 1000.1234 = 0.9998766
+
+This checks that an object implementing TemplateNumber can be used in arithmetic
+1000 + 999.125 = 1999.125
+1000 - 999.125 = 0.875
+1000 * 999.125 = 999125.0
+1000 / 999.125 = 1.0008757662955086
+
+Test integer division
+5 / 2 = 2
+
+Test decimal division
+5 / 2.0 = 2.5
+5.0 / 2 = 2.5
+
+Unary negate
+-5 = -5 = -5 = -5 = -5 = -5
diff --git a/velocity-engine-core/src/test/resources/templates/compare/method.cmp b/velocity-engine-core/src/test/resources/templates/compare/method.cmp
new file mode 100644
index 00000000..1fbec60b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/method.cmp
@@ -0,0 +1,4 @@
+
+
+I am a running man
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/newline.cmp b/velocity-engine-core/src/test/resources/templates/compare/newline.cmp
new file mode 100644
index 00000000..ac3e7ed2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/newline.cmp
@@ -0,0 +1,20 @@
+
+This is a string
+
+
+Call woog
+
+
+hello there
+
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
diff --git a/velocity-engine-core/src/test/resources/templates/compare/parse.cmp b/velocity-engine-core/src/test/resources/templates/compare/parse.cmp
new file mode 100644
index 00000000..45f4dd7d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/parse.cmp
@@ -0,0 +1,22 @@
+
+
+Test the #parse pluggable directive
+
+Now calling parse1.vm :
+---
+
+This is content from parse1.vm
+
+ Hello first element.
+ Hello second element.
+
+Now using a reference to get the next one :
+ --
+
+This is parse2.vm.
+
+ This is from a true #if.
+ --
+done with parse1.vm
+---
+all done!
diff --git a/velocity-engine-core/src/test/resources/templates/compare/pedantic.cmp b/velocity-engine-core/src/test/resources/templates/compare/pedantic.cmp
new file mode 100644
index 00000000..72b117ee
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/pedantic.cmp
@@ -0,0 +1,79 @@
+
+
+This is a test of the new pedantic mode.
+
+There are a few things you can do in pedantic mode.
+
+Like get the spacing between things first elementsecond element to be really, really tight.
+
+Further, it now binds any \n to the control structures, taking them out of the output.
+The hope that this is What You Expect.
+So...
+
+--
+pedantic
+--
+
+should come out looking like
+
+--
+pedantic
+--
+
+But pay attention to what follows the #end statement :
+
+1) First, follow with 'stuff' (not sure why you want to do this... but anway...)
+
+--
+pedantic
+ woogie!
+--
+
+should be
+
+--
+pedantic
+ woogie!
+--
+
+2) Whitespace will be eaten if there is a following newline
+
+--
+pedantic
+--
+
+should be
+
+--
+pedantic
+--
+
+
+-- INLINE STUFF ---
+
+1) respect spaces in the block
+>first elementsecond element<
+> first element second element<
+>first element second element <
+> first element second element <
+
+2) set statement has no output, incuding preceeding whitespace
+ first element is first element
+ second element is second element
+
+ public void foo( String lala )
+ {
+ System.out.println("first element");
+ System.out.println("second element");
+ }
+
+ public void foo( String lala )
+ {
+ System.out.println("first element");
+ System.out.println("second element");
+ }
+
+Inline set statement :
+
+Here are the prices : $10.24 $15.32 $12.15
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/quotes.cmp b/velocity-engine-core/src/test/resources/templates/compare/quotes.cmp
new file mode 100644
index 00000000..fcf113e0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/quotes.cmp
@@ -0,0 +1,9 @@
+
+
+
+ "what is that"
+
+<input type="checkbox" name="jason" />
+
+<input type="checkbox" name="this is from a hashtable!" />
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/range.cmp b/velocity-engine-core/src/test/resources/templates/compare/range.cmp
new file mode 100644
index 00000000..009c12c1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/range.cmp
@@ -0,0 +1,62 @@
+
+[1..5]
+ 1 2 3 4 5
+-----
+[0..0]
+ 0
+-----
+[-4..-5]
+ -4 -5
+-----
+[ 1 .. 5 ]
+ 1 2 3 4 5
+-----
+[5..1]
+ 5 4 3 2 1
+-----
+[-5..5]
+ -5 -4 -3 -2 -1 0 1 2 3 4 5
+-----
+[5..-5]
+ 5 4 3 2 1 0 -1 -2 -3 -4 -5
+-----
+refs $a=1 $b=5 [$a..$b]
+ 1 2 3 4 5
+-----
+refs $a=1 $b="5" [$a.."$b"]
+ 1 2 3 4 5
+-----
+[$a.. 7]
+ 1 2 3 4 5 6 7
+-----
+[-7 ..$a]
+ -7 -6 -5 -4 -3 -2 -1 0 1
+-----
+[ -7 ..$a]
+ -7 -6 -5 -4 -3 -2 -1 0 1
+------
+setting in $foo -> [0..5] :
+0 1 2 3 4 5
+----
+
+Now some use-case examples. Suppose we want a table to have 10 rows
+
+
+<table>
+<tr><td>a</td></tr>
+<tr><td>b</td></tr>
+<tr><td>c</td></tr>
+<tr><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td></tr>
+<tr><td>&nbsp;</td></tr>
+</table>
+
+----
+Wide loop should not OOM
+that's enough
+
+=done=
diff --git a/velocity-engine-core/src/test/resources/templates/compare/reference.cmp b/velocity-engine-core/src/test/resources/templates/compare/reference.cmp
new file mode 100644
index 00000000..da0e414a
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/reference.cmp
@@ -0,0 +1,75 @@
+
+
+bar
+ $_foo equals "bar" : bar
+
+Late introspection :
+
+7
+
+
+More stupid reference escaping ...
+
+When it does exist in the context :
+
+foo
+$foo
+$!foo
+$!foo
+$\!foo
+\$!foo
+
+And when it doesn't :
+
+$bar
+\$bar
+
+\$!bar (because it's just text...)
+
+$!foo
+$\!foo
+$\\!foo
+$\\\!foo
+\\$!foo
+
+Misc tests :
+
+[$foo.bar]
+
+
+Test lower case property names
+
+lunatic
+lunatic
+
+ vector element 1
+ vector element 2
+ vector element 1
+ vector element 2
+
+Now test if we can use lowercase for propertes in set
+Was : lunatic
+Now : geir
+Back : lunatic
+
+Test what was a bug :
+
+boycat.java
+boycat.java
+
+
+More tests :
+
+lunatic
+$lunatic
+#lunatic
+
+$foo.bar ($bar)
+
+Test boolean introspection isFoo()
+
+ Correct
+
+
+ Correct
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/sample.cmp b/velocity-engine-core/src/test/resources/templates/compare/sample.cmp
new file mode 100644
index 00000000..b1bfb3f9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/sample.cmp
@@ -0,0 +1,30 @@
+
+
+<html>
+<head><title>Sample velocity page</title></head>
+<body bgcolor="#ffffff">
+<center>
+
+<h2>Hello from velocity!</h2>
+<i>Here's the list of people</i>
+<table cellspacing="0" cellpadding="5" widht="100%">
+ <tr>
+ <td bgcolor="#eeeeee" align="center">
+ Names
+ </td>
+ </tr>
+ <tr>
+ <td bgcolor="#eeeeee">ArrayList element 1</td>
+ </tr>
+ <tr>
+ <td bgcolor="#eeeeee">ArrayList element 2</td>
+ </tr>
+ <tr>
+ <td bgcolor="#eeeeee">ArrayList element 3</td>
+ </tr>
+ <tr>
+ <td bgcolor="#eeeeee">ArrayList element 4</td>
+ </tr>
+ </table>
+</center>
+</html>
diff --git a/velocity-engine-core/src/test/resources/templates/compare/settest.cmp b/velocity-engine-core/src/test/resources/templates/compare/settest.cmp
new file mode 100644
index 00000000..c0abd91c
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/settest.cmp
@@ -0,0 +1,17 @@
+*
+
+@test settest.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+Tests #set parsing funkyness
+*#
+
+
+
+ I am setthing : bar
+
+SessionBean#setSessionContext
diff --git a/velocity-engine-core/src/test/resources/templates/compare/shorthand.cmp b/velocity-engine-core/src/test/resources/templates/compare/shorthand.cmp
new file mode 100644
index 00000000..6269f0dd
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/shorthand.cmp
@@ -0,0 +1 @@
+<input type="text" name="email" value="">
diff --git a/velocity-engine-core/src/test/resources/templates/compare/stop1.cmp b/velocity-engine-core/src/test/resources/templates/compare/stop1.cmp
new file mode 100644
index 00000000..fc258335
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/stop1.cmp
@@ -0,0 +1,2 @@
+This page checks the stop directive in the main body
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/stop2.cmp b/velocity-engine-core/src/test/resources/templates/compare/stop2.cmp
new file mode 100644
index 00000000..c050d315
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/stop2.cmp
@@ -0,0 +1,6 @@
+This page checks the stop directive inside an if statement
+
+
+this should render
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/stop3.cmp b/velocity-engine-core/src/test/resources/templates/compare/stop3.cmp
new file mode 100644
index 00000000..028bda51
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/stop3.cmp
@@ -0,0 +1,4 @@
+This test checks the stop directive when included from a parse directive.
+
+Foo is: stop3-include.vm
+A line from stop3-include.vm
diff --git a/velocity-engine-core/src/test/resources/templates/compare/string-interpolation.cmp b/velocity-engine-core/src/test/resources/templates/compare/string-interpolation.cmp
new file mode 100644
index 00000000..a849ce87
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/string-interpolation.cmp
@@ -0,0 +1,10 @@
+it will cost you $10.00
+
+dog.jpg
+
+foobar.jpg
+
+foobar.jpg
+
+123
+1 2 3
diff --git a/velocity-engine-core/src/test/resources/templates/compare/string.cmp b/velocity-engine-core/src/test/resources/templates/compare/string.cmp
new file mode 100644
index 00000000..ff1d5652
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/string.cmp
@@ -0,0 +1,30 @@
+
+This is a very long string that we are breaking up into multiple lines for testing.
+
+This is a string. The number 2 = 2
+
+This is a string. The value = 3
+
+
+ New language feature
+allows newlines in the strings
+ might make some happy
+
+
+V V EEEEE L OOOOO CCCCC I TTTTT Y Y
+ V V E L O O C I T Y Y
+ V V EEE L O O C I T Y
+ VV E L O O C I T Y
+ V EEEEE LLLL OOOOO CCCCC I T Y
+
+
+
+RRRRR OOOOO CCCCC K K SSSSS
+R R O O C K K S
+RRRR O O C KK SSSS
+R R O O C K K S
+R R O O C K K S
+R R OOOOO CCCCC K K SSSS
+
+
+[10, 20, 30, 40, 50]
diff --git a/velocity-engine-core/src/test/resources/templates/compare/subclass.cmp b/velocity-engine-core/src/test/resources/templates/compare/subclass.cmp
new file mode 100644
index 00000000..bae233b7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/subclass.cmp
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+Person
+Child
diff --git a/velocity-engine-core/src/test/resources/templates/compare/test.cmp b/velocity-engine-core/src/test/resources/templates/compare/test.cmp
new file mode 100644
index 00000000..c38daf64
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/test.cmp
@@ -0,0 +1,162 @@
+
+
+<html>
+<body>
+
+jason
+
+
+
+
+
+
+this is testing for wild loose commas , ,
+
+$100
+
+
+
+This is the bar way.
+
+ This is $bar.
+
+ This is the if.
+
+
+#set $foo = "bar"
+
+$foo => bar
+$foo; => bar;
+$foo. => bar.
+$foo.. => bar..
+$foo/ => bar/
+$foo" => bar"
+$foo\ => bar\
+$foo< => bar<
+$foo- => bar-
+\$fooo+ => $fooo+
+$foo-x => bar-x
+$foo$ => bar$
+
+
+
+jon
+ nothing here
+
+function preload(imgObj,imgSrc)
+{
+ if (document.images)
+ {
+ eval(imgObj+' = new Image()')
+ eval(imgObj+'.src = "'+imgSrc+'"')
+ }
+}
+
+function changeImage(layer,imgName,imgObj)
+{
+ if (document.images)
+ {
+ if (document.layers && layer!=null) eval('document.'+layer+'.document.images["'+imgName+'"].src = '+imgObj+'.src')
+ else document.images[imgName].src = eval(imgObj+".src")
+ }
+}
+
+
+<!-- This is an HTML comment -->
+
+
+$provider2.Title
+
+x
+
+x
+
+<input type="text" name="email" value="">
+<input type="text" name="email" value="">
+
+
+lunatic
+
+
+lunatic
+
+
+
+
+
+crocodile hunter!
+
+
+
+<!-- look here -->
+
+lunatic
+
+
+
+jason
+
+
+jason
+
+
+
+
+
+
+
+
+
+ This is a property that returns a boolean
+ value of true.
+
+ This expression is always (true).
+
+
+Foreach with a variable.
+
+ This is ArrayList element 1.
+ This is ArrayList element 2.
+ This is ArrayList element 3.
+ This is ArrayList element 4.
+
+Foreach with an array.
+
+<table>
+ <tr>
+ <td>This is first element</td>
+ </tr>
+ <tr>
+ <td>This is second element</td>
+ </tr>
+</table>
+
+
+ This is the vector element 1.
+ This is the vector element 2.
+
+
+Foreach with a method.
+
+ This is ArrayList element 1.
+ This is ArrayList element 2.
+ This is ArrayList element 3.
+ This is ArrayList element 4.
+
+$10.00
+
+"this is great"
+
+(this is also great)
+
+This is the \#stuff and this
+is the way \#to \#go.
+
+this = that
+
+I am a jason.
+
+ Yes the APL rules!
+
+</body>
+</html>
diff --git a/velocity-engine-core/src/test/resources/templates/compare/velocimacro.cmp b/velocity-engine-core/src/test/resources/templates/compare/velocimacro.cmp
new file mode 100644
index 00000000..cdcdd99b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/velocimacro.cmp
@@ -0,0 +1,59 @@
+
+
+
+Now, use the #quietnull example from the global library VM_global_library.vm :
+Now, there should be nothing in the brackets : ><
+
+Where there should be something here : >hello!<
+
+
+<table>
+ <tr><tdi bgcolor=blue>$10.24</td></tr>
+ <tr><tdi bgcolor=blue>$15.32</td></tr>
+ <tr><tdi bgcolor=blue>$12.15</td></tr>
+</table>
+
+
+Further tests. The following VMs and non-VM should be properly escaped :
+#tablerow
+#quietnull
+\#notavm
+>\<
+
+Now try to define a new quietnull VM :
+
+It should have been rejected, as the default is to not let inlines override existing, and there
+should be a message in velocity.log.
+Therefore it should still function normally :
+>hello!<
+><
+
+We should be able to use argless VMs (and directives....)
+
+Hello! I have no args!
+
+
+And there was a bug where I wasn't getting the params right for the use-instance :
+
+
+Arg :>$i<
+
+String literals should work as you expect :
+Arg :>stringliteral<
+
+
+Test boolean args :
+
+ arg true
+ arg false
+
+Test map args :
+
+
+aval
+bval
+
+avalinline
+bvalinline
+
+- Another fine Velocity Production -
diff --git a/velocity-engine-core/src/test/resources/templates/compare/velocimacro2.cmp b/velocity-engine-core/src/test/resources/templates/compare/velocimacro2.cmp
new file mode 100644
index 00000000..e7c3ae59
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/velocimacro2.cmp
@@ -0,0 +1,43 @@
+
+
+
+
+ Hello from foo : hi
+ Hello from foo2 : hi
+ Hello from foo_two : hi
+
+
+ Hello from foo : $a
+ Hello from foo : $a
+
+
+
+ $a : no
+ $a : no
+
+
+ bar : yes
+
+ Hello from foo2 : bar
+
+
+ <td align=center class=v10><b>#</b></td>
+ <td align=center class=v10><b> # </b></td>
+ <td align=center class=v10><b>\#</b></td>
+
+
+
+ hi victor
+
+
+
+
+>1<
+>true<
+>false<
+>hello<
+>hello<
+>1<
+>[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]<
+>[a, b, 1]<
+
diff --git a/velocity-engine-core/src/test/resources/templates/compare/vm_test1.cmp b/velocity-engine-core/src/test/resources/templates/compare/vm_test1.cmp
new file mode 100644
index 00000000..5de4b06e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/vm_test1.cmp
@@ -0,0 +1,8 @@
+
+
+ global recurse 5
+ global recurse 4
+ global recurse 3
+ global recurse 2
+ global recurse 1
+ \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/compare/vm_test2.cmp b/velocity-engine-core/src/test/resources/templates/compare/vm_test2.cmp
new file mode 100644
index 00000000..4b584f4e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/compare/vm_test2.cmp
@@ -0,0 +1,9 @@
+
+
+
+ local recurse 5
+ local recurse 4
+ local recurse 3
+ local recurse 2
+ local recurse 1
+ \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/context_safety.vm b/velocity-engine-core/src/test/resources/templates/context_safety.vm
new file mode 100644
index 00000000..4b2c45f7
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/context_safety.vm
@@ -0,0 +1,3 @@
+#foreach($a in $vector)
+ $a
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/curly-directive.vm b/velocity-engine-core/src/test/resources/templates/curly-directive.vm
new file mode 100644
index 00000000..3da1fa38
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/curly-directive.vm
@@ -0,0 +1,34 @@
+#*
+
+Check to see if directives with curly braces around them parse properly.
+
+*#
+
+#{if} (true)
+ this is a test
+#{elseif} (true)
+ more text
+#{else}
+ this is a test
+#end
+
+## a more realistic example
+#if(true)sometext#{else}more text#{end}and yet more
+
+### others
+#{set} ($a = 3)
+
+#{macro}(mymacro)
+ hello
+#{end}
+
+#{mymacro}()
+
+#foreach ($a in [1..3])x#{end}
+
+## Check escaped directives
+
+\#if
+\#else
+\#elseif
+\#set
diff --git a/velocity-engine-core/src/test/resources/templates/diabolical.vm b/velocity-engine-core/src/test/resources/templates/diabolical.vm
new file mode 100644
index 00000000..c8d8e207
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/diabolical.vm
@@ -0,0 +1,74 @@
+#*
+
+@test diabolical.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+This is for wierd strangeness that doesn't have any other home.
+
+*#
+
+$f.
+$f
+$f. $f
+#set($f = "foo")
+$f.
+$f
+$f. $f
+$thingy.dingy
+"#FFFFFF"
+"$ow"
+
+"$row","var"
+#333333
+#FFFFFF
+"#FFFFF
+#FFFFF"
+"#000000">
+"#ffffff"
+
+$strings.getVillageType($col.Type)
+$strings.getVillageType( $col.Type)
+$strings.getVillageType($col.Type )
+
+
+-#-# Inline loops #-#-
+
+blargh>#foreach($a in $stringarray)<$a>#end<blargh
+blargh>#foreach($a in $stringarray) <$a>#end<blargh
+blargh>#foreach($a in $stringarray)<$a> #end<blargh
+blargh>#foreach($a in $stringarray) <$a> #end<blargh
+
+#"FFFF
+-#-#-#-#-#
+#-#-#-#
+$-$-$-$-
+#FF00FF00
+#'FF
+
+$tstrings.chop($generator.parse("sql/mysql/columns", "", "table", $tbl),1)
+$provider.chop("stringy",1)
+
+#set($foo = "foo")
+#if ($foo.length()>0)$foo#end
+#set($bar = "bar")
+#if ($bar.length()>0)$bar#end
+
+
+#set($a=1)
+#0F
+
+$nullToString
+$nullToString.toString()
+$!nullToString
+$!nullToString.toString()
+
+$#set($foo = $bar)##
+$fooo#set($foo = $bar)##
+$fooo.#set($foo = $bar)##
+$fooo.bar#set($foo = $bar)##
+$fooo.bar(#set($foo = $bar)##
+$fooo.bar()#set($foo = $bar)##
diff --git a/velocity-engine-core/src/test/resources/templates/encodingtest.vm b/velocity-engine-core/src/test/resources/templates/encodingtest.vm
new file mode 100644
index 00000000..63a9e890
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/encodingtest.vm
@@ -0,0 +1,4 @@
+Thanks to Kent Johnson for this example and the nudge.
+
+<p>Chinese: 网站登录</p>
+<p>Spanish: niño</p>
diff --git a/velocity-engine-core/src/test/resources/templates/encodingtest2.vm b/velocity-engine-core/src/test/resources/templates/encodingtest2.vm
new file mode 100644
index 00000000..128847dc
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/encodingtest2.vm
@@ -0,0 +1,4 @@
+This is an example of chinese code encoding:
+
+#set ($ch = "上网")
+The chinese string is $ch, its length is $ch.length()
diff --git a/velocity-engine-core/src/test/resources/templates/encodingtest3.vm b/velocity-engine-core/src/test/resources/templates/encodingtest3.vm
new file mode 100644
index 00000000..e0d7e24e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/encodingtest3.vm
@@ -0,0 +1,19 @@
+<table>
+<tr>
+<td>
+<strong>
+Chinese/GBK
+</strong>
+</td>
+<td>
+<p>
+50ÄêÇ°µÄ½ñÌ죬ÖйúÈËÃñÖ¾Ô¸¾üÐÛôñôñÆø°º°º£¬ÔÚÈýǧÀュɽÉÏ¿ªÊ¼¿¹ÃÀÔ®³¯£¬±£¼ÒÎÀ¹ú¡£ÔÚÄǶοɸè¿ÉÆüµÄËêÔÂÖУ¬¶àÉÙÖйúÈËΪǰ·½Ö¾Ô¸¾ü½«Ê¿µÄ¾«ÉñËù¹ÄÎ裬ÔÚ¼ÄÍÐÃÀºÃ×£Ô¸µÄͬʱ£¬Ò²½«¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±ÕâÁ½¸öÃû´ÊƵƵΪ×Ô¼º¸Õ³öÉúµÄ×ÓÅ®ÃüÃû¡£50ÄêÀ´£¬¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±ÃÇ´ø×ÅÕⳡսÕùµÄÀúÊ·ºÛ¼££¬Ò»¸ö¸öµ½Á˽«½ü°ë°ÙµÄÄêÁ䣬½ñÌ죬ËûÃǹ¤×÷¡¢Éú»îµÃÈçºÎ£¿ÄǶÎÀúÊ·ÔÚ½ñÌìµÄËûÃÇÑÛÀÓÖÊÇÔõÑùÒ»·¬¾°Ï󣿱¾±¨¼ÇÕß×òÌìÏÂÎçÆð·ÖÍ·×·×Ù£¬Ñ°·ÃÄϾ©´ó½ÖСÏïÖеġ°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¡£
+<p>
+×·×ÙÒÁʼ£¬ÎÒÃÇÊ×ÏȺÍÄϾ©Êй«°²¾ÖµÄ»§Õþ¹ÜÀí²¿ÃÅÈ¡µÃÁËÁªÏµ£¬Ï£ÍûÄÜͨ¹ýËûÃDz鵽ÄϾ©Êе½µ×ÓжàÉÙ¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¡£ÄϾ©ÊÐÇø250ÍòÈË¿Ú£¬¼ÓÉÏÎåÏس¬¹ý500ÍòÈË¿Ú£¬²éÒ»±éÈËÃûÆÄ·Ñʱ¼ä¡£ËûÃÇ´óÁ¦Ö§³Ö£¬ÔÚ¹¤×÷Öмû·ì²åÕëΪÎÒÃÇ°²ÅÅÁËÉÏ»ú²éѯ£¬ÎÒÃÇÏÈ´Ó¡°Ô®³¯¡±²éÆ𣬾­¹ý½üÁ½¸öСʱµÄµçÄÔ²éѯ£¬ÄϾ©ÊÐÇø¹²²éµ½ÓÐ233λÊÐÃñµÄÃû×ÖÖк¬ÓС°Ô®³¯¡±¡£²é¡°¿¹ÃÀ¡±ÎÒÃDz»¸ÒÔÙ¡°É§ÈÅ¡±ËûÃÇÈç´Ë·ÑÁ¦²éѯ£¬±ã½øÐÐÁ˳éÑù²éѯ£¬½á¹û²éµÃÄϾ©Ãû½Ð¡°ÕÅ¿¹ÃÀ¡±µÄÓÐ6¸ö£¬¡°Íõ¿¹ÃÀ¡±8¸ö£¬¡°ËÃÀ¡±3¸ö£¬¡°ÀÃÀ¡±2¸ö£¬±ÈÆð¡°Ô®³¯¡±£¬¡°¿¹ÃÀ¡±Ò²²»ÔÚÉÙÊý¡£
+<p>
+ÔÚ²éѯ¡°Ô®³¯¡±µÄ¹ý³ÌÖУ¬ÓÉÓÚÊÇûÓÐÉ趨±»²éѯÕßµÄÄêÁäµÄ£¬½á¹û233¸ö¡°Ô®³¯¡±ÖУ¬²¢²»ÊÇÎÒÃÇÏëÏñÖж¼ÊÇ1950ÄêÖÁ1954ÄêÕâ¶Îʱ¼äÄÚ³öÉúµÄ£¬ÓÐЩÈ˵ijöÉúÄê·ÝÊÇ1944Äê¡¢1945Ä꣬¸ÃÊй«°²¾Ö»§Õþ¿ÆµÄͬ־˵£¬ÕâЩÈ˺ܿÉÄÜÊÇÔÚÉÏСѧʱȡµÄÃû»ò¸ÄµÄÃû£¬ÄÇʱÕýÖµ³¯ÏÊÕ½ÕùÆڼ䣬¸¸Ä¸Ëæ×ŵ±Ê±³±Á÷Ò²¸øº¢×ÓÈ¡(¸Ä)ÁËÕâÑùµÄÃû¡£
+<p>
+²éѯ×ÊÁÏÏÔʾ£¬¾ø´ó¶àÊý¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¶¼ÊÇ50Äê´ú³õµÄ¼¸Äê¼ä³öÊÀ¡£»§Õþ¿ÆµÄһλŮͬ־»ØÒä˵£¬ÄÇ»á½Ð¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±ÕæÊÇÒ»ÖÖʱ´úµÄ³±Á÷£¬Ëý¼ÇµÃµ±Ê±ÁÚ¾Ó¼ÒÓÐ4¸öСº¢£¬ÒÀ´Î¾Í½Ð¡°¿¹ÃÀ¡±¡¢¡°Ô®³¯¡±¡¢¡°±£¼Ò¡±¡¢¡°ÎÀ¹ú¡±£¬½ÐÒ»±é×Ô¼Òº¢×ÓµÄÃû×Ö£¬Õæ¸öÊÇÓÐÖÖ¡°ÐÛôñôñÆø°º°º¡±µÄ¸Ð¾õÔÚÐļ䡣
+<td>
+</tr>
+</table> \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/encodingtest_KOI8-R.vm b/velocity-engine-core/src/test/resources/templates/encodingtest_KOI8-R.vm
new file mode 100644
index 00000000..45f55a44
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/encodingtest_KOI8-R.vm
@@ -0,0 +1,104 @@
+From - Sat Jun 2 11:47:36 2001
+Return-Path: <velocity-user-return-1985-geirm=optonline.net@jakarta.apache.org>
+Received: from mta1.srv.hcvlny.cv.net (mta1.srv.hcvlny.cv.net [167.206.5.4])
+ by s1.optonline.net (8.10.2/8.10.2) with ESMTP id f4U9uVc26907
+ for <@mail.srv.nrwlct.cv.net:geirm@optonline.net>; Wed, 30 May 2001 05:56:31 -0400 (EDT)
+Received: from apache.org (h31.sny.collab.net [64.208.42.41])
+ by mta1.srv.hcvlny.cv.net
+ (iPlanet Messaging Server 5.0 Patch 2 (built Dec 14 2000))
+ with SMTP id <0GE5008U97M83H@mta1.srv.hcvlny.cv.net> for geirm@optonline.net
+ (ORCPT geirm@optonline.net); Wed, 30 May 2001 05:56:33 -0400 (EDT)
+Received: (qmail 19350 invoked by uid 500); Wed, 30 May 2001 09:56:20 +0000
+Received: (qmail 19153 invoked from network); Wed, 30 May 2001 09:56:18 +0000
+Date: Wed, 30 May 2001 13:57:17 +0400
+From: Vitaly Repetenko <vit@mtu.ru>
+Subject: Re: Russian Character Encoding
+To: velocity-user@jakarta.apache.org
+Reply-to: velocity-user@jakarta.apache.org
+Message-id: <3B14C3FD.3834AF02@mtu.ru>
+Organization: MTU-Intel
+MIME-version: 1.0
+X-Mailer: Mozilla 4.77 [en] (Windows NT 5.0; U)
+Content-type: multipart/mixed; boundary="Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)"
+X-Accept-Language: ru,en
+Precedence: bulk
+Delivered-to: mailing list velocity-user@jakarta.apache.org
+Mailing-List: contact velocity-user-help@jakarta.apache.org; run by ezmlm
+X-Recipient: velocity-user@jakarta.apache.org
+X-Spam-Rating: h31.sny.collab.net 1.6.2 0/1000/N
+References: <3B139D1A.E49723E2@mtu.ru> <3B139DB1.CD00E5E2@optonline.net>
+ <3B13A6B9.B02D04BF@mtu.ru> <3B13B5FB.57130BA9@optonline.net>
+ <3B149DF4.72383B1D@mtu.ru> <3B14BC65.A688FFFB@optonline.net>
+List-Post: <mailto:velocity-user@jakarta.apache.org>
+List-Subscribe: <mailto:velocity-user-subscribe@jakarta.apache.org>
+List-Unsubscribe: <mailto:velocity-user-unsubscribe@jakarta.apache.org>
+List-Help: <mailto:velocity-user-help@jakarta.apache.org>
+X-Mozilla-Status: 8011
+X-Mozilla-Status2: 00000000
+X-UIDL: a57ee51a80f6ed07173b135dbac0735e
+
+This is a multi-part message in MIME format.
+
+--Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)
+Content-type: text/plain; charset=koi8-r
+Content-transfer-encoding: 7BIT
+
+template
+
+"Geir Magnusson Jr." wrote:
+
+> Vitaly Repetenko wrote:
+> >
+> > Hi!
+> >
+> > Test is attached.
+> >
+> > Not only russian capital "U" is converted into space but russian capital "F" (ASCII
+> > code E6) also.
+> >
+>
+> Can you attach a test template? Or better yet, see if the latest in CVS
+> fixes it? It might have gone in later than the nightly snapshot, so you
+> may need to just get a dump from CVS.
+>
+> geir
+>
+> --
+> Geir Magnusson Jr. geirm@optonline.net
+> System and Software Consulting
+> Developing for the web? See http://jakarta.apache.org/velocity/
+> "still climbing up to the shoulders..."
+
+--Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)
+Content-type: text/html; charset=koi8-r; name=charset_test.vm
+Content-transfer-encoding: 8BIT
+Content-disposition: inline; filename=charset_test.vm
+
+<html>
+<head><title>Russian charset test</title></head>
+<body bgcolor="#ffffff">
+
+<pre>
+ABCDEFGHIJKLMNOPRSTUVWXYZ
+abcdefghijklmnoprstuvwxyz
+
+Russian alphabet:(32 chars) Displayed without codes 0xF5 0xE6 (code page KOI8-R)
+
+Â×ÞÚÄųÃßÊËÌÍÎÏÐÒÔÕÆÈÖÉÇÀÙÜÑÝÛÁÓ
+ ^^
+------------------>F5E6
+â÷þúäå£ãÿêëìíîïðòôõæèöéçàùüñýûáó
+
+alt="Èõíèúê"
+ ^
+---->F5
+alt="Öêúâ"
+ ^
+---->E6
+</pre>
+
+</html>
+
+
+--Boundary_(ID_tldpu1b8SMKs0pXiY1Dv8g)--
+
diff --git a/velocity-engine-core/src/test/resources/templates/equality.vm b/velocity-engine-core/src/test/resources/templates/equality.vm
new file mode 100644
index 00000000..39e85d43
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/equality.vm
@@ -0,0 +1,31 @@
+#*
+
+@test equality.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($user = "jason")
+#set($login = false)
+#set($count = 5)
+
+#if ($user == "jason")
+ the user $user is logged in!
+#end
+
+
+#if ($count == 5)
+ the count is 5!
+#end
+
+#if ($login == false)
+ the user isn't logged in.
+#end
+
+#if ($count != 3)
+ \$count is not equal to 3
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/escape.vm b/velocity-engine-core/src/test/resources/templates/escape.vm
new file mode 100644
index 00000000..aedf8f42
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/escape.vm
@@ -0,0 +1,55 @@
+#*
+
+@test escape.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+\A
+
+\#set($woo = "bar")
+#set($woo = "bar")
+
+\$woo => $woo
+
+The following should print 'as is' :
+$f\oo
+\a
+"\r"
+
+Now, test the built in directives. Note that $foo isn't in the context :
+\#set($foo = $foo + 1)
+\#set(\$foo = $foo + 1)
+\#if($foo)
+\#if ( $foo )
+\#else
+\#end
+\#elseif(
+
+Now, a reference not in the context:
+\$foo -> $foo
+\#if($foo)
+\#if(\$foo)
+
+Put it in :
+#set($foo = 1)
+\$foo -> $foo
+\#if($foo)
+\#if(\$foo)
+
+This isn't in the context, so we get the full monty :
+ \$woobie.blagh()
+
+The following two are references :
+ \$provider.Title = $provider.Title
+ \$provider.getTitle() = $provider.getTitle()
+
+Now, pluggable directives:
+
+\#notadirective
+\#foreach
+
diff --git a/velocity-engine-core/src/test/resources/templates/escape2.vm b/velocity-engine-core/src/test/resources/templates/escape2.vm
new file mode 100644
index 00000000..1ba83a79
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/escape2.vm
@@ -0,0 +1,138 @@
+#*
+@test escape2.vm
+
+More interesting cases...
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+--- Schmoo ---
+
+These are not in the context, so they should render as they are here (schmoo).
+$foo
+\$foo
+\\$foo
+
+\#woogie
+\\#woogie
+\\\#woogie
+
+Now put $foo in the context :
+#set($foo = "bar")
+\$foo = $foo
+\\\$foo =\\$foo
+\\\\\$foo =\\\\$foo
+
+As we increase the number of \'s, we alternate renderings :
+$foo
+\$foo
+\\$foo
+\\\$foo
+\\\\$foo
+
+--- Pluggable Directives ----
+
+We are doing an \#include("test.txt"), starting with 0 '\' preceeding :
+
+#include("test.txt")
+\#include("test.txt")
+\\#include("test.txt")
+\\\#include("test.txt")
+\\\\#include("test.txt")
+
+Now, foreach is a PD. Escape the first one, and then not the second so it
+renders. The third and fourth examples show the single 'unpleasantry' about this. The \
+is only an escape when 'touching' VTL, otherwise, it's just schmoo.
+
+\#foreach(
+
+\\#foreach($a in $stringarray) $a \\#end
+
+\\#foreach($a in $stringarray) $a \ \\#end
+
+\\#foreach($a in $stringarray)$a\ \\#end
+
+
+--- Control Structures ----
+
+First should be escaped...
+\#if(true) hi \#end
+
+This isn't. Note then that it has to render the \\ as a \ because it's stuck to the VTL
+
+\\#if(true) hi \\#end
+
+\\#if(true) hi #end
+
+And so forth...
+\\\#if(true) hi \\\#end
+
+\\\\#if(true) hi \\\\#end
+
+And more...
+
+\#if(true)
+ hi
+\#else
+ there
+\#end
+
+\\#if(true)
+ hi
+\\#else
+ there
+\\#end
+
+\\\#if(true)
+ hi
+\\\#else
+ there
+\\\#end
+
+\\#if(false)
+ hi
+\\#elseif(true)
+ there
+\\#end
+
+\\\#if(false)
+ hi
+\\\#elseif(true)
+ there
+\\\#end
+
+## testing combinations like #$foo
+
+#$foo1
+\#$foo1
+#${foo1}
+\#$${foo1}
+#set($foo1 = "C0C0C0")
+#$foo1
+\#$foo1
+#${foo1}
+\#$${foo1}
+#\$${foo1}
+
+## and wacky stuff that are not references, but
+## because of the MORE tokens, get screwed up
+
+$(QUERY_STRING{forumid})
+\$(QUERY_STRING{forumid})
+\\$(QUERY_STRING{forumid})
+
+##
+## and just slashes....
+##
+
+\
+\\
+\\\
+\\\\
+\\\\\
+\\\\\\
+\\\\\\\
+\\\\\\\\
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-array.vm b/velocity-engine-core/src/test/resources/templates/foreach-array.vm
new file mode 100644
index 00000000..371004d6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-array.vm
@@ -0,0 +1,62 @@
+#*
+
+@test foreach-array.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+--Foreach with an array. 2 blank lines follow
+
+##foreach ($element in $provider.Array)
+
+<table>
+#foreach ($element in $stringarray)
+ <tr>
+ <td>This is $element and it is the $foreach.count item</td>
+ </tr>
+#end
+</table>
+
+--Foreach with a null array. 1 blank line follows
+
+<table>
+#foreach ($element in $woogiefoogie)
+ <tr>
+ <td>This is $element and it is the $foreach.count item</td>
+ </tr>
+#end
+</table>
+
+-- And when we declare the array in-template :
+
+#set($colors=["red","blue","green"])
+Choose among :
+#foreach( $color in $colors )
+ $color
+#end
+
+#set($bar= 'bar')
+#set($foo = [ 'a' ])
+#set($foo2 = [ $bar ])
+#set($foo2 = [$bar ])
+#set($foo2 = [ $bar])
+#set($foo2 = [ $bar] )
+#foreach( $i in $foo )
+ \$foo : $i
+#end
+
+#foreach($i in $foo2)
+ \$foo2 : $i
+#end
+
+-- Empty array
+#foreach( $i in [] )
+ really?!
+#else
+ empty
+#end
+
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-introspect.vm b/velocity-engine-core/src/test/resources/templates/foreach-introspect.vm
new file mode 100644
index 00000000..e242e0f2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-introspect.vm
@@ -0,0 +1,13 @@
+##
+## tests a difficult introspection problem -
+## the iterator is a private inner class
+##
+
+#set($array = [1..10])
+#set($it = $array.iterator())
+
+#foreach($num in [21..30])
+ $num $it.next()
+#end
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-map.vm b/velocity-engine-core/src/test/resources/templates/foreach-map.vm
new file mode 100644
index 00000000..c4ddf692
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-map.vm
@@ -0,0 +1,14 @@
+#**
+
+@test foreach-map.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#foreach ($element in $provider.Hashtable)
+ $element
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-method.vm b/velocity-engine-core/src/test/resources/templates/foreach-method.vm
new file mode 100644
index 00000000..ae0e4e12
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-method.vm
@@ -0,0 +1,16 @@
+#*
+
+@test foreach-method.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+Foreach with a method.
+
+#foreach ($element in $provider.getCustomers())
+ This is $element.
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-null-list.vm b/velocity-engine-core/src/test/resources/templates/foreach-null-list.vm
new file mode 100644
index 00000000..dbaacdc5
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-null-list.vm
@@ -0,0 +1,17 @@
+#*
+
+@test foreach-null-list.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+Foreach with a list that contains null.
+
+#foreach ($element in $nullList)
+ This is $element.
+ $foreach.count
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-type.vm b/velocity-engine-core/src/test/resources/templates/foreach-type.vm
new file mode 100644
index 00000000..6022884f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-type.vm
@@ -0,0 +1,76 @@
+#*
+
+@test foreach-type.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+Using a Object []
+
+#foreach( $i in $obarr)
+ $i
+#end
+
+#foreach( $i in [])
+ $i
+#else
+ empty
+#end
+
+Using a Map
+
+#foreach( $i in $map )
+ $i
+#end
+
+#foreach( $i in {} )
+ $i
+#else
+ this map was empty
+#end
+
+Using a Collection
+
+#foreach($i in $collection )
+ $i
+#end
+
+Using an Iterator
+
+#foreach($i in $iterator )
+ $i
+#end
+
+#set($emptyList = [])
+#foreach($i in $emptyList.iterator())
+ $i
+#else
+ empty
+#end
+
+Using an Enumeration
+
+#foreach($i in $enumerator)
+ $i
+#else
+ error
+#end
+
+
+Using an array of primitives
+
+#foreach( $i in $intarr )
+ $i
+#else
+ error
+#end
+
+#foreach( $i in $emptyarr )
+ $i
+#else
+ empty
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/foreach-variable.vm b/velocity-engine-core/src/test/resources/templates/foreach-variable.vm
new file mode 100644
index 00000000..3b6be489
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/foreach-variable.vm
@@ -0,0 +1,44 @@
+#*
+
+@test foreach-variable.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+Foreach with a variable.
+
+#foreach ($element in $list)
+ This is $element.
+ $foreach.count
+#end
+
+#foreach ($element in $list)
+ -- inner foreach --
+ #foreach ($element in $list)
+ This is $element.
+ $foreach.count
+ #end
+ -- inner foreach --
+
+ -- outer foreach --
+ This is $element.
+ $foreach.count
+ -- outer foreach --
+#end
+
+#foreach(${item} in ${list})
+$item ${item}
+#end
+
+#foreach(${item} in $list)
+$item ${item}
+#end
+
+#foreach($item in ${list})
+$item ${item}
+#end
+
diff --git a/velocity-engine-core/src/test/resources/templates/formal.vm b/velocity-engine-core/src/test/resources/templates/formal.vm
new file mode 100644
index 00000000..e6204dd2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/formal.vm
@@ -0,0 +1,46 @@
+#*
+
+@test formal.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+1. $!{provider.Title}
+
+2. $provider.Title
+
+3. $!provider.Title
+
+4. ${provider.Title}
+
+${provider.Title}${provider.Title}
+
+${provider.Title}MapBuilder
+
+${provider.Title}.map.${provider.Title}MapBuilder
+
+#set($this = "this")
+#set($that = "that")
+
+${this}${that}
+
+${provider.getTitle()}
+
+$provider.getHashtable().get( "key0")
+$provider.getHashtable().get("key1" )
+$provider.getHashtable().get( "key2" )
+$provider.getHashtable().get("floogie!")
+
+## curly wierdness
+
+{$provider.Title
+$provider.Title}
+{$provider.Title}
+$provider}.Title
+${provider}.Title
+$provider{.Title
+
diff --git a/velocity-engine-core/src/test/resources/templates/get.vm b/velocity-engine-core/src/test/resources/templates/get.vm
new file mode 100644
index 00000000..9dea7bf0
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/get.vm
@@ -0,0 +1,19 @@
+#*
+
+@test get.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+## Now the provider has been outfitted with
+## a get(key) method so it will just return
+## the key when asked for it.
+
+$provider.Muck
+$provider.Duck
+$provider.Truck
+
diff --git a/velocity-engine-core/src/test/resources/templates/if.vm b/velocity-engine-core/src/test/resources/templates/if.vm
new file mode 100644
index 00000000..7da8d6c1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/if.vm
@@ -0,0 +1,22 @@
+#*
+
+@test if.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#if (true)
+ this is true
+#end
+
+#if (false)
+ this is false
+#end
+
+#if ($foo)
+ this isn't defined yet.
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/ifstatement.vm b/velocity-engine-core/src/test/resources/templates/ifstatement.vm
new file mode 100644
index 00000000..b67cfa9e
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/ifstatement.vm
@@ -0,0 +1,79 @@
+#*
+
+@test ifstatement.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($foo = "woogie" )
+
+start :
+
+#if($foo)
+ right
+#end
+
+#if(!$foo)
+ wrong
+#else
+ right
+#end
+
+#if( !$foo )
+ wrong
+#else
+ right
+#end
+
+#if( ! $foo )
+ wrong
+#else
+ right
+#end
+
+#if( ! ! $foo )
+ right
+#end
+
+
+#if($bar)
+ wrong
+#else
+ right
+#end
+
+#if($bar)
+ wrong
+#elseif( $foo )
+ right
+#end
+
+#if( $bar )
+ wrong
+#elseif( $floogie )
+ wrong
+#elseif( $woppie )
+ wrong
+#else
+ right
+#end
+
+#if(!$bar)
+ right
+#end
+
+#if( !$bar)
+ right
+#end
+
+#if( !!$bar)
+ wrong
+#else
+ right
+#end
+
+done
diff --git a/velocity-engine-core/src/test/resources/templates/include.vm b/velocity-engine-core/src/test/resources/templates/include.vm
new file mode 100644
index 00000000..060fe29b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/include.vm
@@ -0,0 +1,17 @@
+#*
+
+@test include.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#include("include.vm" "include.vm")
+
+#set($foo = "subdir/test.txt")
+
+#include($foo)
+
diff --git a/velocity-engine-core/src/test/resources/templates/interpolation.vm b/velocity-engine-core/src/test/resources/templates/interpolation.vm
new file mode 100644
index 00000000..d426c86d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/interpolation.vm
@@ -0,0 +1,82 @@
+#*
+
+@test interpolation.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+Start with simple string interpolation :
+
+#set($name = "jason")
+$provider.concat("it will cost you $10.00", "")
+
+#set($image = "dog")
+$provider.concat("${image}.jpg", "")
+
+#set($foo_bar = "foobar")
+$provider.concat("${foo_bar}.jpg", "")
+
+#set($foo__bar = "foobar")
+$provider.concat("${foo__bar}.jpg", "")
+
+#set($one = 1)
+#set($two = 2)
+#set($three = 3)
+$provider.concat("${one}${two}${three}", "")
+$provider.concat("$one $two $three", "")
+
+How about a directive? Sure :
+
+#set($arr = ["a","b","c"])
+#set($foo = "#foreach($a in $arr) >$a< #end")
+
+$foo
+
+For our next trick, lets interpolate a.... VelociMacro!
+
+#macro( interpfoo )
+ Hi, I'm a VM!
+#end
+
+#set($ivm = "#interpfoo()")
+
+$ivm
+
+And now, for something completely different :
+
+#set($code = "#if(false) True #else False #end")
+
+$code
+
+Now, non interpolated stringlits :
+
+#set($a = "$code")
+#set($b = '$code')
+#set($c = '$!$\!code')
+
+$a
+$b
+$c
+
+Now, check comments within strings. double quotes they should be removed.
+Single quotes, they should be kept literal.
+
+#set($c1 = "test##test")
+#set($c2 = "test ##test")
+#set($c3 = "##test")
+#set($c4 = "test#* hello *#test")
+#set($c5 = 'test##test')
+#set($c6 = 'test#* hello *#test')
+
+$c1
+$c2
+$c3
+$c4
+$c5
+$c6
+
+-- end --
diff --git a/velocity-engine-core/src/test/resources/templates/logical.vm b/velocity-engine-core/src/test/resources/templates/logical.vm
new file mode 100644
index 00000000..0e636eb5
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/logical.vm
@@ -0,0 +1,468 @@
+#*
+
+@test logical.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($foo = 5)
+
+#if ($foo > 1)
+ \$foo is greater then 1
+#end
+
+#if ($foo < 10)
+ \$foo is less than 10
+#end
+
+#if ($foo >= 5)
+ \$foo is great than or equal to 5
+#end
+
+#if ($foo <= 5)
+ \$foo is less than or equal to 5
+#end
+
+#set($foo = false)
+
+#if (!($foo == true))
+ foo is false
+#end
+
+--
+
+#set ($foo = 49/2)
+#set ($bar = 10)
+#if ($foo > $bar)
+ \$foo is greater than \$bar
+#end
+
+#if ($foo >= $bar)
+ \$foo is greater than or equal to \$bar
+#end
+
+#if ($bar < $foo)
+ \$bar is less than \$foo
+#end
+
+#if ($bar <= $foo)
+ \$bar is less than or equal to \$foo
+#end
+
+--
+
+#set ($foo = 49/2)
+#set ($bar = 10)
+#if ($foo > $bar)
+ \$foo is greater than \$bar
+#end
+
+#if ($foo >= $bar)
+ \$foo is greater than or equal to \$bar
+#end
+
+#if ($bar < $foo)
+ \$bar is less than \$foo
+#end
+
+#if ($bar <= $foo)
+ \$bar is less than or equal to \$foo
+#end
+
+--
+
+#set ($foo = 3)
+#set ($bar = 4)
+#if ($foo == $foo)
+ \$foo is equal to \$foo
+#end
+
+#if ($foo != $bar)
+ \$foo is not equal to \$bar
+#end
+
+--
+
+#set ($foo = 888)
+#set ($bar = 1111)
+#set ($abc = $templatenumber1.AsNumber)
+#if ($templatenumber1 > $foo)
+ \$templatenumber1 is greater than \$foo
+#end
+
+#if ($templatenumber1 >= $foo)
+ \$templatenumber1 is greater than or equal to \$foo
+#end
+
+#if ($foo < $templatenumber1)
+ \$foo is less than \$templatenumber1
+#end
+
+#if ($foo <= $templatenumber1)
+ \$foo is less than or equal to \$templatenumber1
+#end
+
+--
+
+#if ($bar > $templatenumber1)
+ \$bar is greater than \$templatenumber1
+#end
+
+#if ($bar >= $templatenumber1)
+ \$bar is greater than or equal to \$templatenumber1
+#end
+
+#if ($templatenumber1 < $bar)
+ \$templatenumber1 is less than \$bar
+#end
+
+#if ($templatenumber1 <= $bar)
+ \$templatenumber1 is less than or equal to \$bar
+#end
+
+--
+
+#if ($abc == $templatenumber1)
+ \$abc is equal to \$templatenumber1
+#end
+
+#if ($templatenumber1 == $abc)
+ \$templatenumber1 is equal to \$abc
+#end
+
+#if ($bar != $templatenumber1)
+ \$bar is not equal to \$templatenumber1
+#end
+
+#if ($templatenumber1 != $bar)
+ \$templatenumber1 is not equal to \$bar
+#end
+
+--
+#set($t = true)
+#set($f = false)
+
+Logical OR :
+
+#if($t || $f)
+ right
+#else
+ wrong
+#end
+
+#if( !($f || $t) )
+ wrong
+#else
+ right
+#end
+
+#if( $null || $t )
+ right
+#else
+ wrong
+#end
+
+#if( $t || $null )
+ right
+#else
+ wrong
+#end
+
+#if( $f || $null)
+ wrong
+#else
+ right
+#end
+
+#if( $null || $null )
+ wrong
+#else
+ right
+#end
+
+Logical AND :
+
+#if( $t && $t)
+ right
+#else
+ wrong
+#end
+
+#if( $f && $f )
+ wrong
+#else
+ right
+#end
+
+#if( !($f && $f) )
+ right
+#else
+ wrong
+#end
+
+#if( $t && $f )
+ wrong
+#else
+ right
+#end
+
+#if( $t && $null )
+ wrong
+#else
+ right
+#end
+
+#if( $null && $t )
+ wrong
+#else
+ right
+#end
+
+#if( $f && $null )
+ wrong
+#else
+ right
+#end
+
+#if( !($null && $null) )
+ right
+#else
+ wrong
+#end
+
+----------
+equivalence
+-----------
+
+#set($int = 1)
+#set($str = "str")
+#set($bool = true)
+
+#if( $int == $str)
+wrong
+#else
+right
+#end
+
+#if( $int == 1 )
+right
+#else
+wrong
+#end
+
+#if ( $int == 2 )
+wrong
+#else
+right
+#end
+
+#if( $str == 2 )
+wrong
+#else
+right
+#end
+
+#if( $str == "str")
+right
+#else
+wrong
+#end
+
+#if( $str == $nonexistantreference )
+wrong
+#else
+right
+#end
+
+#if( $str == $bool )
+wrong
+#else
+right
+#end
+
+#if ($bool == true )
+right
+#else
+wrong
+#end
+
+#if( $bool == false )
+wrong
+#else
+right
+#end
+
+-----------
+comparisons
+-----------
+#set($int = 1)
+#set($str = "str")
+#set($bool = true)
+
+#if( $int > 0 )
+right
+#else
+wrong
+#end
+
+#if( $str > 0 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant > 0 )
+wrong
+#else
+right
+#end
+
+#if( $int >= 0 )
+right
+#else
+wrong
+#end
+
+#if( $str >= 0 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant >= 0 )
+wrong
+#else
+right
+#end
+
+#if( $int < 10 )
+right
+#else
+wrong
+#end
+
+#if( $str < 10 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant < 10 )
+wrong
+#else
+right
+#end
+
+#if( $int <= 10 )
+right
+#else
+wrong
+#end
+
+#if( $str <= 10 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant <= 10 )
+wrong
+#else
+right
+#end
+
+----------------------
+goofy but legal stuff
+----------------------
+#set($lala = ( false || true ) )
+Should equal true : $lala
+
+#set($fofo = ( true && true ) )
+Should equal true : $fofo
+
+#set($fofo = ( true && ( false || true ) ) )
+Should equal true : $fofo
+
+#set($fofo = ( ($t || $f) && $t))
+Should equal true : $fofo
+
+----------------------
+Compare String and StringBuffer
+----------------------
+#set($compClass = ($name == $name2))
+This should be true: $compClass
+
+#set($compClass2 = ($name == $name3))
+This should be false: $compClass2
+
+#set($compClass3 = ($name != $name3))
+This should be true: $compClass3
+
+#set($x = !true)
+
+#if($x == false)
+right
+#else
+wrong
+#end
+
+#set($y = !$x)
+
+#if($y == true)
+right
+#else
+wrong
+#end
+
+Test to see if we can do logical assignment from any expression
+
+#set($val = (3 == 3))
+#if($val == true)
+right
+#else
+wrong
+#end
+
+#set($val = (1 < 2))
+#if( $val == true)
+right
+#else
+wrong
+#end
+
+
+#set($val = (1 <= 2))
+#if( $val == true)
+right
+#else
+wrong
+#end
+
+
+#set($val = (7 > 2))
+#if( $val == true)
+right
+#else
+wrong
+#end
+
+#set($val = (7 >= 2))
+#if( $val == true)
+right
+#else
+wrong
+#end
+
+#set($val = ( 1 != 2))
+#if( $val == true)
+right
+#else
+wrong
+#end
+
+## check empty if statement is ok
+#if( true )#end
diff --git a/velocity-engine-core/src/test/resources/templates/logical2.vm b/velocity-engine-core/src/test/resources/templates/logical2.vm
new file mode 100644
index 00000000..b0c61e70
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/logical2.vm
@@ -0,0 +1,346 @@
+#*
+
+@test logical.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($foo = 5)
+
+#if ($foo gt 1)
+ \$foo is greater then 1
+#end
+
+#if ($foo lt 10)
+ \$foo is less than 10
+#end
+
+#if ($foo ge 5)
+ \$foo is great than or equal to 5
+#end
+
+#if ($foo le 5)
+ \$foo is less than or equal to 5
+#end
+
+#set($foo = false)
+
+#if ( not ($foo eq true))
+ foo is false
+#end
+
+--
+
+#set($t = true)
+#set($f = false)
+
+Logical OR :
+
+#if($t or $f)
+ right
+#else
+ wrong
+#end
+
+#if( not ($f or $t) )
+ wrong
+#else
+ right
+#end
+
+#if( $null or $t )
+ right
+#else
+ wrong
+#end
+
+#if( $t or $null )
+ right
+#else
+ wrong
+#end
+
+#if( $f or $null)
+ wrong
+#else
+ right
+#end
+
+#if( $null or $null )
+ wrong
+#else
+ right
+#end
+
+Logical AND :
+
+#if( $t and $t)
+ right
+#else
+ wrong
+#end
+
+#if( $f and $f )
+ wrong
+#else
+ right
+#end
+
+#if( not ($f and $f) )
+ right
+#else
+ wrong
+#end
+
+#if( $t and $f )
+ wrong
+#else
+ right
+#end
+
+#if( $t and $null )
+ wrong
+#else
+ right
+#end
+
+#if( $null and $t )
+ wrong
+#else
+ right
+#end
+
+#if( $f and $null )
+ wrong
+#else
+ right
+#end
+
+#if( not ($null and $null) )
+ right
+#else
+ wrong
+#end
+
+----------
+equivalence
+-----------
+
+#set($int = 1)
+#set($str = "str")
+#set($bool = true)
+
+#if( $int eq $str)
+wrong
+#else
+right
+#end
+
+#if( $int eq 1 )
+right
+#else
+wrong
+#end
+
+#if ( $int eq 2 )
+wrong
+#else
+right
+#end
+
+#if( $str eq 2 )
+wrong
+#else
+right
+#end
+
+#if( $str eq "str")
+right
+#else
+wrong
+#end
+
+#if( $str eq $nonexistantreference )
+wrong
+#else
+right
+#end
+
+#if( $str eq $bool )
+wrong
+#else
+right
+#end
+
+#if ($bool eq true )
+right
+#else
+wrong
+#end
+
+#if( $bool eq false )
+wrong
+#else
+right
+#end
+
+-----------
+comparisons
+-----------
+#set($int = 1)
+#set($str = "str")
+#set($bool = true)
+
+#if( $int gt 0 )
+right
+#else
+wrong
+#end
+
+#if( $str gt 0 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant gt 0 )
+wrong
+#else
+right
+#end
+
+#if( $int ge 0 )
+right
+#else
+wrong
+#end
+
+#if( $str ge 0 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant ge 0 )
+wrong
+#else
+right
+#end
+
+#if( $int lt 10 )
+right
+#else
+wrong
+#end
+
+#if( $str lt 10 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant lt 10 )
+wrong
+#else
+right
+#end
+
+#if( $int le 10 )
+right
+#else
+wrong
+#end
+
+#if( $str le 10 )
+wrong
+#else
+right
+#end
+
+#if( $nonexistant le 10 )
+wrong
+#else
+right
+#end
+
+----------------------
+goofy but legal stuff
+----------------------
+#set($lala = ( false or true ) )
+Should equal true : $lala
+
+#set($fofo = ( true and true ) )
+Should equal true : $fofo
+
+#set($fofo = ( true and ( false or true ) ) )
+Should equal true : $fofo
+
+#set($fofo = ( ($t or $f) and $t))
+Should equal true : $fofo
+
+
+#set($x = not true)
+
+#if($x eq false)
+right
+#else
+wrong
+#end
+
+#set($y = not $x)
+
+#if($y eq true)
+right
+#else
+wrong
+#end
+
+Test to see if we can do logical assignment from any expression
+
+#set($val = (3 eq 3))
+#if($val eq true)
+right
+#else
+wrong
+#end
+
+#set($val = (1 lt 2))
+#if( $val eq true)
+right
+#else
+wrong
+#end
+
+
+#set($val = (1 le 2))
+#if( $val eq true)
+right
+#else
+wrong
+#end
+
+
+#set($val = (7 gt 2))
+#if( $val eq true)
+right
+#else
+wrong
+#end
+
+#set($val = (7 ge 2))
+#if( $val eq true)
+right
+#else
+wrong
+#end
+
+#set($val = ( 1 ne 2))
+#if( $val eq true)
+right
+#else
+wrong
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/loop.vm b/velocity-engine-core/src/test/resources/templates/loop.vm
new file mode 100644
index 00000000..1c5cc780
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/loop.vm
@@ -0,0 +1,15 @@
+#*
+
+@test loop.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#foreach ($element in $list)
+ #set($foo = $provider.concat(["<", $element, ">"]))
+ $provider.concat("hello", $foo)
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/map.vm b/velocity-engine-core/src/test/resources/templates/map.vm
new file mode 100644
index 00000000..a58437e1
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/map.vm
@@ -0,0 +1,68 @@
+#*
+
+@test map.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+## This is a valid Map and Bar is a valid
+## element.
+
+$hashtable.Bar
+
+## test the new Map support
+
+#set( $hashtable.Foo = "foovalue")
+$hashtable.get("Foo")
+$hashtable.Foo
+
+#set( $hashmap.Foo = "foovalue")
+$hashmap.Foo
+$hashmap.get("Foo")
+
+
+##
+## test the support for the Map creation syntax
+##
+#set($key = 'key')
+#set($value = 'value')
+
+#set($mymap = { "a" : "aval", "bar" : "booboo", 'b' : 'bval', 1 : 2, $key : $value, 'hash' : $hashmap } )
+$mymap.put("bar", { "aa" : "aaa" })
+$mymap.a
+$mymap.get("a")
+$mymap.b
+$mymap.get('b')
+$mymap.get(1)
+$mymap.get($key)
+$mymap.hash.Foo
+$mymap.map.foo
+$mymap.bar
+
+##
+## test for empty map
+##
+
+#set($emptymap = {})
+$emptymap.size()
+
+#set($emptymap = { })
+$emptymap.size()
+
+##
+## test for values given with reference
+##
+
+#set($object = "aa")
+#set($mapz = {"a" : $object, "b" : "bb" })
+#set($mapx = {"a" : "bb", "b" : $object })
+#set($mapy = {"key": $object})
+$mapz.a
+$mapz.b
+$mapx.a
+$mapx.b
+$mapy.key
diff --git a/velocity-engine-core/src/test/resources/templates/math.vm b/velocity-engine-core/src/test/resources/templates/math.vm
new file mode 100644
index 00000000..e0e8e816
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/math.vm
@@ -0,0 +1,71 @@
+#*
+
+@test escape.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+Tests the basic integer math capabilities.
+
+*#
+
+Addition and subtraction :
+#set($foo = 1)
+#set($foo = $foo + 1)
+1 + 1 = $foo
+$foo - 1 = #set($foo = $foo - 1 )$foo
+
+Multiplication, division, and modulus :
+#set($bar = 5)
+#set($rem = $bar % 2)
+#set($rem2 = $bar % 0)
+#set($rem3 = 7%2)
+$bar % 2 = $rem
+$bar % 0 = $rem2
+7 % 2 = $rem3
+$bar / 2 = #set($rem = $bar / 2 )$rem
+$bar / 0 = #set($rem4 = $bar / 0 )$rem4
+$bar * 2 = #set($rem = $bar * 2 )$rem
+
+$bar * -1 = #set($rem = $bar * -1)$rem
+$bar * -2 = #set($rem = $bar *-2)$rem
+$bar * -2 = #set($rem = -2*$bar)$rem
+
+And now null nodes to make sure it doesn't throw an NPE :
+#set($flargh=$woogie + $wabbie)
+
+Some test for the new number-handling
+$int1 + $long1 = #set ($rem = $int1 + $long1)$rem
+$int1 - $long1 = #set ($rem = $int1 - $long1)$rem
+$int1 * $long1 = #set ($rem = $int1 * $long1)$rem
+$int1 / $long1 = #set ($rem = $int1 / $long1)$rem
+$int1 % $long1 = #set ($rem = $int1 % $long1)$rem
+
+$int1 + $float1 = #set ($rem = $int1 + $float1)$rem
+$int1 - $float1 = #set ($rem = $int1 - $float1)$rem
+$int1 * $float1 = #set ($rem = $int1 * $float1)$rem
+$int1 / $float1 = #set ($rem = $int1 / $float1)$rem
+
+This checks that an object implementing TemplateNumber can be used in arithmetic
+$int1 + $templatenumber1.AsNumber = #set ($rem = $int1 + $templatenumber1)$rem
+$int1 - $templatenumber1.AsNumber = #set ($rem = $int1 - $templatenumber1)$rem
+$int1 * $templatenumber1.AsNumber = #set ($rem = $int1 * $templatenumber1)$rem
+$int1 / $templatenumber1.AsNumber = #set ($rem = $int1 / $templatenumber1)$rem
+
+Test integer division
+5 / 2 = #set($result = 5 / 2)$result
+
+Test decimal division
+5 / 2.0 = #set($result = 5 / 2.0)$result
+5.0 / 2 = #set($result = 5.0 / 2)$result
+
+Unary negate
+#set($five = 5)
+#set($a = 0 - $five)
+#set($b = 0-$five)
+#set($c = - $five)
+#set($d = -$five)
+#set($e =-$five)
+-5 = $a = $b = $c = $d = $e
diff --git a/velocity-engine-core/src/test/resources/templates/mergethis.vm b/velocity-engine-core/src/test/resources/templates/mergethis.vm
new file mode 100644
index 00000000..e88fa7f4
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/mergethis.vm
@@ -0,0 +1 @@
+#macro(floog $foo $bar)Hello $foo! Nice $bar!#{end}#floog($name $Floog) \ No newline at end of file
diff --git a/velocity-engine-core/src/test/resources/templates/method.vm b/velocity-engine-core/src/test/resources/templates/method.vm
new file mode 100644
index 00000000..2659284f
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/method.vm
@@ -0,0 +1,13 @@
+#*
+
+@test method.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+$provider.concat(["I", "am", "a", $provider.concat(["running", "man"])])
+
diff --git a/velocity-engine-core/src/test/resources/templates/newline.vm b/velocity-engine-core/src/test/resources/templates/newline.vm
new file mode 100644
index 00000000..8835bf37
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/newline.vm
@@ -0,0 +1,29 @@
+#macro(woogie $a)
+This is $a
+#end
+
+#woogie(
+"a string"
+)
+
+#macro(
+ name
+ $thing
+)
+Call $thing
+#end
+
+#name("woog")
+
+#set($foo =
+"hello there")
+
+$foo
+
+#set($list =
+ [1..10])
+
+#foreach($item
+in $list)
+$item
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/parse.vm b/velocity-engine-core/src/test/resources/templates/parse.vm
new file mode 100644
index 00000000..1e591fd3
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/parse.vm
@@ -0,0 +1,18 @@
+#*
+
+@test parse.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+Test the \#parse pluggable directive
+
+Now calling parse1.vm :
+---
+#parse("parse1.vm")
+---
+all done!
diff --git a/velocity-engine-core/src/test/resources/templates/parse1.vm b/velocity-engine-core/src/test/resources/templates/parse1.vm
new file mode 100644
index 00000000..58164e7d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/parse1.vm
@@ -0,0 +1,22 @@
+#*
+
+@test parse1.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+This is content from parse1.vm
+
+#foreach($a in $stringarray )
+ Hello $a.
+#end
+
+Now using a reference to get the next one :
+#set($foo = "parse2.vm")
+ --
+#parse($foo)
+ --
+done with parse1.vm
diff --git a/velocity-engine-core/src/test/resources/templates/parse2.vm b/velocity-engine-core/src/test/resources/templates/parse2.vm
new file mode 100644
index 00000000..f6d0a126
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/parse2.vm
@@ -0,0 +1,15 @@
+#*
+
+@test parse2.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+This is parse2.vm.
+
+#if (true)
+ This is from a true \#if.
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/pedantic.vm b/velocity-engine-core/src/test/resources/templates/pedantic.vm
new file mode 100644
index 00000000..573c5b32
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/pedantic.vm
@@ -0,0 +1,101 @@
+#*
+
+@test pedantic.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+#set( $pedantic = "pedantic")
+
+This is a test of the new #if($pedantic)$pedantic#end mode.
+
+There are a few things you can do in #if($foobar) GOOGLE! #else$pedantic#end mode.
+
+Like get the spacing between things #foreach($a in $stringarray)$a#end to be really, really tight.
+
+Further, it now binds any \n to the control structures, taking them out of the output.
+The hope that this is What You Expect.
+So...
+
+--
+#if ($pedantic)
+$pedantic
+#end
+--
+
+should come out looking like
+
+--
+pedantic
+--
+
+But pay attention to what follows the \#end statement :
+
+1) First, follow with 'stuff' (not sure why you want to do this... but anway...)
+
+--
+#if ($pedantic)
+$pedantic
+#end woogie!
+--
+
+should be
+
+--
+pedantic
+ woogie!
+--
+
+2) Whitespace will be eaten if there is a following newline
+
+--
+#if ($pedantic)
+$pedantic
+#end
+--
+
+should be
+
+--
+pedantic
+--
+
+
+-- INLINE STUFF ---
+
+1) respect spaces in the block
+>#foreach($a in $stringarray)$a#end<
+>#foreach($a in $stringarray) $a#end<
+>#foreach($a in $stringarray)$a #end<
+>#foreach($a in $stringarray) $a #end<
+
+2) set statement has no output, incuding preceeding whitespace
+#foreach($a in $stringarray)
+ #set($b = $a)
+ $a is $b
+#end
+
+ public void foo( String lala )
+ {
+#foreach($a in $stringarray)
+ #set($b = $a)
+ System.out.println("$b");
+#end
+ }
+
+ public void foo( String lala )
+ {
+ #foreach($a in $stringarray)
+ #set($b = $a)
+ System.out.println("$b");
+ #end
+ }
+
+Inline set statement :
+
+Here are the prices :#set( $arr = ["$10.24","$15.32","$12.15"] ) #foreach($a in $arr) $a #end
+
+
diff --git a/velocity-engine-core/src/test/resources/templates/quotes.vm b/velocity-engine-core/src/test/resources/templates/quotes.vm
new file mode 100644
index 00000000..88a84b72
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/quotes.vm
@@ -0,0 +1,21 @@
+#*
+
+@test quotes.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($this = "that")
+
+#if (true)
+ "what is $this"
+#end
+
+<input type="checkbox" name="$name" #if($checked)checked="1"#end/>
+
+<input type="checkbox" name="$hashtable.Bar" #if($checked)checked="1"#end/>
+
diff --git a/velocity-engine-core/src/test/resources/templates/range.vm b/velocity-engine-core/src/test/resources/templates/range.vm
new file mode 100644
index 00000000..4fdbb021
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/range.vm
@@ -0,0 +1,93 @@
+#*
+
+@test range.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+Tests the range operator [n..m]
+
+*#
+[1..5]
+#foreach($i in [1..5]) $i #end
+
+-----
+[0..0]
+#foreach($i in [0..0]) $i #end
+
+-----
+[-4..-5]
+#foreach($i in [-4..-5]) $i #end
+
+-----
+[ 1 .. 5 ]
+#foreach($i in [ 1 .. 5 ]) $i #end
+
+-----
+[5..1]
+#foreach($i in [5..1]) $i #end
+
+-----
+[-5..5]
+#foreach($i in [-5..5]) $i #end
+
+-----
+[5..-5]
+#foreach($i in [5..-5]) $i #end
+
+-----
+#set($a = 1)
+#set($b = 5)
+refs \$a=$a \$b=$b [\$a..\$b]
+#foreach($i in [$a..$b]) $i #end
+
+-----
+#set($a = 1)
+#set($b = "5")
+refs \$a=$a \$b="$b" [\$a.."\$b"]
+#foreach($i in [$a..$b]) $i #end
+
+-----
+[\$a.. 7]
+#foreach($i in [$a.. 7]) $i #end
+
+-----
+[-7 ..\$a]
+#foreach($i in [-7 ..$a]) $i #end
+
+-----
+[ -7 ..\$a]
+#foreach($i in[ -7 ..$a]) $i #end
+
+------
+#set($foo = [0..5])
+setting in \$foo -> [0..5] :
+#foreach($i in $foo )$i #end
+
+----
+
+Now some use-case examples. Suppose we want a table to have 10 rows
+
+#set($arr = ["a","b","c"])
+
+<table>
+#foreach($i in $arr)
+<tr><td>$i</td></tr>
+#end
+#foreach($i in [4..10])
+<tr><td>&nbsp;</td></tr>
+#end
+</table>
+
+----
+Wide loop should not OOM
+#foreach($i in [0..67000000])
+#if($i == 100)
+that's enough
+#break
+#end
+#end
+
+=done=
diff --git a/velocity-engine-core/src/test/resources/templates/reference.vm b/velocity-engine-core/src/test/resources/templates/reference.vm
new file mode 100644
index 00000000..fdebe147
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/reference.vm
@@ -0,0 +1,104 @@
+#*
+
+@test reference.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+#set($_foo = "bar")
+$_foo
+#if ($_foo.equals("bar"))
+ \$_foo equals "bar" : $_foo
+#end
+
+Late introspection :
+
+$vector.firstElement().length()
+
+
+More stupid reference escaping ...
+
+When it does exist in the context :
+
+#set($foo = "foo")
+$foo
+\$foo
+\$!foo
+$\!foo
+$\\!foo
+\$\!foo
+
+And when it doesn't :
+
+$bar
+\$bar
+
+\$!bar (because it's just text...)
+
+$\!foo
+$\\!foo
+$\\\!foo
+$\\\\!foo
+\\$\!foo
+
+Misc tests :
+
+[$foo.bar]
+
+
+Test lower case property names
+
+$provider.Title
+$provider.title
+
+#foreach($i in $provider.vector)
+ $i
+#end
+#foreach($i in $provider.Vector)
+ $i
+#end
+
+Now test if we can use lowercase for propertes in set
+#set($oldtitle = $provider.title)
+Was : $oldtitle
+#set($provider.title = "geir")
+Now : $provider.title
+#set($provider.title = $oldtitle)
+Back : $provider.title
+
+Test what was a bug :
+
+#set($b = 'boy')
+#set($c = 'cat')
+$b${c}.java
+${b}${c}.java
+
+
+More tests :
+
+$provider.title
+$$provider.title
+#$provider.title
+
+$foo.bar#if( $foo ) ($bar) #end
+
+
+Test boolean introspection isFoo()
+
+#if( $boolobj.boolean == true )
+ Correct
+#else
+ Wrong
+#end
+
+
+#if ( $boolobj.notboolean == true )
+ Wrong
+#else
+ Correct
+#end
+
diff --git a/velocity-engine-core/src/test/resources/templates/sample.vm b/velocity-engine-core/src/test/resources/templates/sample.vm
new file mode 100644
index 00000000..7bec2676
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/sample.vm
@@ -0,0 +1,32 @@
+#*
+
+@test sample.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+<html>
+<head><title>Sample velocity page</title></head>
+<body bgcolor="#ffffff">
+<center>
+
+<h2>Hello from velocity!</h2>
+<i>Here's the list of people</i>
+<table cellspacing="0" cellpadding="5" widht="100%">
+ <tr>
+ <td bgcolor="#eeeeee" align="center">
+ Names
+ </td>
+ </tr>
+ #foreach ($name in $provider.Customers)
+ <tr>
+ <td bgcolor="#eeeeee">$name</td>
+ </tr>
+ #end
+</table>
+</center>
+</html>
diff --git a/velocity-engine-core/src/test/resources/templates/settest.vm b/velocity-engine-core/src/test/resources/templates/settest.vm
new file mode 100644
index 00000000..7db1652d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/settest.vm
@@ -0,0 +1,22 @@
+*
+
+@test settest.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+Tests #set parsing funkyness
+*#
+
+#macro( setthing $a )
+ I am setthing : $a
+#end
+
+#set($foo = "bar")
+#set ($foofoo = "barbar")
+
+#setthing( $foo )
+
+SessionBean#setSessionContext
diff --git a/velocity-engine-core/src/test/resources/templates/shorthand.vm b/velocity-engine-core/src/test/resources/templates/shorthand.vm
new file mode 100644
index 00000000..53f18ba2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/shorthand.vm
@@ -0,0 +1 @@
+<input type="text" name="email" value="$!email">
diff --git a/velocity-engine-core/src/test/resources/templates/stop1.vm b/velocity-engine-core/src/test/resources/templates/stop1.vm
new file mode 100644
index 00000000..d3501396
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/stop1.vm
@@ -0,0 +1,19 @@
+This page checks the stop directive in the main body
+
+#stop
+
+this should not render
+
+check reference
+#set($a = 10)
+$a
+
+check method call
+$a.toString()
+
+check escaped directive
+\#if
+
+check escaped reference
+\$a
+
diff --git a/velocity-engine-core/src/test/resources/templates/stop2.vm b/velocity-engine-core/src/test/resources/templates/stop2.vm
new file mode 100644
index 00000000..9a530ce2
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/stop2.vm
@@ -0,0 +1,31 @@
+This page checks the stop directive inside an if statement
+
+#if(false)
+
+ #stop
+
+#end
+
+this should render
+
+#if(true)
+
+#stop
+
+#end
+
+this should not render
+
+check reference
+#set($a = 10)
+$a
+
+check method call
+$a.toString()
+
+check escaped directive
+\#if
+
+check escaped reference
+\$a
+
diff --git a/velocity-engine-core/src/test/resources/templates/stop3-include.vm b/velocity-engine-core/src/test/resources/templates/stop3-include.vm
new file mode 100644
index 00000000..b0584dd9
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/stop3-include.vm
@@ -0,0 +1,3 @@
+A line from stop3-include.vm
+#stop
+This line should not be seen.
diff --git a/velocity-engine-core/src/test/resources/templates/stop3.vm b/velocity-engine-core/src/test/resources/templates/stop3.vm
new file mode 100644
index 00000000..bcc5d862
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/stop3.vm
@@ -0,0 +1,8 @@
+This test checks the stop directive when included from a parse directive.
+
+#set($foo = "stop3-include.vm")
+Foo is: $foo
+#parse("$foo")
+Since the template issued a stop, this line should not be visible.
+#parse("$foo")
+
diff --git a/velocity-engine-core/src/test/resources/templates/string.vm b/velocity-engine-core/src/test/resources/templates/string.vm
new file mode 100644
index 00000000..cf95b5ba
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/string.vm
@@ -0,0 +1,51 @@
+## now lets try some string concatenation
+
+#set($stringy = "This is a very long string"
+ + " that we are breaking up into multiple"
+ + " lines for testing."
+)
+$stringy
+
+#set($stringy = "This is a string. The number 2 = " + 2)
+$stringy
+
+#set($three = 3)
+#set($stringy = "This is a string."
++ " The value = "
++ $three
+)
+$stringy
+
+#set($haiku =
+"
+ New language feature
+allows newlines in the strings
+ might make some happy"
+)
+$haiku
+
+#set($shape =
+"
+V V EEEEE L OOOOO CCCCC I TTTTT Y Y
+ V V E L O O C I T Y Y
+ V V EEE L O O C I T Y
+ VV E L O O C I T Y
+ V EEEEE LLLL OOOOO CCCCC I T Y
+"
+)
+$shape
+
+#set($shape =
+'
+RRRRR OOOOO CCCCC K K SSSSS
+R R O O C K K S
+RRRR O O C KK SSSS
+R R O O C K K S
+R R O O C K K S
+R R OOOOO CCCCC K K SSSS
+'
+)
+$shape
+
+## test array to string
+$intarr
diff --git a/velocity-engine-core/src/test/resources/templates/subclass.vm b/velocity-engine-core/src/test/resources/templates/subclass.vm
new file mode 100644
index 00000000..22e9da12
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/subclass.vm
@@ -0,0 +1,26 @@
+#**
+
+@test subclass.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+## This is our base class.
+#set($person = $provider.Person)
+
+## This is a subclass of Person
+#set($child = $provider.Child)
+
+#*
+
+showPerson takes a person object, so check to
+see that subclasses of Person work too!
+
+*#
+
+$provider.showPerson($person)
+$provider.showPerson($child)
diff --git a/velocity-engine-core/src/test/resources/templates/subdir/test.txt b/velocity-engine-core/src/test/resources/templates/subdir/test.txt
new file mode 100644
index 00000000..e11d1caa
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/subdir/test.txt
@@ -0,0 +1,2 @@
+This is included text!
+
diff --git a/velocity-engine-core/src/test/resources/templates/templates.properties b/velocity-engine-core/src/test/resources/templates/templates.properties
new file mode 100644
index 00000000..a9759251
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/templates.properties
@@ -0,0 +1,65 @@
+# 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.
+
+test.template.1 = arithmetic
+test.template.2 = array
+test.template.3 = block
+test.template.4 = comment
+test.template.5 = equality
+test.template.6 = escape
+test.template.7 = foreach-array
+test.template.8 = foreach-method
+test.template.9 = foreach-variable
+test.template.10 = formal
+test.template.11 = if
+test.template.12 = logical
+test.template.13 = loop
+test.template.14 = method
+test.template.15 = quotes
+test.template.16 = sample
+test.template.17 = shorthand
+test.template.18 = test
+test.template.19 = diabolical
+test.template.20 = pedantic
+test.template.21 = subclass
+test.template.22 = foreach-map
+test.template.23 = include
+test.template.24 = escape2
+test.template.25 = parse
+test.template.26 = velocimacro
+test.template.27 = reference
+test.template.28 = interpolation
+test.template.29 = vm_test1
+test.template.30 = map
+test.template.31 = ifstatement
+test.template.32 = math
+test.template.33 = range
+test.template.34 = get
+test.template.35 = velocimacro2
+test.template.36 = foreach-type
+test.template.37 = foreach-introspect
+test.template.38 = settest
+test.template.39 = newline
+test.template.40 = logical2
+test.template.41 = string
+test.template.42 = stop1
+test.template.43 = stop2
+test.template.44 = foreach-null-list
+test.template.45 = curly-directive
+test.template.46 = comment-eof
+test.template.47 = commas
+test.template.48 = stop3
diff --git a/velocity-engine-core/src/test/resources/templates/test.txt b/velocity-engine-core/src/test/resources/templates/test.txt
new file mode 100644
index 00000000..3b04478b
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/test.txt
@@ -0,0 +1 @@
+--text--
diff --git a/velocity-engine-core/src/test/resources/templates/test.vm b/velocity-engine-core/src/test/resources/templates/test.vm
new file mode 100644
index 00000000..d4482209
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/test.vm
@@ -0,0 +1,221 @@
+#*
+
+@test test.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+
+<html>
+<body>
+
+$name
+
+#if ($customer)
+ $customer.Name
+#end
+
+
+## this is a comment.
+
+#if ($customer)
+ this is the first line
+#end
+
+## This is the test bed.
+
+this is testing for wild loose commas , ,
+
+$100
+
+
+#set($foo = "bar")
+
+This is the $foo way.
+
+#if ($foo)
+ This is $bar.
+#elseif ($bar)
+ This is the first elseif!
+#elseif ($foo)
+ This is the second elseif!
+#else
+ This is the else
+#end
+
+#if ($foo)
+ This is the if.
+#else
+ This is the else.
+#end
+
+
+\#set \$foo = "bar"
+
+\$foo => $foo
+\$foo; => $foo;
+\$foo. => $foo.
+\$foo.. => $foo..
+\$foo/ => $foo/
+\$foo" => $foo"
+\$foo\ => $foo\
+\$foo< => $foo<
+\$foo- => $foo-
+\$fooo+ => $fooo+
+\$foo-x => $foo-x
+\$foo$ => $foo$
+
+
+
+#set($iam_cool = "jon")
+$iam_cool
+$!nada nothing here
+
+function preload(imgObj,imgSrc)
+{
+ if (document.images)
+ {
+ eval(imgObj+' = new Image()')
+ eval(imgObj+'.src = "'+imgSrc+'"')
+ }
+}
+
+function changeImage(layer,imgName,imgObj)
+{
+ if (document.images)
+ {
+ if (document.layers && layer!=null) eval('document.'+layer+'.document.images["'+imgName+'"].src = '+imgObj+'.src')
+ else document.images[imgName].src = eval(imgObj+".src")
+ }
+}
+
+#if ($javascript)
+ function changeImage(layer,imgName,imgObj)
+ {
+ if (document.images)
+ {
+ if (document.layers && layer!=null) eval('document.'+layer+'.document.images["'+imgName+'"].src = '+imgObj+'.src')
+ else document.images[imgName].src = eval(imgObj+".src")
+ }
+ }
+#end
+
+<!-- This is an HTML comment -->
+
+
+$provider2.Title
+
+#set($a = "x")
+$a
+
+#set($b = $a)
+$b
+
+<input type="text" name="email" value="">
+<input type="text" name="email" value="$!schmarg">
+
+
+#set($c = $provider.Title)
+$c
+
+
+#set($d = $provider.getTitle())
+$d
+
+
+#set($provider.Title = "crocodile hunter!")
+
+
+
+$provider.Title
+
+
+
+<!-- look here -->
+
+#set($provider.Title = $d)
+$provider.Title
+
+
+
+#set($provider.Title = $provider.Name)
+$provider.Title
+
+
+#set($provider.Title = $provider.getName())
+$provider.Title
+
+
+
+#set($a = true)
+
+#set($b = false)
+
+#set($provider.State = true)
+
+#set($provider.State = false)
+
+
+
+#if ($provider.StateTrue)
+ This is a property that returns a boolean
+ value of true.
+#end
+
+#if (true)
+ This expression is always (true).
+#end
+
+
+Foreach with a variable.
+
+#foreach ($element in $list)
+ This is $element.
+#end
+
+Foreach with an array.
+
+<table>
+#foreach ($element in $provider.Array)
+ <tr>
+ <td>This is $element</td>
+ </tr>
+#end
+</table>
+
+
+#foreach ($element in $provider.Vector)
+ This is the $element.
+#end
+
+
+Foreach with a method.
+
+#foreach ($element in $provider.getCustomers())
+ This is $element.
+#end
+
+$10.00
+
+"this is great"
+
+(this is also great)
+
+This is the \#stuff and this
+is the way \#to \#go.
+
+this = that
+
+I am a $provider.getTitle().
+
+#if ($provider.theAPLRules())
+ Yes the APL rules!
+#else
+ It still rules!
+#end
+
+</body>
+</html>
diff --git a/velocity-engine-core/src/test/resources/templates/testCase644.vm b/velocity-engine-core/src/test/resources/templates/testCase644.vm
new file mode 100644
index 00000000..03141762
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/testCase644.vm
@@ -0,0 +1,20 @@
+
+#macro(arrayError)
+ #set($foo = [])
+ $foo.get(2)
+#end
+
+#macro(nullMethod)
+ #set($foo = $NULL)
+ $foo.bar
+#end
+
+#macro(badRef)
+ $bar
+#end
+
+#macro(forloop)
+ #set($val = 1)
+ #foreach($i in $val)
+ #end
+#end
diff --git a/velocity-engine-core/src/test/resources/templates/velocimacro.vm b/velocity-engine-core/src/test/resources/templates/velocimacro.vm
new file mode 100644
index 00000000..39c2b20d
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/velocimacro.vm
@@ -0,0 +1,88 @@
+#**
+
+@test velocimacro.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+## inline VM : shows how a tablerow might be generated
+
+#macro( tablerow $array $color )
+#foreach( $element in $array )
+ <tr><tdi bgcolor=$color>$element</td></tr>
+#end
+#end
+
+Now, use the \#quietnull example from the global library VM_global_library.vm :
+Now, there should be nothing in the brackets : >#quietnull($nada)<
+
+#set($foo = "hello!")
+Where there should be something here : >#quietnull($foo)<
+
+#set($arr = ["$10.24","$15.32","$12.15"])
+#set($col = "blue")
+
+<table>
+#tablerow( $arr $col)
+</table>
+
+
+Further tests. The following VMs and non-VM should be properly escaped :
+\#tablerow
+\#quietnull
+\#notavm
+>\\#quietnull($nada)<
+
+Now try to define a new quietnull VM :
+#macro( quietnull $a )
+ QuietNull : $a
+#end
+
+It should have been rejected, as the default is to not let inlines override existing, and there
+should be a message in velocity.log.
+Therefore it should still function normally :
+>#quietnull($foo)<
+>#quietnull($nada)<
+
+We should be able to use argless VMs (and directives....)
+#macro( noargs )
+Hello! I have no args!
+#end
+
+#noargs()
+
+
+And there was a bug where I wasn't getting the params right for the use-instance :
+
+#macro( showarg $i )
+Arg :>$i<
+#end
+
+#showarg( $jdom.getRootElement().getChild("properties").getChild("author").getTextTrim() )
+
+String literals should work as you expect :
+#showarg( "stringliteral")
+
+
+Test boolean args :
+
+#testbool(true)
+#testbool(false)
+
+Test map args :
+
+#macro(showmap $map $key)
+$map.get($key)
+#end
+
+#set($map = {"a":"aval", "b":"bval" } )
+#showmap($map "a")
+#showmap($map "b")
+
+#showmap({"a":"avalinline", "b":"bvalinline"} "a")
+#showmap({"a":"avalinline", "b":"bvalinline"} "b")
+
+- Another fine Velocity Production -
diff --git a/velocity-engine-core/src/test/resources/templates/velocimacro2.vm b/velocity-engine-core/src/test/resources/templates/velocimacro2.vm
new file mode 100644
index 00000000..e1577616
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/velocimacro2.vm
@@ -0,0 +1,91 @@
+#**
+
+@test velocimacro2.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+*#
+#macro( foo $a )
+ Hello from foo : $a
+#end
+
+#macro( foo2 $a )
+ Hello from foo2 : $a
+#end
+
+#macro( foo_two $a )
+ Hello from foo_two : $a
+#end
+
+#foo( "hi" )
+#foo2( "hi" )
+#foo_two( "hi" )
+
+
+#foo( $notincontext )
+#foo( $notincontext.getThing() )
+
+#macro( tester $a )
+ #if($a)
+ $a : yes
+ #else
+ $a : no
+ #end
+#end
+
+##
+## test to see if we can print these
+## as schmoo
+##
+
+#tester( $notincontext )
+#tester( $notincontext.woogie() )
+
+#set($foo = "bar")
+
+#tester($foo)
+
+#foo2( ${foo} )
+
+#macro( poundthis $truth )
+ #if ($truth )
+ <td align=center class=v10><b>#</b></td>
+ <td align=center class=v10><b> # </b></td>
+ <td align=center class=v10><b>\#</b></td>
+ #end
+#end
+
+#poundthis( true )
+
+##
+## test for bug reported when stringlit changed to on-init parsing
+##
+
+#macro( blorp $bar ) $bar #end
+#macro( schlorp $i )#blorp( "hi $i" ) #end
+
+#schlorp("victor")
+
+
+##
+## test all directive arg types
+##
+
+#macro(dirarg $a)
+>$a<
+#end
+
+#set($ref = 1)
+
+#dirarg(1)
+#dirarg(true)
+#dirarg(false)
+#dirarg("hello")
+#dirarg('hello')
+#dirarg($ref)
+#dirarg([1..10])
+#dirarg(['a','b',$ref])
+
diff --git a/velocity-engine-core/src/test/resources/templates/vm_test1.vm b/velocity-engine-core/src/test/resources/templates/vm_test1.vm
new file mode 100644
index 00000000..31f336a6
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/vm_test1.vm
@@ -0,0 +1,16 @@
+#**
+
+@test vm_test1.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+Tests VM recursion. In our VM library, we have a VM callrecurse()
+which calls recurse(). The point is to have the global def of
+recurse() called since we don't define it here.
+
+*#
+
+#callrecurse()
diff --git a/velocity-engine-core/src/test/resources/templates/vm_test2.vm b/velocity-engine-core/src/test/resources/templates/vm_test2.vm
new file mode 100644
index 00000000..ad3db649
--- /dev/null
+++ b/velocity-engine-core/src/test/resources/templates/vm_test2.vm
@@ -0,0 +1,24 @@
+#**
+
+@test vm_test2.vm
+
+This template is used for Velocity regression testing.
+If you alter this template make sure you change the
+corresponding comparison file so that the regression
+test doesn't fail incorrectly.
+
+Tests VM recursion *and* the local template namespace feature.
+This version of recurse should override the global version
+when called by the global VM callrecurse()
+*#
+#macro( recurse $a )
+ local recurse $a
+ #set( $a = $a - 1)
+ #if ($a > 0)
+ #recurse( $a )
+ #end
+#end
+
+#set($count = 5)
+
+#callrecurse()
diff --git a/velocity-engine-examples/README.md b/velocity-engine-examples/README.md
new file mode 100644
index 00000000..6ea1d51d
--- /dev/null
+++ b/velocity-engine-examples/README.md
@@ -0,0 +1,83 @@
+# Welcome to Velocity!
+
+This package contains a few examples to help get you started.
+
+As always, the if you have any questions :
+
+1. Make sure you followed any directions :-) (did you build
+ everything?)
+
+2. Review documentation included in this package, or online at
+ [https://velocity.apache.org/](https://velocity.apache.org/)
+
+3. Ask on the [velocity-user list](https://velocity.apache.org/contact.html#user).
+ This is a great source of support information. To join, read the
+ [contact page](https://velocity.apache.org/contact.html) and then follow the links to join the lists.
+
+## Building from sources
+
+To build these examples from Velocity sources, please refer to the [Velocity build page](https://velocity.apache.org/engine/${project.version}/build.html). The build will generate the zip archive target/${project.build.finalName}-pkg.zip containing all the examples.
+
+The archive contains a `build.sh` script which you can use to re-build the examples after tweaking the sources in the `src` directory.
+
+## Running the examples
+
+Once you have downloaded or sucessfully built the ${project.build.finalName}-pkg.zip downloaded package, unzip it in the location of your choice and change to the ${project.build.finalName} directory.
+
+Note for Windows users: the shell scripts used to running the examples are meant for linux or BSD, but can easily be adapted as batch files. all they do is build the classpath from the jars in the lib/ directory, then invoke Java on the main class with the adequate arguments.
+
+Finally, note that more examples of using Velocity can be found in the velocity-tools subject. Of special note is the VelocityViewServlet, a quick and easy way to build a web application that uses Velocity.
+
+ https://velocity.apache.org/tools/
+
+# Velocity Examples
+
+## Application Example #1
+
+This simple example shows how to use the Velocity Template Engine
+in a standalone program. It should be pre-compiled for you. Run it using the example
+template provided (`example.vm`):
+
+ ./example1.sh
+
+## Application Example #2
+
+Another simple example showing how to use Velocity in a standalone
+program. This examples uses the org.apache.velocity.util.Velocity application utility
+class, which provides a few convenient methods for application programmers. It also
+should be precompiled for you. To run:
+
+ ./example2.sh
+
+## Context Example
+
+This is a demonstration of 2 different context implementations:
+
+- a context implementation that uses a database as the storage. You will need to copy the appropriate JDBC driver jar file in the lib/ directory, and to adapt the JDBC driver and credentials accordingly in the `src/org/apache/velocity/example/DBContextTest.java` file.
+
+- a context implementation that uses a `TreeMap` for storage. Very simple.
+
+To run:
+
+ ./dbcontexttest.sh
+
+## Xml App example
+
+This is simple example that demonstrates direct access of XML data via
+Velocity templates, as well as Velocimacro recursion. To run:
+
+ ./xmlapp_example.sh
+
+## Event Example
+
+This is a simple yet more advanced example, and shows how to use the event handling
+features of Velocity. This is an advanced topic, so if you are just
+starting with Velocity, you can come back to it later. To run:
+
+ ./event_example.sh
+
+___
+
+Thanks for using Velocity!
+
+>the Velocity team
diff --git a/velocity-engine-examples/pom.xml b/velocity-engine-examples/pom.xml
new file mode 100644
index 00000000..cff04136
--- /dev/null
+++ b/velocity-engine-examples/pom.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-parent</artifactId>
+ <version>2.4-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>velocity-engine-examples</artifactId>
+ <name>Apache Velocity Engine - Examples</name>
+ <description>Very simple examples to use Velocity</description>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/lib</outputDirectory>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <configuration>
+ <outputDirectory>${project.build.directory}/examples</outputDirectory>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <descriptors>
+ <descriptor>src/assembly/examples.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>package-examples</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <filtering>true</filtering>
+ </resource>
+ <resource>
+ <directory>src/etc</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jdom</groupId>
+ <artifactId>jdom</artifactId>
+ <version>1.1.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/velocity-engine-examples/src/assembly/examples.xml b/velocity-engine-examples/src/assembly/examples.xml
new file mode 100644
index 00000000..210fd420
--- /dev/null
+++ b/velocity-engine-examples/src/assembly/examples.xml
@@ -0,0 +1,62 @@
+<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
+ <id>pkg</id>
+ <baseDirectory>${project.build.directory}</baseDirectory>
+ <includeBaseDirectory>false</includeBaseDirectory>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <fileSets>
+ <!-- copy dependencies to lib/ -->
+ <fileSet>
+ <directory>${project.build.directory}</directory>
+ <outputDirectory>${project.build.finalName}</outputDirectory>
+ <includes>
+ <include>lib/*.jar</include>
+ </includes>
+ </fileSet>
+ <!-- copy self jar to lib/ -->
+ <fileSet>
+ <directory>${project.build.directory}</directory>
+ <outputDirectory>${project.build.finalName}/lib</outputDirectory>
+ <includes>
+ <include>${project.build.finalName}.jar</include>
+ </includes>
+ </fileSet>
+ <!-- copy examples resources -->
+ <fileSet>
+ <directory>${project.build.directory}/examples</directory>
+ <outputDirectory>${project.build.finalName}</outputDirectory>
+ <includes>
+ <include>*.vm</include>
+ <include>*.xml</include>
+ <include>*.properties</include>
+ </includes>
+ </fileSet>
+ <!-- copy examples scripts -->
+ <fileSet>
+ <directory>${project.build.directory}/examples</directory>
+ <outputDirectory>${project.build.finalName}</outputDirectory>
+ <fileMode>0755</fileMode>
+ <includes>
+ <include>*.sh</include>
+ </includes>
+ </fileSet>
+ <!-- copy readme file -->
+ <fileSet>
+ <directory>${project.basedir}</directory>
+ <outputDirectory>${project.build.finalName}</outputDirectory>
+ <includes>
+ <include>README.md</include>
+ </includes>
+ </fileSet>
+ <!-- copy sources -->
+ <fileSet>
+ <directory>${project.basedir}/src/main/java</directory>
+ <outputDirectory>${project.build.finalName}/src</outputDirectory>
+ <includes>
+ <include>**/*.java</include>
+ </includes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/velocity-engine-examples/src/etc/build.sh b/velocity-engine-examples/src/etc/build.sh
new file mode 100644
index 00000000..81652a4f
--- /dev/null
+++ b/velocity-engine-examples/src/etc/build.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# 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.
+
+echo "Building examples..."
+
+echo rm lib/${project.build.finalName}.jar
+rm lib/${project.build.finalName}.jar
+
+_VELCP=.
+for i in lib/*.jar
+do
+ _VELCP="$_VELCP:$i"
+done
+
+# convert the unix path to windows
+if [ "$OSTYPE" = "cygwin32" ] || [ "$OSTYPE" = "cygwin" ] ; then
+ _VELCP=`cygpath --path --windows "$_VELCP"`
+fi
+
+echo "Using classpath $_VELCP"
+
+find src -name "*.java" > sources.txt
+mkdir classes
+javac -cp $_VELCP -d classes @sources.txt && jar cf lib/${project.build.finalName}.jar -C classes . && echo "Build successful."
diff --git a/velocity-engine-examples/src/etc/dbcontexttest.sh b/velocity-engine-examples/src/etc/dbcontexttest.sh
new file mode 100755
index 00000000..26c56e21
--- /dev/null
+++ b/velocity-engine-examples/src/etc/dbcontexttest.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# 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.
+
+echo "DBContextTest : please ensure your DB engine is set up and jdbc drivers are in classpath. See DBContextTest.java for clues."
+echo "This is an unsupported demo."
+
+echo "Running DBContextTest with input file 'dbtest.vm'"
+
+_VELCP=.
+for i in lib/*.jar
+do
+ _VELCP="$_VELCP:$i"
+done
+
+# convert the unix path to windows
+if [ "$OSTYPE" = "cygwin32" ] || [ "$OSTYPE" = "cygwin" ] ; then
+ _VELCP=`cygpath --path --windows "$_VELCP"`
+fi
+
+echo "Using classpath $_VELCP"
+
+
+java -cp $_VELCP org.apache.velocity.example.DBContextTest dbtest.vm
+
diff --git a/velocity-engine-examples/src/etc/example1.sh b/velocity-engine-examples/src/etc/example1.sh
new file mode 100755
index 00000000..acee004c
--- /dev/null
+++ b/velocity-engine-examples/src/etc/example1.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# 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.
+
+echo "Running Example with input file 'example1.vm'"
+
+_VELCP=.
+for i in lib/*.jar
+do
+ _VELCP="$_VELCP:$i"
+done
+
+# convert the unix path to windows
+if [ "$OSTYPE" = "cygwin32" ] || [ "$OSTYPE" = "cygwin" ] ; then
+ _VELCP=`cygpath --path --windows "$_VELCP"`
+fi
+
+echo "Using classpath $_VELCP"
+
+java -cp $_VELCP org.apache.velocity.example.Example example1.vm
+
diff --git a/velocity-engine-examples/src/etc/example2.sh b/velocity-engine-examples/src/etc/example2.sh
new file mode 100755
index 00000000..0016f77f
--- /dev/null
+++ b/velocity-engine-examples/src/etc/example2.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# 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.
+
+echo "Running Example2"
+
+_VELCP=.
+for i in lib/*.jar
+do
+ _VELCP="$_VELCP:$i"
+done
+
+# convert the unix path to windows
+if [ "$OSTYPE" = "cygwin32" ] || [ "$OSTYPE" = "cygwin" ] ; then
+ _VELCP=`cygpath --path --windows "$_VELCP"`
+fi
+
+echo "Using classpath $_VELCP"
+
+java -cp $_VELCP org.apache.velocity.example.Example2
+
diff --git a/velocity-engine-examples/src/etc/xmlapp_example.sh b/velocity-engine-examples/src/etc/xmlapp_example.sh
new file mode 100755
index 00000000..fcd51527
--- /dev/null
+++ b/velocity-engine-examples/src/etc/xmlapp_example.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# 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.
+
+echo "Running XMLTest with input file 'xml.vm'"
+
+_VELCP=.
+for i in lib/*.jar
+do
+ _VELCP="$_VELCP:$i"
+done
+
+# convert the unix path to windows
+if [ "$OSTYPE" = "cygwin32" ] || [ "$OSTYPE" = "cygwin" ] ; then
+ _VELCP=`cygpath --path --windows "$_VELCP"`
+fi
+
+echo "Using classpath $_VELCP"
+
+java -cp $_VELCP org.apache.velocity.example.XMLTest xml.vm
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContext.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContext.java
new file mode 100644
index 00000000..c6e64ea6
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContext.java
@@ -0,0 +1,190 @@
+package org.apache.velocity.example;
+/*
+ * 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.AbstractContext;
+import org.apache.velocity.context.Context;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+/**
+ * Example context impl that uses a database to store stuff :)
+ *
+ * yes, this is silly
+ *
+ * expects a mysql db test with table
+ *
+ * CREATE TABLE contextstore (
+ * k varchar(100),
+ * val blob
+ * );
+ *
+ * very fragile, crappy code.... just a demo!
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+
+public class DBContext extends AbstractContext
+{
+ Connection conn = null;
+
+ public DBContext()
+ {
+ super();
+ setup();
+ }
+
+ public DBContext( Context inner )
+ {
+ super( inner );
+ setup();
+ }
+
+ /**
+ * retrieves a serialized object from the db
+ * and returns the living instance to the
+ * caller.
+ */
+ @Override
+ public Object internalGet(String key )
+ {
+ try
+ {
+ String sql = "SELECT val FROM contextstore WHERE k ='"+key+"'";
+ Statement s = conn.createStatement();
+ ResultSet rs = s.executeQuery( sql );
+
+ Object o = null;
+ ObjectInputStream in = null;
+
+ if(rs.next())
+ {
+ in = new ObjectInputStream( rs.getBinaryStream(1) );
+ o = in.readObject();
+ in.close();
+ }
+
+ rs.close();
+ s.close();
+
+ return o;
+ }
+ catch(Exception e)
+ {
+ System.out.println("internalGet() : " + e );
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * Serializes and stores an object in the database.
+ * This is really a hokey way to do it, and will
+ * cause problems. The right way is to use a
+ * prepared statement...
+ */
+ @Override
+ public Object internalPut(String key, Object value )
+ {
+ try
+ {
+ Statement s = conn.createStatement();
+ s.executeUpdate( "DELETE FROM contextstore WHERE k = '" + key + "'" );
+ s.close();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream( baos );
+ out.writeObject( value );
+ byte buf[] = baos.toByteArray();
+ out.close();
+ baos.close();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(buf);
+ PreparedStatement ps = conn.prepareStatement( "INSERT INTO contextstore (k,val) values ('"+key+"', ?)");
+ ps.setBinaryStream(1, bais, buf.length);
+ ps.executeUpdate();
+ ps.close();
+
+ }
+ catch(Exception e)
+ {
+ System.out.println("internalGet() : " + e );
+ }
+
+ return null;
+ }
+
+ /**
+ * Not implementing. Not required for Velocity core
+ * operation, so not bothering. As we say above :
+ * "very fragile, crappy code..."
+ */
+ @Override
+ public boolean internalContainsKey(String key)
+ {
+ return false;
+ }
+
+ /**
+ * Not implementing. Not required for Velocity core
+ * operation, so not bothering. As we say above :
+ * "very fragile, crappy code..."
+ */
+ @Override
+ public String[] internalGetKeys()
+ {
+ return null;
+ }
+
+ /**
+ * Not implementing. Not required for Velocity core
+ * operation, so not bothering. As we say above :
+ * "very fragile, crappy code..."
+ */
+ @Override
+ public Object internalRemove(String key)
+ {
+ return null;
+ }
+
+
+ private void setup()
+ {
+ try
+ {
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ conn = DriverManager.getConnection("jdbc:mysql://localhost/test?user=YOUR_DB_USER&password=YOUR_DB_PASSWORD");
+ }
+ catch (Exception e)
+ {
+ System. out.println(e);
+ }
+ }
+}
+
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContextTest.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContextTest.java
new file mode 100644
index 00000000..4c123204
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/DBContextTest.java
@@ -0,0 +1,76 @@
+package org.apache.velocity.example;
+/*
+ * 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.RuntimeSingleton;
+
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Hashtable;
+import java.util.Properties;
+
+/**
+ * the ultimate in silliness...
+ *
+ * tests the DBContext example by putting a string and a hashtable
+ * into the context and then rendering a simple template with it.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+
+public class DBContextTest
+{
+ public DBContextTest(String templateFile)
+ {
+ try
+ {
+ RuntimeSingleton.init( new Properties() );
+
+ Template template = RuntimeSingleton.getTemplate(templateFile);
+
+ DBContext dbc = new DBContext();
+
+ Hashtable h = new Hashtable();
+ h.put("Bar", "this is from a hashtable!");
+
+ dbc.put( "string", "Hello!");
+ dbc.put( "hashtable", h );
+
+ Writer writer = new BufferedWriter(new OutputStreamWriter(System.out));
+
+ template.merge(dbc, writer);
+
+ writer.flush();
+ writer.close();
+ }
+ catch( Exception e )
+ {
+ RuntimeSingleton.getLog().error("Something funny happened", e);
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ DBContextTest t;
+ t = new DBContextTest(args[0]);
+ }
+}
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/EventExample.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/EventExample.java
new file mode 100644
index 00000000..610039b1
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/EventExample.java
@@ -0,0 +1,564 @@
+package org.apache.velocity.example;
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.event.EventCartridge;
+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.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.util.introspection.Info;
+
+import org.slf4j.helpers.FormattingTuple;
+import org.slf4j.helpers.MarkerIgnoringBase;
+import org.slf4j.helpers.MessageFormatter;
+
+import java.io.StringWriter;
+
+/**
+ * This class is a simple demonstration of how the event handling
+ * features of the Velocity Servlet Engine are used. It uses a
+ * custom logger as well to check the log message stream
+ * when testing the InvalidReferenceEventHandler.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+
+public class EventExample extends MarkerIgnoringBase
+ implements ReferenceInsertionEventHandler, MethodExceptionEventHandler, InvalidReferenceEventHandler
+{
+ private boolean logOutput = false;
+ private boolean exceptionSwitch = false;
+
+ public static void main( String args[] )
+ {
+ EventExample ee = new EventExample();
+ }
+
+ public EventExample()
+ {
+ try
+ {
+ /*
+ * this class implements the Logger interface, so we
+ * can use it as a logger for Velocity
+ */
+
+ Velocity.setProperty(Velocity.RUNTIME_LOG_INSTANCE, this );
+ Velocity.init();
+ }
+ catch(Exception e)
+ {
+ System.out.println("Problem initializing Velocity : " + e );
+ return;
+ }
+
+ /*
+ * lets make a Context and add some data
+ */
+
+ VelocityContext context = new VelocityContext();
+
+ context.put("name", "Velocity");
+
+ /*
+ * Now make an event cartridge, register all the
+ * event handlers (at once) and attach it to the
+ * Context
+ */
+
+ EventCartridge ec = new EventCartridge();
+ ec.addEventHandler(this);
+ ec.attachToContext( context );
+
+ try
+ {
+ /*
+ * lets test each type of event handler individually
+ * using 'dynamic' templates
+ *
+ * First, the reference insertion handler
+ */
+
+ System.out.println("");
+ System.out.println("Velocity Event Handling Demo");
+ System.out.println("============================");
+ System.out.println("");
+
+ String s = "The word 'Velocity' should be bounded by emoticons : $name.";
+
+ StringWriter w = new StringWriter();
+ Velocity.evaluate( context, w, "mystring", s );
+
+ System.out.println("Reference Insertion Test : ");
+ System.out.println(" " + w.toString());
+ System.out.println("");
+
+ /*
+ * using the same handler, we can deal with
+ * null references as well
+ */
+
+ s = "There is no reference $floobie, $nullvalue or anything in the brackets : >$!silentnull<";
+
+ w = new StringWriter();
+ Velocity.evaluate( context, w, "mystring", s );
+
+ System.out.println("Reference Insertion Test with null references : ");
+ System.out.println(" " + w.toString());
+ System.out.println("");
+
+ /*
+ * now lets test setting a null value - this test
+ * should result in *no* log output.
+ * Turn on the logger output.
+ */
+
+ logOutput = true;
+
+ s = "#set($settest = $NotAReference)";
+ w = new StringWriter();
+
+ System.out.println("invalidSetMethod test : " );
+ System.out.print(" There should be nothing between >");
+ Velocity.evaluate( context, w, "mystring", s );
+ System.out.println("< the brackets.");
+ System.out.println("");
+
+ /*
+ * now lets test setting a null value - this test
+ * should result in log output.
+ */
+
+ s = "#set($logthis = $NotAReference)";
+ w = new StringWriter();
+
+ System.out.println("invalidSetMethod test : " );
+ System.out.print(" There should be a log message between >");
+ Velocity.evaluate( context, w, "mystring", s );
+ System.out.println("< the brackets.");
+ System.out.println("");
+
+ logOutput = false;
+
+ /*
+ * finally, we test a method exception event - we do this
+ * by putting this class in the context, and calling
+ * a method that does nothing but throw an exception.
+ * we use a little switch to turn the event handling
+ * on and off
+ *
+ * Note also how the reference insertion process
+ * happens as well
+ */
+
+ exceptionSwitch = true;
+
+ context.put("this", this );
+
+ s = " $this.throwException()";
+ w = new StringWriter();
+
+ System.out.println("MethodExceptionEventHandler test : " );
+ System.out.print(" This exception will be controlled and converted into a string : ");
+ Velocity.evaluate( context, w, "mystring", s );
+ System.out.println(" " + w.toString());
+ System.out.println("");
+
+ /*
+ * now, we turn the switch off, and we can see that the
+ * exception will propgate all the way up here, and
+ * wil be caught by the catch() block below
+ */
+
+ exceptionSwitch = false;
+
+ s = " $this.throwException()";
+ w = new StringWriter();
+
+ System.out.println("MethodExceptionEventHandler test : " );
+ System.out.println(" This exception will NOT be controlled. "
+ + " The next thing you should see is the catch() output ");
+ Velocity.evaluate( context, w, "mystring", s );
+ System.out.println("If you see this, it didn't work!");
+
+ }
+ catch( ParseErrorException pee )
+ {
+ /*
+ * thrown if something is wrong with the
+ * syntax of our template string
+ */
+ System.out.println("ParseErrorException : " + pee );
+ }
+ catch( MethodInvocationException mee )
+ {
+ /*
+ * thrown if a method of a reference
+ * called by the template
+ * throws an exception. That won't happen here
+ * as we aren't calling any methods in this
+ * example, but we have to catch them anyway
+ */
+ System.out.println(" Catch Block : MethodInvocationException : " + mee );
+ }
+ catch( Exception e )
+ {
+ System.out.println("Exception : " + e );
+ }
+ }
+
+ /**
+ * silly method to throw an exception to demonstrate
+ * the method invocation exception event handling
+ */
+ public void throwException()
+ throws Exception
+ {
+ throw new Exception("Hello from throwException()");
+ }
+
+ /**
+ * Event handler for when a reference is inserted into the output stream.
+ */
+ @Override
+ public Object referenceInsert(Context context, String reference, Object value )
+ {
+ /*
+ * if we have a value
+ * lets decorate the reference with emoticons
+ */
+
+ String s = null;
+
+ if( value != null )
+ {
+ s = " ;) " + value.toString() + " :-)";
+ }
+ else
+ {
+ /*
+ * we only want to deal with $floobie - anything
+ * else we let go
+ */
+ if ( reference.equals("floobie") )
+ {
+ s = "<no floobie value>";
+ }
+ }
+ return s;
+ }
+
+ /**
+ */
+
+ @Override
+ public Object invalidGetMethod(Context context, String reference, Object object, String property, Info info)
+ {
+ /* NOP */
+ return null;
+ }
+
+ @Override
+ public Object invalidMethod(Context context, String reference, Object object, String method, Info info)
+ {
+ /* NOP */
+ return null;
+ }
+
+ /**
+ * Event handler for when the right hand side of
+ * a #set() directive is null, which results in
+ * a log message. This method gives the application
+ * a chance to 'vote' on msg generation
+ */
+ @Override
+ public boolean invalidSetMethod(Context context, String leftreference, String rightreference, Info info)
+ {
+ if (leftreference.equals("logthis"))
+ {
+ System.out.print("Setting reference " + leftreference + " to null");
+ }
+ return false;
+ }
+
+ @Override
+ public Object methodException(Context context, Class claz, String method, Exception e, Info info ) {
+ /*
+ * only do processing if the switch is on
+ */
+
+ if( exceptionSwitch && method.equals("throwException"))
+ {
+ return "Hello from the methodException() event handler method.";
+ }
+
+ throw new RuntimeException(e);
+ }
+
+ /*
+ * Our own logging towards System.out
+ */
+
+ /**
+ * This just prints the message and level to System.out.
+ */
+ public void log(int level, String message)
+ {
+ if (logOutput)
+ {
+ System.out.println("level : " + level + " msg : " + message);
+ }
+ }
+
+
+ /**
+ * This prints the level, message, and the Throwable's message to
+ * System.out.
+ */
+ public void log(int level, String message, Throwable t)
+ {
+ if (logOutput)
+ {
+ System.out.println("level : " + level + " msg : " + message + " t : "
+ + t.getMessage());
+ }
+ }
+
+ /**
+ * This prints the level and formatted message to
+ * System.out.
+ */
+ public void formatAndLog(int level, String format, Object... arguments)
+ {
+ if (logOutput)
+ {
+ FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments);
+ log(level, tp.getMessage());
+ }
+ }
+
+ /**
+ * This always returns true because logging levels can't be disabled in
+ * this impl.
+ */
+ public boolean isLevelEnabled(int level)
+ {
+ return true;
+ }
+
+ public static final int LOG_LEVEL_TRACE = 1;
+ public static final int LOG_LEVEL_DEBUG = 2;
+ public static final int LOG_LEVEL_INFO = 3;
+ public static final int LOG_LEVEL_WARN = 4;
+ public static final int LOG_LEVEL_ERROR = 5;
+
+ /**
+ * Required init methods for Logger interface
+ */
+
+ @Override
+ public String getName()
+ {
+ return "EventExample";
+ }
+
+ @Override
+ public boolean isTraceEnabled() {
+ return isLevelEnabled(LOG_LEVEL_TRACE);
+ }
+
+ @Override
+ public void trace(String msg) {
+ log(LOG_LEVEL_TRACE, msg);
+ }
+
+ @Override
+ public void trace(String format, Object param1)
+ {
+ formatAndLog(LOG_LEVEL_TRACE, format, param1);
+ }
+
+ @Override
+ public void trace(String format, Object param1, Object param2)
+ {
+ formatAndLog(LOG_LEVEL_TRACE, format, param1, param2);
+ }
+
+ @Override
+ public void trace(String format, Object... argArray)
+ {
+ formatAndLog(LOG_LEVEL_TRACE, format, argArray);
+ }
+
+ @Override
+ public void trace(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_TRACE, msg, t);
+ }
+
+ @Override
+ public boolean isDebugEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_DEBUG);
+ }
+
+ @Override
+ public void debug(String msg)
+ {
+ log(LOG_LEVEL_DEBUG, msg);
+ }
+
+ @Override
+ public void debug(String format, Object param1)
+ {
+ formatAndLog(LOG_LEVEL_DEBUG, format, param1);
+ }
+
+ @Override
+ public void debug(String format, Object param1, Object param2)
+ {
+ formatAndLog(LOG_LEVEL_DEBUG, format, param1, param2);
+ }
+
+ @Override
+ public void debug(String format, Object... argArray)
+ {
+ formatAndLog(LOG_LEVEL_DEBUG, format, argArray);
+ }
+
+ @Override
+ public void debug(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_DEBUG, msg, t);
+ }
+
+ @Override
+ public boolean isInfoEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_INFO);
+ }
+
+ @Override
+ public void info(String msg)
+ {
+ log(LOG_LEVEL_INFO, msg);
+ }
+
+ @Override
+ public void info(String format, Object arg)
+ {
+ formatAndLog(LOG_LEVEL_INFO, format, arg);
+ }
+
+ @Override
+ public void info(String format, Object arg1, Object arg2)
+ {
+ formatAndLog(LOG_LEVEL_INFO, format, arg1, arg2);
+ }
+
+ @Override
+ public void info(String format, Object... argArray)
+ {
+ formatAndLog(LOG_LEVEL_INFO, format, argArray);
+ }
+
+ @Override
+ public void info(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_INFO, msg, t);
+ }
+
+ @Override
+ public boolean isWarnEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_WARN);
+ }
+
+ @Override
+ public void warn(String msg)
+ {
+ log(LOG_LEVEL_WARN, msg);
+ }
+
+ @Override
+ public void warn(String format, Object arg)
+ {
+ formatAndLog(LOG_LEVEL_WARN, format, arg);
+ }
+
+ @Override
+ public void warn(String format, Object arg1, Object arg2)
+ {
+ formatAndLog(LOG_LEVEL_WARN, format, arg1, arg2);
+ }
+
+ @Override
+ public void warn(String format, Object... argArray)
+ {
+ formatAndLog(LOG_LEVEL_WARN, format, argArray);
+ }
+
+ @Override
+ public void warn(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_WARN, msg, t);
+ }
+
+ @Override
+ public boolean isErrorEnabled()
+ {
+ return isLevelEnabled(LOG_LEVEL_ERROR);
+ }
+
+ @Override
+ public void error(String msg)
+ {
+ log(LOG_LEVEL_ERROR, msg);
+ }
+
+ @Override
+ public void error(String format, Object arg)
+ {
+ formatAndLog(LOG_LEVEL_ERROR, format, arg);
+ }
+
+ @Override
+ public void error(String format, Object arg1, Object arg2)
+ {
+ formatAndLog(LOG_LEVEL_ERROR, format, arg1, arg2);
+ }
+
+ @Override
+ public void error(String format, Object... argArray)
+ {
+ formatAndLog(LOG_LEVEL_ERROR, format, argArray);
+ }
+
+ @Override
+ public void error(String msg, Throwable t)
+ {
+ log(LOG_LEVEL_ERROR, msg, t);
+ }
+}
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/Example.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/Example.java
new file mode 100644
index 00000000..7717f382
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/Example.java
@@ -0,0 +1,126 @@
+package org.apache.velocity.example;
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+
+/**
+ * This class is a simple demonstration of how the Velocity Template Engine
+ * can be used in a standalone application.
+ *
+ * @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 Example
+{
+ public Example(String templateFile)
+ {
+ try
+ {
+ /*
+ * setup
+ */
+
+ Velocity.init("velocity.properties");
+
+ /*
+ * Make a context object and populate with the data. This
+ * is where the Velocity engine gets the data to resolve the
+ * references (ex. $list) in the template
+ */
+
+ VelocityContext context = new VelocityContext();
+ context.put("list", getNames());
+
+ /*
+ * get the Template object. This is the parsed version of your
+ * template input file. Note that getTemplate() can throw
+ * ResourceNotFoundException : if it doesn't find the template
+ * ParseErrorException : if there is something wrong with the VTL
+ * Exception : if something else goes wrong (this is generally
+ * indicative of as serious problem...)
+ */
+
+ Template template = null;
+
+ try
+ {
+ template = Velocity.getTemplate(templateFile);
+ }
+ catch( ResourceNotFoundException rnfe )
+ {
+ System.out.println("Example : error : cannot find template " + templateFile );
+ }
+ catch( ParseErrorException pee )
+ {
+ System.out.println("Example : Syntax error in template " + templateFile + ":" + pee );
+ }
+
+ /*
+ * Now have the template engine process your template using the
+ * data placed into the context. Think of it as a 'merge'
+ * of the template and the data to produce the output stream.
+ */
+
+ BufferedWriter writer = new BufferedWriter(
+ new OutputStreamWriter(System.out));
+
+ if ( template != null)
+ template.merge(context, writer);
+
+ /*
+ * flush and cleanup
+ */
+
+ writer.flush();
+ writer.close();
+ }
+ catch( Exception e )
+ {
+ System.out.println(e);
+ }
+ }
+
+ public ArrayList getNames()
+ {
+ ArrayList list = new ArrayList();
+
+ list.add("ArrayList element 1");
+ list.add("ArrayList element 2");
+ list.add("ArrayList element 3");
+ list.add("ArrayList element 4");
+
+ return list;
+ }
+
+ public static void main(String[] args)
+ {
+ Example t = new Example(args[0]);
+ }
+}
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/Example2.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/Example2.java
new file mode 100644
index 00000000..bf3546a6
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/Example2.java
@@ -0,0 +1,116 @@
+package org.apache.velocity.example;
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+
+import java.io.StringWriter;
+
+/**
+ * This class is a simple demonstration of how the Velocity Template Engine
+ * can be used in a standalone application using the Velocity utility class.
+ *
+ * It demonstrates two of the 'helper' methods found in the org.apache.velocity.util.Velocity
+ * class, mergeTemplate() and evaluate().
+ *
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+
+public class Example2
+{
+ public static void main( String args[] )
+ {
+ /* first, we init the runtime engine. Defaults are fine. */
+
+ try
+ {
+ Velocity.init();
+ }
+ catch(Exception e)
+ {
+ System.out.println("Problem initializing Velocity : " + e );
+ return;
+ }
+
+ /* lets make a Context and put data into it */
+
+ VelocityContext context = new VelocityContext();
+
+ context.put("name", "Velocity");
+ context.put("project", "Engine");
+
+ /* lets render a template */
+
+ StringWriter w = new StringWriter();
+
+ try
+ {
+ Velocity.mergeTemplate("example2.vm", "ISO-8859-1", context, w );
+ }
+ catch (Exception e )
+ {
+ System.out.println("Problem merging template : " + e );
+ }
+
+ System.out.println(" template : " + w );
+
+ /*
+ * lets dynamically 'create' our template
+ * and use the evaluate() method to render it
+ */
+
+ String s = "We are using $project $name to render this.";
+ w = new StringWriter();
+
+ try
+ {
+ Velocity.evaluate( context, w, "mystring", s );
+ }
+ catch( ParseErrorException pee )
+ {
+ /*
+ * thrown if something is wrong with the
+ * syntax of our template string
+ */
+ System.out.println("ParseErrorException : " + pee );
+ }
+ catch( MethodInvocationException mee )
+ {
+ /*
+ * thrown if a method of a reference
+ * called by the template
+ * throws an exception. That won't happen here
+ * as we aren't calling any methods in this
+ * example, but we have to catch them anyway
+ */
+ System.out.println("MethodInvocationException : " + mee );
+ }
+ catch( Exception e )
+ {
+ System.out.println("Exception : " + e );
+ }
+
+ System.out.println(" string : " + w );
+ }
+}
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/TreeMapContext.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/TreeMapContext.java
new file mode 100644
index 00000000..40a6dce5
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/TreeMapContext.java
@@ -0,0 +1,84 @@
+package org.apache.velocity.example;
+/*
+ * 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.AbstractContext;
+import org.apache.velocity.context.Context;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * Example context impl that uses a TreeMap
+ *
+ * Not much point other than to show how easy it is.
+ *
+ * This is unsupported, example code.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+
+public class TreeMapContext extends AbstractContext
+{
+ private Map<String, Object> context = new TreeMap<>();
+
+ public TreeMapContext()
+ {
+ super();
+ }
+
+ public TreeMapContext( Context inner )
+ {
+ super( inner );
+ }
+
+ @Override
+ public Object internalGet(String key )
+ {
+ return context.get( key );
+ }
+
+ @Override
+ public Object internalPut(String key, Object value )
+ {
+ return context.put( key, value );
+ }
+
+ @Override
+ public boolean internalContainsKey(String key)
+ {
+ return context.containsKey( key );
+ }
+
+ @Override
+ public String[] internalGetKeys()
+ {
+ Set<String> keys = context.keySet();
+ return keys.toArray(new String[keys.size()]);
+ }
+
+ @Override
+ public Object internalRemove(String key)
+ {
+ return context.remove( key );
+ }
+}
+
diff --git a/velocity-engine-examples/src/main/java/org/apache/velocity/example/XMLTest.java b/velocity-engine-examples/src/main/java/org/apache/velocity/example/XMLTest.java
new file mode 100644
index 00000000..78cba681
--- /dev/null
+++ b/velocity-engine-examples/src/main/java/org/apache/velocity/example/XMLTest.java
@@ -0,0 +1,121 @@
+package org.apache.velocity.example;
+/*
+ * 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.VelocityContext;
+import org.apache.velocity.app.Velocity;
+
+import org.jdom.Document;
+import org.jdom.input.SAXBuilder;
+
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+
+/**
+ * Example to show basic XML handling in a template.
+ *
+ * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
+ * @version $Id$
+ */
+public class XMLTest
+{
+ public XMLTest( String templateFile)
+ {
+ Writer writer = null;
+
+ try
+ {
+ /*
+ * and now call init
+ */
+
+ Velocity.init();
+
+
+ /*
+ * build a Document from our xml
+ */
+
+ SAXBuilder builder;
+ Document root = null;
+
+ try
+ {
+ builder = new SAXBuilder();
+ root = builder.build("test.xml");
+ }
+ catch( Exception ee)
+ {
+ System.out.println("Exception building Document : " + ee);
+ return;
+ }
+
+ /*
+ * now, make a Context object and populate it.
+ */
+
+ VelocityContext context = new VelocityContext();
+ context.put("root", root);
+
+ /*
+ * make a writer, and merge the template 'against' the context
+ */
+
+ Template template = Velocity.getTemplate(templateFile);
+
+ writer = new BufferedWriter(new OutputStreamWriter(System.out));
+ template.merge( context , writer);
+ }
+ catch( Exception e )
+ {
+ System.out.println("Exception : " + e);
+ }
+ finally
+ {
+ if ( writer != null)
+ {
+ try
+ {
+ writer.flush();
+ writer.close();
+ }
+ catch( Exception ee )
+ {
+ System.out.println("Exception : " + ee );
+ }
+ }
+ }
+ }
+
+ public static void main(String[] args)
+ {
+ XMLTest t;
+
+ if( args.length < 1 )
+ {
+ System.out.println("Usage : java XMLTest <templatename>");
+ return;
+ }
+
+ t = new XMLTest(args[0]);
+ }
+}
diff --git a/velocity-engine-examples/src/main/resources/dbtest.vm b/velocity-engine-examples/src/main/resources/dbtest.vm
new file mode 100644
index 00000000..1ee3979e
--- /dev/null
+++ b/velocity-engine-examples/src/main/resources/dbtest.vm
@@ -0,0 +1,18 @@
+## 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.
+$string
+$hashtable.Bar
diff --git a/velocity-engine-examples/src/main/resources/example1.vm b/velocity-engine-examples/src/main/resources/example1.vm
new file mode 100644
index 00000000..7b17de21
--- /dev/null
+++ b/velocity-engine-examples/src/main/resources/example1.vm
@@ -0,0 +1,32 @@
+## 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.
+
+#set( $this = "Velocity")
+
+$this is great!
+
+#foreach( $name in $list )
+ $name is great!
+#end
+
+#set( $condition = true)
+
+#if ($condition)
+ The condition is true!
+#else
+ The condition is false!
+#end
diff --git a/velocity-engine-examples/src/main/resources/example2.vm b/velocity-engine-examples/src/main/resources/example2.vm
new file mode 100644
index 00000000..27738c13
--- /dev/null
+++ b/velocity-engine-examples/src/main/resources/example2.vm
@@ -0,0 +1,17 @@
+## 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.
+Hello from $name in the $project project.
diff --git a/velocity-engine-examples/src/main/resources/test.xml b/velocity-engine-examples/src/main/resources/test.xml
new file mode 100644
index 00000000..a76ce44a
--- /dev/null
+++ b/velocity-engine-examples/src/main/resources/test.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<document>
+ <properties>
+ <author>
+ <email>user@velocity.apache.org</email>
+ <name>
+ <last>
+ <full>Team</full>
+ <firstinitial>T</firstinitial>
+ </last>
+ <first>
+ Velocity
+ </first>
+ </name>
+ </author>
+ <title>
+ The Velocity Project
+ </title>
+ </properties>
+
+ <body>
+ I am the body
+ </body>
+</document>
diff --git a/velocity-engine-examples/src/main/resources/velocity.properties b/velocity-engine-examples/src/main/resources/velocity.properties
new file mode 100644
index 00000000..00a7ec66
--- /dev/null
+++ b/velocity-engine-examples/src/main/resources/velocity.properties
@@ -0,0 +1,20 @@
+# 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.
+
+# Here you can configure Velocity behavior,
+# see http://velocity.apache.org/engine/${project.version}/configuration.html#configuring-velocity
+runtime.log.name = velocity
diff --git a/velocity-engine-examples/src/main/resources/xml.vm b/velocity-engine-examples/src/main/resources/xml.vm
new file mode 100644
index 00000000..366b79b1
--- /dev/null
+++ b/velocity-engine-examples/src/main/resources/xml.vm
@@ -0,0 +1,42 @@
+## 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.
+#macro ( recursive $e $indent )
+#if( $e.getChildren().size() > 0 )
+$indent <$e.getName()>
+#foreach ($child in $e.getChildren() )
+#recursive( $child "$indent " )
+#end
+$indent </$e.getName()>
+#else
+$indent <$e.getName()>
+$indent $e.getTextTrim()
+$indent </$e.getName()>
+#end
+#end
+
+#set($i = " ")
+
+First, we print out the document tree with a
+recursive Velocimacro :
+
+#recursive( $root.getRootElement() $i )
+
+
+Next, we access pieces of data directly :
+
+email : $root.getRootElement().getChild("properties").getChild("author").getChild("email").getText()
+last name : $root.getRootElement().getChild("properties").getChild("author").getChild("name").getChild("last").getChild("full").getText()
diff --git a/velocity-engine-scripting/pom.xml b/velocity-engine-scripting/pom.xml
new file mode 100644
index 00000000..1c6eaf9f
--- /dev/null
+++ b/velocity-engine-scripting/pom.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-parent</artifactId>
+ <version>2.4-SNAPSHOT</version>
+ </parent>
+ <artifactId>velocity-engine-scripting</artifactId>
+ <name>Apache Velocity - JSR 223 Scripting</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.velocity</groupId>
+ <artifactId>velocity-engine-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.plugin.version}</version>
+ <configuration>
+ <systemProperties>
+ <property>
+ <name>test.resources.dir</name>
+ <value>${project.build.directory}</value>
+ </property>
+ </systemProperties>
+ </configuration>
+ <executions>
+ <execution>
+ <id>integration-test</id>
+ <phase>integration-test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityCompiledScript.java b/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityCompiledScript.java
new file mode 100644
index 00000000..f88eb803
--- /dev/null
+++ b/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityCompiledScript.java
@@ -0,0 +1,70 @@
+package org.apache.velocity.script;
+
+/*
+ * 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.VelocityContext;
+
+import javax.script.CompiledScript;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+public class VelocityCompiledScript extends CompiledScript
+{
+ protected VelocityScriptEngine engine;
+ protected Template template;
+
+ public VelocityCompiledScript(VelocityScriptEngine e, Template t)
+ {
+ engine = e;
+ template = t;
+ }
+
+ @Override
+ public Object eval(ScriptContext scriptContext) throws ScriptException
+ {
+ VelocityContext velocityContext = VelocityScriptEngine.getVelocityContext(scriptContext);
+ Writer out = scriptContext.getWriter();
+ if (out == null)
+ {
+ out = new StringWriter();
+ scriptContext.setWriter(out);
+ }
+ try
+ {
+ template.merge(velocityContext, out);
+ out.flush();
+ }
+ catch (Exception exp)
+ {
+ throw new ScriptException(exp);
+ }
+ return out;
+ }
+
+ @Override
+ public ScriptEngine getEngine()
+ {
+ return engine;
+ }
+}
diff --git a/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java b/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java
new file mode 100644
index 00000000..469ca45d
--- /dev/null
+++ b/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngine.java
@@ -0,0 +1,338 @@
+package org.apache.velocity.script;
+
+/*
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met: Redistributions of source code
+ * must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution. Neither the name of the Sun Microsystems nor the names of
+ * is contributors may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Main class for the Velocity script engine.
+ * All the variants of the eval() methods return the writer. The default writer is a PrintWriter towards System.out.
+ * To specify a specific writer, use getContext().setWriter(writer). To get a resulting string, pass a StringWriter.
+ *
+ * You can specify a pathname towards a Velocity properties file using the "org.apache.velocity.script.properties" key,
+ * either as a ScriptContext attribute, or as a System property.
+ *
+ * Example use:
+ * <pre>
+ * ScriptEngine vel = new VelocityScriptEngine();
+ * vel.getContext().setAttribute(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, "path/to/velocity.properties");
+ * vel.getContext().setWriter(new StringWriter());
+ * vel.put("foo","World");
+ * Object result = vel.eval("Hello $foo !");
+ * String helloWorld = result.toString()
+ * </pre>
+ *
+ * Please refer to the javax.script.ScriptEngine documentation for additional details.
+ *
+ * @author A. Sundararajan
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: VelocityScriptEngine.java$
+ */
+
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.RuntimeInstance;
+import org.apache.velocity.runtime.resource.loader.ResourceLoader;
+import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
+
+import javax.script.AbstractScriptEngine;
+import javax.script.Bindings;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptException;
+import javax.script.SimpleBindings;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Properties;
+
+public class VelocityScriptEngine extends AbstractScriptEngine implements Compilable
+{
+ /**
+ * Key used to provide this engine with the pathname of the Velocity properties file.
+ * This key is first searched in the ScriptContext attributes, then as a System property
+ */
+ public static final String VELOCITY_PROPERTIES_KEY = "org.apache.velocity.script.properties";
+
+ // my factory, may be null
+ private volatile ScriptEngineFactory factory;
+ private volatile RuntimeInstance velocityEngine;
+
+ /**
+ * constructs a new Velocity script engine, linked to the given factory
+ * @param factory
+ */
+ public VelocityScriptEngine(ScriptEngineFactory factory)
+ {
+ this.factory = factory;
+ }
+
+ /**
+ * constructs a new standalone Velocity script engine
+ */
+ public VelocityScriptEngine()
+ {
+ this(null);
+ }
+
+ /**
+ * get the internal Velocity RuntimeInstance
+ * @return the internal Velocity RuntimeInstance
+ */
+ protected RuntimeInstance getVelocityEngine()
+ {
+ return velocityEngine;
+ }
+
+ /**
+ * Evaluate the given script.
+ * If you wish to get a resulting string, call getContext().setWriter(new StringWriter()) then call toString()
+ * on the returned writer.
+ * @param str script source
+ * @param ctx script context
+ * @return the script context writer (by default a PrintWriter towards System.out)
+ * @throws ScriptException
+ */
+ @Override
+ public Object eval(String str, ScriptContext ctx)
+ throws ScriptException
+ {
+ return eval(new StringReader(str), ctx);
+ }
+
+ /**
+ * Evaluate the given script.
+ * If you wish to get a resulting string, call getContext().setWriter(new StringWriter()) then call toString()
+ * on the returned writer.
+ * @param reader script source reader
+ * @param ctx script context
+ * @return the script context writer (by default a PrintWriter towards System.out)
+ * @throws ScriptException
+ */
+ @Override
+ public Object eval(Reader reader, ScriptContext ctx)
+ throws ScriptException
+ {
+ initVelocityEngine(ctx);
+ String fileName = getFilename(ctx);
+ VelocityContext vctx = getVelocityContext(ctx);
+ Writer out = ctx.getWriter();
+ if (out == null)
+ {
+ out = new StringWriter();
+ ctx.setWriter(out);
+ }
+ try
+ {
+ velocityEngine.evaluate(vctx, out, fileName, reader);
+ out.flush();
+ }
+ catch (Exception exp)
+ {
+ throw new ScriptException(exp);
+ }
+ return out;
+ }
+
+ /**
+ * get the factory used to create this script engine
+ * @return factory
+ */
+ @Override
+ public ScriptEngineFactory getFactory()
+ {
+ if (factory == null)
+ {
+ synchronized (this)
+ {
+ if (factory == null)
+ {
+ factory = new VelocityScriptEngineFactory();
+ }
+ }
+ }
+ return factory;
+ }
+
+ /**
+ * creates a new Bindings to be used with this script
+ * @return new bindings
+ */
+ @Override
+ public Bindings createBindings()
+ {
+ return new SimpleBindings();
+ }
+
+ private void initVelocityEngine(ScriptContext ctx)
+ {
+ if (ctx == null)
+ {
+ ctx = getContext();
+ }
+ if (velocityEngine == null)
+ {
+ synchronized (this)
+ {
+ if (velocityEngine != null) return;
+
+ Properties props = getVelocityProperties(ctx);
+ RuntimeInstance tmpEngine = new RuntimeInstance();
+ try
+ {
+ if (props != null)
+ {
+ tmpEngine.init(props);
+ }
+ else
+ {
+ tmpEngine.init();
+ }
+ }
+ catch (RuntimeException rexp)
+ {
+ throw rexp;
+ }
+ catch (Exception exp)
+ {
+ throw new RuntimeException(exp);
+ }
+ velocityEngine = tmpEngine;
+ }
+ }
+ }
+
+ protected static VelocityContext getVelocityContext(ScriptContext ctx)
+ {
+ ctx.setAttribute("context", ctx, ScriptContext.ENGINE_SCOPE);
+ Bindings globalScope = ctx.getBindings(ScriptContext.GLOBAL_SCOPE);
+ Bindings engineScope = ctx.getBindings(ScriptContext.ENGINE_SCOPE);
+ if (globalScope != null)
+ {
+ return new VelocityContext(engineScope, new VelocityContext(globalScope));
+ }
+ else
+ {
+ return new VelocityContext(engineScope);
+ }
+ }
+
+ protected static String getFilename(ScriptContext ctx)
+ {
+ Object fileName = ctx.getAttribute(ScriptEngine.FILENAME);
+ return fileName != null? fileName.toString() : "<unknown>";
+ }
+
+ protected static Properties getVelocityProperties(ScriptContext ctx)
+ {
+ try
+ {
+ Object props = ctx.getAttribute(VELOCITY_PROPERTIES_KEY);
+ if (props instanceof Properties)
+ {
+ return (Properties) props;
+ }
+ else
+ {
+ String propsName = System.getProperty(VELOCITY_PROPERTIES_KEY);
+ if (propsName != null)
+ {
+ File propsFile = new File(propsName);
+ if (propsFile.exists() && propsFile.canRead())
+ {
+ Properties p = new Properties();
+ p.load(new FileInputStream(propsFile));
+ return p;
+ }
+ }
+ }
+ }
+ catch (Exception exp)
+ {
+ System.err.println(exp);
+ }
+ return null;
+ }
+
+ /**
+ * Compile a template
+ * @param script template source
+ * @return compiled template
+ * @throws ScriptException
+ */
+ @Override
+ public CompiledScript compile(String script) throws ScriptException
+ {
+ return compile(new StringReader(script));
+ }
+
+ /**
+ * Compile a template
+ * @param script template source
+ * @return compiled template
+ * @throws ScriptException
+ */
+ @Override
+ public CompiledScript compile(Reader script) throws ScriptException
+ {
+ initVelocityEngine(null);
+ ResourceLoader resourceLoader = new SingleResourceReader(script);
+ Template template = new Template();
+ template.setRuntimeServices(velocityEngine);
+ template.setResourceLoader(resourceLoader);
+ try
+ {
+ template.process();
+ }
+ catch(Exception e)
+ {
+ // CB TODO - exception may have line/col informations, that ScriptException can exploit
+ throw new ScriptException(e);
+ }
+ return new VelocityCompiledScript(this, template);
+ }
+
+ // a dummy resource reader class, serving a single resource given by the provided resource reader
+ protected static class SingleResourceReader extends StringResourceLoader
+ {
+ private Reader reader;
+
+ public SingleResourceReader(Reader r)
+ {
+ reader = r;
+ }
+
+ @Override
+ public Reader getResourceReader(String source, String encoding) throws ResourceNotFoundException
+ {
+ return reader;
+ }
+ }
+}
diff --git a/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java b/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java
new file mode 100644
index 00000000..7dd77fc1
--- /dev/null
+++ b/velocity-engine-scripting/src/main/java/org/apache/velocity/script/VelocityScriptEngineFactory.java
@@ -0,0 +1,231 @@
+package org.apache.velocity.script;
+
+/*
+ * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met: Redistributions of source code
+ * must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution. Neither the name of the Sun Microsystems nor the names of
+ * is contributors may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Factory class for the Velocity scripting interface. Please refer to the
+ * javax.script.ScriptEngineFactory documentation for details.
+ *
+ * @author A. Sundararajan
+ * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
+ * @version $Id: VelocityScriptEngineFactory.java$
+ */
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.velocity.runtime.VelocityEngineVersion;
+
+public class VelocityScriptEngineFactory implements ScriptEngineFactory
+{
+
+ private static final String VELOCITY_NAME = "Velocity";
+ private static final String VELOCITY_VERSION = VelocityEngineVersion.VERSION;
+ private static final String VELOCITY_LANGUAGE = "VTL";
+
+ private static List<String> names;
+ private static List<String> extensions;
+ private static List<String> mimeTypes;
+
+ private static Properties parameters;
+
+ static
+ {
+ names = new ArrayList<>();
+ names.add("velocity");
+ names.add("Velocity");
+ names = Collections.unmodifiableList(names);
+ extensions = new ArrayList<>();
+ extensions.add("vm");
+ extensions.add("vtl");
+ extensions.add("vhtml");
+ extensions = Collections.unmodifiableList(extensions);
+ mimeTypes = new ArrayList<>();
+ mimeTypes.add("text/x-velocity");
+ mimeTypes = Collections.unmodifiableList(mimeTypes);
+ parameters = new Properties();
+ parameters.put(ScriptEngine.NAME, VELOCITY_NAME);
+ parameters.put(ScriptEngine.ENGINE_VERSION, VELOCITY_VERSION);
+ parameters.put(ScriptEngine.ENGINE, VELOCITY_NAME);
+ parameters.put(ScriptEngine.LANGUAGE, VELOCITY_LANGUAGE);
+ parameters.put(ScriptEngine.LANGUAGE_VERSION, VELOCITY_VERSION);
+ parameters.put("THREADING", "MULTITHREADED");
+ }
+
+ /**
+ * get engine name
+ * @return engine name, aka "Velocity"
+ */
+ @Override
+ public String getEngineName()
+ {
+ return VELOCITY_NAME;
+ }
+
+ /**
+ * get engine version
+ * @return engine version string
+ */
+ @Override
+ public String getEngineVersion()
+ {
+ return VELOCITY_VERSION;
+ }
+
+ /**
+ * get the list of file extensions handled by Velocity: vm, vtl, vhtml
+ * @return extensions list
+ */
+ @Override
+ public List<String> getExtensions()
+ {
+ return extensions;
+ }
+
+ /**
+ * get language name
+ * @return language name, aka "VTL"
+ */
+ @Override
+ public String getLanguageName()
+ {
+ return VELOCITY_NAME;
+ }
+
+ /**
+ * get language version (same as engine version)
+ * @return language version string
+ */
+ @Override
+ public String getLanguageVersion()
+ {
+ return VELOCITY_VERSION;
+ }
+
+ /**
+ * get Velocity syntax for calling method 'm' on object 'obj' with provided arguments
+ * @param obj
+ * @param m
+ * @param args
+ * @return VTL call ${obj.m(args...)}
+ */
+ @Override
+ public String getMethodCallSyntax(String obj, String m, String... args)
+ {
+ StringBuilder buf = new StringBuilder();
+ buf.append("${");
+ buf.append(obj);
+ buf.append(".");
+ buf.append(m);
+ buf.append("(");
+ if (args.length != 0)
+ {
+ int i = 0;
+ for (; i < args.length - 1; i++)
+ {
+ buf.append("$").append(args[i]);
+ buf.append(", ");
+ }
+ buf.append("$").append(args[i]);
+ }
+ buf.append(")}");
+ return buf.toString();
+ }
+
+ /**
+ * get the list of Velocity mime types
+ * @return singleton { 'text/x-velocity' }
+ */
+ @Override
+ public List<String> getMimeTypes()
+ {
+ return mimeTypes;
+ }
+
+ /**
+ * get the list of names
+ * @return { 'velocity', 'Velocity' }
+ */
+ @Override
+ public List<String> getNames()
+ {
+ return names;
+ }
+
+ /**
+ * get VTL expression used to display specified string
+ * @param toDisplay
+ * @return escaped string #[[toDisplay]]#
+ */
+ @Override
+ public String getOutputStatement(String toDisplay)
+ {
+ StringBuilder buf = new StringBuilder();
+ buf.append("#[[").append(toDisplay).append("]]#");
+ return buf.toString();
+ }
+
+ /**
+ * get engine parameter for provided key
+ * @param key
+ * @return found parameter, or null
+ */
+ @Override
+ public String getParameter(String key)
+ {
+ return parameters.getProperty(key);
+ }
+
+ /**
+ * get whole VTL program given VTL lines
+ * @param statements VTL lines
+ * @return lines concatenated with carriage returns
+ */
+ @Override
+ public String getProgram(String... statements)
+ {
+ StringBuilder buf = new StringBuilder();
+ for (String statement : statements)
+ {
+ buf.append(statement);
+ buf.append(System.lineSeparator());
+ }
+ return buf.toString();
+ }
+
+ /**
+ * get a Velocity script engine
+ * @return a new Velocity script engine
+ */
+ @Override
+ public ScriptEngine getScriptEngine()
+ {
+ return new VelocityScriptEngine(this);
+ }
+}
diff --git a/velocity-engine-scripting/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/velocity-engine-scripting/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
new file mode 100644
index 00000000..0a4afb48
--- /dev/null
+++ b/velocity-engine-scripting/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory
@@ -0,0 +1 @@
+org.apache.velocity.script.VelocityScriptEngineFactory
diff --git a/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/AbstractScriptTest.java b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/AbstractScriptTest.java
new file mode 100644
index 00000000..b702df78
--- /dev/null
+++ b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/AbstractScriptTest.java
@@ -0,0 +1,31 @@
+package org.apache.velocity.script.test;
+
+
+import junit.framework.TestCase;
+import org.apache.velocity.script.VelocityScriptEngineFactory;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+
+public abstract class AbstractScriptTest extends TestCase {
+ protected ScriptEngine engine;
+ protected ScriptEngineFactory engineFactory;
+ protected ScriptEngineManager manager;
+
+ @Override
+ public void setUp() {
+ manager = new ScriptEngineManager();
+ }
+
+ public void setupEngine(ScriptEngineFactory scriptEngineFactory){
+ manager.registerEngineName("velocity", scriptEngineFactory);
+ engine = manager.getEngineByName("velocity");
+ }
+
+ public void setupWithDefaultFactory() {
+ manager.registerEngineName("velocity", new VelocityScriptEngineFactory());
+ engine = manager.getEngineByName("velocity");
+ engineFactory = engine.getFactory();
+ }
+}
diff --git a/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/ScriptEngineTest.java b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/ScriptEngineTest.java
new file mode 100644
index 00000000..f7b9771f
--- /dev/null
+++ b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/ScriptEngineTest.java
@@ -0,0 +1,97 @@
+package org.apache.velocity.script.test;
+/*
+ * 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.script.VelocityScriptEngine;
+
+import javax.script.Bindings;
+import javax.script.Compilable;
+import javax.script.CompiledScript;
+import javax.script.ScriptContext;
+import javax.script.ScriptException;
+import java.io.StringWriter;
+
+public class ScriptEngineTest extends AbstractScriptTest {
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ super.setupWithDefaultFactory();
+ }
+
+ public void testBasicOpe() {
+
+ engine.put("name1", "value1");
+ Bindings engineScope = engine.getContext().getBindings(ScriptContext.ENGINE_SCOPE);
+ assertEquals("Engine#put should have same effect as context.put ", engineScope.get("name1"), "value1");
+
+ String val = engine.get("name1").toString();
+ assertEquals("Engine#get should have same effect as context.get ", engineScope.get("name1"), val);
+ }
+
+
+ public void testJSR223tException(){
+ try {
+ engine.get("");
+ fail("Cannot pass empty name");
+ } catch (IllegalArgumentException n) {
+ //Success
+ }
+
+ try {
+ engine.setContext(null);
+ fail("Cannot pass null to context");
+ } catch (NullPointerException n) {
+ //Success
+ }
+
+ }
+
+ public void testEngineEvals() throws ScriptException {
+ String path = System.getProperty("test.resources.dir");
+ engine.getContext().setWriter(new StringWriter());
+ engine.getContext().setAttribute(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, path + "/test-classes/velocity.properties", ScriptContext.ENGINE_SCOPE);
+ String script = "<html><body>#set( $foo = 'Velocity' )Hello $foo World!</body><html>";
+ Object result = engine.eval(script);
+ assertEquals(result.toString(), "<html><body>Hello Velocity World!</body><html>");
+ }
+
+ public void testCompilable() throws ScriptException
+ {
+ String path = System.getProperty("test.resources.dir");
+ engine.getContext().setWriter(new StringWriter());
+ engine.getContext().setAttribute(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, path + "/test-classes/velocity.properties", ScriptContext.ENGINE_SCOPE);
+ String script = "$foo";
+ engine.put("foo", "bar");
+ CompiledScript compiled = ((Compilable)engine).compile(script);
+ Object result = compiled.eval();
+ assertEquals(result.toString(), "bar");
+ }
+
+ public void testContext() throws ScriptException
+ {
+ String path = System.getProperty("test.resources.dir");
+ engine.getContext().setWriter(new StringWriter());
+ engine.getContext().setAttribute(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, path + "/test-classes/velocity.properties", ScriptContext.ENGINE_SCOPE);
+ String script = "$context.class.name $context.writer.class.name $context.reader.class.name $context.errorWriter.class.name";
+ String result = engine.eval(script).toString();
+ assertEquals("javax.script.SimpleScriptContext java.io.StringWriter java.io.InputStreamReader java.io.PrintWriter", result);
+ }
+
+}
diff --git a/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/VelocityScriptContextTest.java b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/VelocityScriptContextTest.java
new file mode 100644
index 00000000..8031cd0e
--- /dev/null
+++ b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/VelocityScriptContextTest.java
@@ -0,0 +1,159 @@
+package org.apache.velocity.script.test;
+
+/*
+* 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 javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.SimpleBindings;
+import java.util.List;
+
+
+public class VelocityScriptContextTest extends AbstractScriptTest {
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ super.setupWithDefaultFactory();
+ }
+
+ public void testInitialScopes() {
+ List<Integer> defaultScopes = engine.getContext().getScopes();
+ assertEquals(defaultScopes.size(), 2);
+ assertTrue(defaultScopes.contains(ScriptContext.ENGINE_SCOPE));
+ assertTrue(defaultScopes.contains(ScriptContext.GLOBAL_SCOPE));
+ }
+
+ public void testScopes() {
+
+ Bindings velocityBindings = new SimpleBindings();
+ engine.getContext().setBindings(velocityBindings, ScriptContext.ENGINE_SCOPE);
+ assertNotNull(engine.getBindings(ScriptContext.ENGINE_SCOPE));
+ assertNotNull("Engines Registered through manager sets the global scope", engine.getBindings(ScriptContext.GLOBAL_SCOPE));
+ }
+
+ public void testEngineScopeAttributes() {
+
+ ScriptContext context = engine.getContext();
+ context.setAttribute("engine-prop1", "engine-value1", ScriptContext.ENGINE_SCOPE);
+
+ assertEquals(context.getAttribute("engine-prop1", ScriptContext.ENGINE_SCOPE), "engine-value1");
+ assertNull(context.getAttribute("engine-prop1", ScriptContext.GLOBAL_SCOPE));
+
+ context.removeAttribute("engine-prop1", ScriptContext.ENGINE_SCOPE);
+ assertNull(context.getAttribute("engine-prop1", ScriptContext.ENGINE_SCOPE));
+
+
+ }
+
+ public void testGlobalScopeAttributes() {
+
+ ScriptContext context = engine.getContext();
+ context.setAttribute("global-prop1", "global-value1", ScriptContext.GLOBAL_SCOPE);
+
+ assertEquals(context.getAttribute("global-prop1", ScriptContext.GLOBAL_SCOPE), "global-value1");
+ assertNull(context.getAttribute("global-prop1", ScriptContext.ENGINE_SCOPE));
+
+ context.removeAttribute("global-prop1", ScriptContext.GLOBAL_SCOPE);
+ assertNull(context.getAttribute("global-prop1", ScriptContext.GLOBAL_SCOPE));
+
+ }
+
+ public void testJSRExceptions() {
+
+ ScriptContext context = engine.getContext();
+ context.setAttribute("global-prop", "global-value", ScriptContext.GLOBAL_SCOPE);
+ int invalidScope = 99;
+
+ try {
+ context.setBindings(null, ScriptContext.ENGINE_SCOPE);
+ fail("Cannot pass null binding for engine scope");
+ } catch (NullPointerException n) {
+ //Success
+ }
+
+ try {
+ context.setBindings(new SimpleBindings(), invalidScope);
+ fail("Cannot pass invalid scope");
+ } catch (IllegalArgumentException n) {
+ //Success
+ }
+ try {
+ context.getBindings(invalidScope);
+ fail("Cannot pass invalid scope");
+ } catch (IllegalArgumentException n) {
+ //Success
+ }
+
+
+ try {
+ context.setAttribute(null, "value", ScriptContext.ENGINE_SCOPE);
+
+ fail("Name cannot be null");
+ } catch (NullPointerException n) {
+ //Success
+ }
+ try {
+ context.setAttribute("name1", "value", invalidScope);
+
+ fail("Cannot pass invalid scope");
+ } catch (IllegalArgumentException n) {
+ //Success
+ }
+
+
+
+ try {
+ context.getAttribute(null, ScriptContext.ENGINE_SCOPE);
+
+ fail("Name cannot be null");
+ } catch (NullPointerException n) {
+ //Success
+ }
+ try {
+ context.getAttribute("name1", invalidScope);
+
+ fail("Cannot pass invalid scope");
+ } catch (IllegalArgumentException n) {
+ //Success
+ }
+
+
+ try {
+ context.removeAttribute(null, ScriptContext.ENGINE_SCOPE);
+
+ fail("Name cannot be null");
+ } catch (NullPointerException n) {
+ //Success
+ }
+ try {
+ context.removeAttribute("name1", invalidScope);
+
+ fail("Cannot pass invalid scope");
+ } catch (IllegalArgumentException n) {
+ //Success
+ }
+
+
+ context.setAttribute("prop2","engine-value2",ScriptContext.ENGINE_SCOPE);
+ context.setAttribute("prop2","global-value2",ScriptContext.GLOBAL_SCOPE);
+ assertEquals("Should return lowest scope value binded value",context.getAttribute("prop2"),"engine-value2");
+
+ }
+}
diff --git a/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/resources/eventtool.vm b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/resources/eventtool.vm
new file mode 100644
index 00000000..68e33fe8
--- /dev/null
+++ b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/resources/eventtool.vm
@@ -0,0 +1,5 @@
+$event;
+
+Event Created by $event.getName()
+Event Created on $event.getDate()
+Event ID is $event.getID()
diff --git a/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/CustomEvent.java b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/CustomEvent.java
new file mode 100644
index 00000000..b76970ee
--- /dev/null
+++ b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/CustomEvent.java
@@ -0,0 +1,50 @@
+package org.apache.velocity.script.test.tools;
+/*
+ * 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.util.Date;
+
+public class CustomEvent {
+
+ String name = "";
+ Date date;
+
+ public CustomEvent(String name) {
+ this.name = name;
+ this.date = new Date();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public long getID(){
+ return date.getTime();
+ }
+
+ @Override
+ public String toString(){
+ return "This is a test event template: created by " + name + " on " + date.toString();
+ }
+
+} \ No newline at end of file
diff --git a/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/EventToolTest.java b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/EventToolTest.java
new file mode 100644
index 00000000..eef11eb6
--- /dev/null
+++ b/velocity-engine-scripting/src/test/java/org/apache/velocity/script/test/tools/EventToolTest.java
@@ -0,0 +1,61 @@
+package org.apache.velocity.script.test.tools;
+/*
+ * 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.script.VelocityScriptEngine;
+import org.apache.velocity.script.test.AbstractScriptTest;
+
+import javax.script.ScriptContext;
+import javax.script.ScriptException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Properties;
+
+public class EventToolTest extends AbstractScriptTest {
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ super.setupWithDefaultFactory();
+ }
+
+ public void testHelloWorldTool() throws ScriptException {
+ ScriptContext context = engine.getContext();
+ Properties properties = new Properties();
+ properties.put("resource.loader", "class");
+ properties.put("class.resource.loader.description", "Template Class Loader");
+ properties.put("class.resource.loader.class",
+ "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+ CustomEvent event = new CustomEvent("MyEvent");
+ context.getBindings(ScriptContext.ENGINE_SCOPE).put("event", event);
+ context.setAttribute(VelocityScriptEngine.VELOCITY_PROPERTIES_KEY, properties, ScriptContext.ENGINE_SCOPE);
+ context.setAttribute(VelocityScriptEngine.FILENAME, "eventtool.vm", ScriptContext.ENGINE_SCOPE);
+ Writer writer = new StringWriter();
+ context.setWriter(writer);
+ engine.eval("$event;\n" +
+ "Event Created by $event.getName()\n" +
+ "Event Created on $event.getDate()\n" +
+ "Event ID is $event.getID()");
+ // check string start
+ String check = "This is a test event template: created by MyEvent on ";
+ assertEquals(writer.toString().substring(0, check.length()), check);
+ }
+
+
+}
diff --git a/velocity-engine-scripting/src/test/resources/velocity.properties b/velocity-engine-scripting/src/test/resources/velocity.properties
new file mode 100644
index 00000000..d4f683db
--- /dev/null
+++ b/velocity-engine-scripting/src/test/resources/velocity.properties
@@ -0,0 +1,17 @@
+# 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.
+runtime.log = velocity_example.log