001: package org.apache.velocity.runtime.directive;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.IOException;
023: import java.io.Writer;
024: import java.util.Iterator;
025:
026: import org.apache.velocity.app.event.EventCartridge;
027: import org.apache.velocity.context.Context;
028: import org.apache.velocity.context.InternalContextAdapter;
029: import org.apache.velocity.exception.MethodInvocationException;
030: import org.apache.velocity.exception.ParseErrorException;
031: import org.apache.velocity.exception.ResourceNotFoundException;
032: import org.apache.velocity.exception.TemplateInitException;
033: import org.apache.velocity.runtime.RuntimeConstants;
034: import org.apache.velocity.runtime.RuntimeServices;
035: import org.apache.velocity.runtime.parser.node.ASTReference;
036: import org.apache.velocity.runtime.parser.node.Node;
037: import org.apache.velocity.runtime.parser.node.SimpleNode;
038: import org.apache.velocity.runtime.resource.Resource;
039: import org.apache.velocity.util.introspection.Info;
040: import org.apache.velocity.util.introspection.IntrospectionCacheData;
041:
042: /**
043: * Foreach directive used for moving through arrays,
044: * or objects that provide an Iterator.
045: *
046: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
047: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
048: * @author Daniel Rall
049: * @version $Id: Foreach.java 479060 2006-11-25 00:30:19Z henning $
050: */
051: public class Foreach extends Directive {
052: /**
053: * A special context to use when the foreach iterator returns a null. This
054: * is required since the standard context may not support nulls.
055: * All puts and gets are passed through, except for the foreach iterator key.
056: */
057: protected static class NullHolderContext implements
058: InternalContextAdapter {
059: private InternalContextAdapter innerContext = null;
060: private String loopVariableKey = "";
061: private boolean active = true;
062:
063: /**
064: * Create the context as a wrapper to be used within the foreach
065: * @param key the reference used in the foreach
066: * @param context the parent context
067: */
068: private NullHolderContext(String key,
069: InternalContextAdapter context) {
070: innerContext = context;
071: if (key != null)
072: loopVariableKey = key;
073: }
074:
075: /**
076: * Get an object from the context, or null if the key is equal to the loop variable
077: * @see org.apache.velocity.context.InternalContextAdapter#get(java.lang.String)
078: * @exception MethodInvocationException passes on potential exception from reference method call
079: */
080: public Object get(String key) throws MethodInvocationException {
081: return (active && loopVariableKey.equals(key)) ? null
082: : innerContext.get(key);
083: }
084:
085: /**
086: * @see org.apache.velocity.context.InternalContextAdapter#put(java.lang.String key, java.lang.Object value)
087: */
088: public Object put(String key, Object value) {
089: if (loopVariableKey.equals(key) && (value == null)) {
090: active = true;
091: }
092:
093: return innerContext.put(key, value);
094: }
095:
096: /**
097: * Allows callers to explicitly put objects in the local context.
098: * Objects added to the context through this method always end up
099: * in the top-level context of possible wrapped contexts.
100: *
101: * @param key name of item to set.
102: * @param value object to set to key.
103: * @see org.apache.velocity.context.InternalWrapperContext#localPut(String, Object)
104: */
105: public Object localPut(final String key, final Object value) {
106: return put(key, value);
107: }
108:
109: /**
110: * Does the context contain the key
111: * @see org.apache.velocity.context.InternalContextAdapter#containsKey(java.lang.Object key)
112: */
113: public boolean containsKey(Object key) {
114: return innerContext.containsKey(key);
115: }
116:
117: /**
118: * @see org.apache.velocity.context.InternalContextAdapter#getKeys()
119: */
120: public Object[] getKeys() {
121: return innerContext.getKeys();
122: }
123:
124: /**
125: * Remove an object from the context
126: * @see org.apache.velocity.context.InternalContextAdapter#remove(java.lang.Object key)
127: */
128: public Object remove(Object key) {
129: if (loopVariableKey.equals(key)) {
130: active = false;
131: }
132: return innerContext.remove(key);
133: }
134:
135: /**
136: * @see org.apache.velocity.context.InternalContextAdapter#pushCurrentTemplateName(java.lang.String s)
137: */
138: public void pushCurrentTemplateName(String s) {
139: innerContext.pushCurrentTemplateName(s);
140: }
141:
142: /**
143: * @see org.apache.velocity.context.InternalContextAdapter#popCurrentTemplateName()
144: */
145: public void popCurrentTemplateName() {
146: innerContext.popCurrentTemplateName();
147: }
148:
149: /**
150: * @see org.apache.velocity.context.InternalContextAdapter#getCurrentTemplateName()
151: */
152: public String getCurrentTemplateName() {
153: return innerContext.getCurrentTemplateName();
154: }
155:
156: /**
157: * @see org.apache.velocity.context.InternalContextAdapter#getTemplateNameStack()
158: */
159: public Object[] getTemplateNameStack() {
160: return innerContext.getTemplateNameStack();
161: }
162:
163: /**
164: * @see org.apache.velocity.context.InternalContextAdapter#icacheGet(java.lang.Object key)
165: */
166: public IntrospectionCacheData icacheGet(Object key) {
167: return innerContext.icacheGet(key);
168: }
169:
170: /**
171: * @see org.apache.velocity.context.InternalContextAdapter#icachePut(java.lang.Object key, org.apache.velocity.util.introspection.IntrospectionCacheData o)
172: */
173: public void icachePut(Object key, IntrospectionCacheData o) {
174: innerContext.icachePut(key, o);
175: }
176:
177: /**
178: * @see org.apache.velocity.context.InternalContextAdapter#setCurrentResource(org.apache.velocity.runtime.resource.Resource r)
179: */
180: public void setCurrentResource(Resource r) {
181: innerContext.setCurrentResource(r);
182: }
183:
184: /**
185: * @see org.apache.velocity.context.InternalContextAdapter#getCurrentResource()
186: */
187: public Resource getCurrentResource() {
188: return innerContext.getCurrentResource();
189: }
190:
191: /**
192: * @see org.apache.velocity.context.InternalContextAdapter#getBaseContext()
193: */
194: public InternalContextAdapter getBaseContext() {
195: return innerContext.getBaseContext();
196: }
197:
198: /**
199: * @see org.apache.velocity.context.InternalContextAdapter#getInternalUserContext()
200: */
201: public Context getInternalUserContext() {
202: return innerContext.getInternalUserContext();
203: }
204:
205: /**
206: * @see org.apache.velocity.context.InternalContextAdapter#attachEventCartridge(org.apache.velocity.app.event.EventCartridge ec)
207: */
208: public EventCartridge attachEventCartridge(EventCartridge ec) {
209: EventCartridge cartridge = innerContext
210: .attachEventCartridge(ec);
211:
212: return cartridge;
213: }
214:
215: /**
216: * @see org.apache.velocity.context.InternalContextAdapter#getEventCartridge()
217: */
218: public EventCartridge getEventCartridge() {
219: return innerContext.getEventCartridge();
220: }
221:
222: /**
223: * @see org.apache.velocity.context.InternalContextAdapter#getAllowRendering()
224: */
225: public boolean getAllowRendering() {
226: return innerContext.getAllowRendering();
227: }
228:
229: /**
230: * @see org.apache.velocity.context.InternalContextAdapter#setAllowRendering(boolean v)
231: */
232: public void setAllowRendering(boolean v) {
233: innerContext.setAllowRendering(v);
234: }
235:
236: }
237:
238: /**
239: * Return name of this directive.
240: * @return The name of this directive.
241: */
242: public String getName() {
243: return "foreach";
244: }
245:
246: /**
247: * Return type of this directive.
248: * @return The type of this directive.
249: */
250: public int getType() {
251: return BLOCK;
252: }
253:
254: /**
255: * The name of the variable to use when placing
256: * the counter value into the context. Right
257: * now the default is $velocityCount.
258: */
259: private String counterName;
260:
261: /**
262: * What value to start the loop counter at.
263: */
264: private int counterInitialValue;
265:
266: /**
267: * The maximum number of times we're allowed to loop.
268: */
269: private int maxNbrLoops;
270:
271: /**
272: * The reference name used to access each
273: * of the elements in the list object. It
274: * is the $item in the following:
275: *
276: * #foreach ($item in $list)
277: *
278: * This can be used class wide because
279: * it is immutable.
280: */
281: private String elementKey;
282:
283: /**
284: * immutable, so create in init
285: */
286: protected Info uberInfo;
287:
288: /**
289: * simple init - init the tree and get the elementKey from
290: * the AST
291: * @param rs
292: * @param context
293: * @param node
294: * @throws TemplateInitException
295: */
296: public void init(RuntimeServices rs,
297: InternalContextAdapter context, Node node)
298: throws TemplateInitException {
299: super .init(rs, context, node);
300:
301: counterName = rsvc.getString(RuntimeConstants.COUNTER_NAME);
302: counterInitialValue = rsvc
303: .getInt(RuntimeConstants.COUNTER_INITIAL_VALUE);
304: maxNbrLoops = rsvc.getInt(RuntimeConstants.MAX_NUMBER_LOOPS,
305: Integer.MAX_VALUE);
306: if (maxNbrLoops < 1) {
307: maxNbrLoops = Integer.MAX_VALUE;
308: }
309:
310: /*
311: * this is really the only thing we can do here as everything
312: * else is context sensitive
313: */
314:
315: SimpleNode sn = (SimpleNode) node.jjtGetChild(0);
316:
317: if (sn instanceof ASTReference) {
318: elementKey = ((ASTReference) sn).getRootString();
319: } else {
320: /*
321: * the default, error-prone way which we'll remove
322: * TODO : remove if all goes well
323: */
324: elementKey = sn.getFirstToken().image.substring(1);
325: }
326:
327: /*
328: * make an uberinfo - saves new's later on
329: */
330:
331: uberInfo = new Info(context.getCurrentTemplateName(),
332: getLine(), getColumn());
333: }
334:
335: /**
336: * renders the #foreach() block
337: * @param context
338: * @param writer
339: * @param node
340: * @return True if the directive rendered successfully.
341: * @throws IOException
342: * @throws MethodInvocationException
343: * @throws ResourceNotFoundException
344: * @throws ParseErrorException
345: */
346: public boolean render(InternalContextAdapter context,
347: Writer writer, Node node) throws IOException,
348: MethodInvocationException, ResourceNotFoundException,
349: ParseErrorException {
350: /*
351: * do our introspection to see what our collection is
352: */
353:
354: Object listObject = node.jjtGetChild(2).value(context);
355:
356: if (listObject == null)
357: return false;
358:
359: Iterator i = null;
360:
361: try {
362: i = rsvc.getUberspect().getIterator(listObject, uberInfo);
363: }
364: /**
365: * pass through application level runtime exceptions
366: */
367: catch (RuntimeException e) {
368: throw e;
369: } catch (Exception ee) {
370: rsvc.getLog().error("Error getting iterator for #foreach",
371: ee);
372: }
373:
374: if (i == null) {
375: return false;
376: }
377:
378: int counter = counterInitialValue;
379: boolean maxNbrLoopsExceeded = false;
380:
381: /*
382: * save the element key if there is one, and the loop counter
383: */
384: Object o = context.get(elementKey);
385: Object savedCounter = context.get(counterName);
386:
387: /*
388: * Instantiate the null holder context if a null value
389: * is returned by the foreach iterator. Only one instance is
390: * created - it's reused for every null value.
391: */
392: NullHolderContext nullHolderContext = null;
393:
394: while (!maxNbrLoopsExceeded && i.hasNext()) {
395: // TODO: JDK 1.4+ -> valueOf()
396: context.localPut(counterName, new Integer(counter));
397: Object value = i.next();
398: context.localPut(elementKey, value);
399:
400: /*
401: * If the value is null, use the special null holder context
402: */
403: if (value == null) {
404: if (nullHolderContext == null) {
405: // lazy instantiation
406: nullHolderContext = new NullHolderContext(
407: elementKey, context);
408: }
409: node.jjtGetChild(3).render(nullHolderContext, writer);
410: } else {
411: node.jjtGetChild(3).render(context, writer);
412: }
413: counter++;
414:
415: // Determine whether we're allowed to continue looping.
416: // ASSUMPTION: counterInitialValue is not negative!
417: maxNbrLoopsExceeded = (counter - counterInitialValue) >= maxNbrLoops;
418: }
419:
420: /*
421: * restores the loop counter (if we were nested)
422: * if we have one, else just removes
423: */
424:
425: if (savedCounter != null) {
426: context.put(counterName, savedCounter);
427: } else {
428: context.remove(counterName);
429: }
430:
431: /*
432: * restores element key if exists
433: * otherwise just removes
434: */
435:
436: if (o != null) {
437: context.put(elementKey, o);
438: } else {
439: context.remove(elementKey);
440: }
441:
442: return true;
443: }
444: }
|