diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java')
-rwxr-xr-x | velocity-engine-core/src/main/java/org/apache/velocity/runtime/directive/Block.java | 209 |
1 files changed, 209 insertions, 0 deletions
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; + } + } +} |