1 package org.apache.velocity.runtime.directive;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.IOException;
23 import java.io.StringWriter;
24 import java.io.Writer;
25
26 import org.apache.commons.lang.text.StrBuilder;
27 import org.apache.velocity.context.InternalContextAdapter;
28 import org.apache.velocity.exception.TemplateInitException;
29 import org.apache.velocity.runtime.Renderable;
30 import org.apache.velocity.runtime.RuntimeServices;
31 import org.apache.velocity.runtime.log.Log;
32 import org.apache.velocity.runtime.parser.node.Node;
33
34 /**
35 * Directive that puts an unrendered AST block in the context
36 * under the specified key, postponing rendering until the
37 * reference is used and rendered.
38 *
39 * @author Andrew Tetlaw
40 * @author Nathan Bubna
41 * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a>
42 * @since 1.7
43 * @version $Id: Block.java 686842 2008-08-18 18:29:31Z nbubna $
44 */
45 public abstract class Block extends Directive
46 {
47 protected Node block;
48 protected Log log;
49 protected int maxDepth;
50 protected String key;
51
52 /**
53 * Return type of this directive.
54 */
55 public int getType()
56 {
57 return BLOCK;
58 }
59
60 /**
61 * simple init - get the key
62 */
63 public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
64 throws TemplateInitException
65 {
66 super.init(rs, context, node);
67
68 log = rs.getLog();
69
70 /**
71 * No checking is done. We just grab the last child node and assume
72 * that it's the block!
73 */
74 block = node.jjtGetChild(node.jjtGetNumChildren() - 1);
75 }
76
77 public boolean render(InternalContextAdapter context, Writer writer)
78 {
79 preRender(context);
80 try
81 {
82 return block.render(context, writer);
83 }
84 catch (IOException e)
85 {
86 String msg = "Failed to render " + id(context) + " to writer "
87 + " at " + Log.formatFileString(this);
88
89 log.error(msg, e);
90 throw new RuntimeException(msg, e);
91 }
92 catch (StopCommand stop)
93 {
94 if (!stop.isFor(this))
95 {
96 throw stop;
97 }
98 return true;
99 }
100 finally
101 {
102 postRender(context);
103 }
104 }
105
106 /**
107 * Creates a string identifying the source and location of the block
108 * definition, and the current template being rendered if that is
109 * different.
110 */
111 protected String id(InternalContextAdapter context)
112 {
113 StrBuilder str = new StrBuilder(100)
114 .append("block $").append(key);
115 if (!context.getCurrentTemplateName().equals(getTemplateName()))
116 {
117 str.append(" used in ").append(context.getCurrentTemplateName());
118 }
119 return str.toString();
120 }
121
122 /**
123 * actual class placed in the context, holds the context
124 * being used for the render, as well as the parent (which already holds
125 * everything else we need).
126 */
127 public static class Reference implements Renderable
128 {
129 private InternalContextAdapter context;
130 private Block parent;
131 private int depth;
132
133 public Reference(InternalContextAdapter context, Block parent)
134 {
135 this.context = context;
136 this.parent = parent;
137 }
138
139 /**
140 * Render the AST of this block into the writer using the context.
141 */
142 public boolean render(InternalContextAdapter context, Writer writer)
143 {
144 depth++;
145 if (depth > parent.maxDepth)
146 {
147 /* this is only a debug message, as recursion can
148 * happen in quasi-innocent situations and is relatively
149 * harmless due to how we handle it here.
150 * this is more to help anyone nuts enough to intentionally
151 * use recursive block definitions and having problems
152 * pulling it off properly.
153 */
154 parent.log.debug("Max recursion depth reached for " + parent.id(context)
155 + " at " + Log.formatFileString(parent));
156 depth--;
157 return false;
158 }
159 else
160 {
161 parent.render(context, writer);
162 depth--;
163 return true;
164 }
165 }
166
167 public String toString()
168 {
169 Writer writer = new StringWriter();
170 if (render(context, writer))
171 {
172 return writer.toString();
173 }
174 return null;
175 }
176 }
177 }