You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by co...@apache.org on 2003/07/08 07:48:53 UTC

cvs commit: cocoon-2.1/src/webapp/WEB-INF cocoon.xconf

coliver     2003/07/07 22:48:53

  Modified:    lib      jars.xml
               src/scratchpad/webapp/samples/garbage/calc sitemap.xmap
               src/scratchpad/webapp/samples/jxforms sitemap.xmap
               src/scratchpad/webapp/samples/petstore sitemap.xmap
               src/webapp/WEB-INF cocoon.xconf
  Added:       lib/core commons-jexl-1.0-beta-1.jar
               src/java/org/apache/cocoon/components/flow/javascript/fom
                        FOM_Cocoon.java FOM_JavaScriptFlowHelper.java
                        FOM_JavaScriptInterpreter.java fom_system.js
               src/java/org/apache/cocoon/generation
                        JXTemplateGenerator.java
               src/java/org/apache/cocoon/transformation
                        JXTemplateTransformer.java
  Removed:     src/scratchpad/lib commons-jexl-1.0-beta-1.jar
               src/scratchpad/src/org/apache/cocoon/components/flow/javascript/fom
                        FOM_Cocoon.java FOM_JavaScript.xconf
                        FOM_JavaScriptFlowHelper.java
                        FOM_JavaScriptInterpreter.java fom_system.js
               src/scratchpad/src/org/apache/cocoon/generation
                        JXTemplateGenerator.java
               src/scratchpad/src/org/apache/cocoon/transformation
                        JXTemplateTransformer.java
  Log:
  Moving JXTemplate* and FOM to core
  
  Revision  Changes    Path
  1.60      +9 -1      cocoon-2.1/lib/jars.xml
  
  Index: jars.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/lib/jars.xml,v
  retrieving revision 1.59
  retrieving revision 1.60
  diff -u -r1.59 -r1.60
  --- jars.xml	5 Jul 2003 16:33:06 -0000	1.59
  +++ jars.xml	8 Jul 2003 05:48:52 -0000	1.60
  @@ -567,6 +567,14 @@
     <lib>core/commons-jxpath-1.1.jar</lib>
     <homepage>http://jakarta.apache.org/commons/jxpath/</homepage>
    </file>
  + <file>
  +
  +  <title>Jakarta Commons Jexl</title>
  +  <description>JSTL Expression Language</description>
  +  <used-by>JXTemplateGenerator</used-by>
  +  <lib>core/commons-jexl-1.0-beta-1.jar</lib>
  +  <homepage>http://jakarta.apache.org/commons/jexl/</homepage>
  + </file>
   
    <file>
     <title>Eclipse Java Development Tools Core</title>
  
  
  
  1.1                  cocoon-2.1/lib/core/commons-jexl-1.0-beta-1.jar
  
  	<<Binary file>>
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/flow/javascript/fom/FOM_Cocoon.java
  
  Index: FOM_Cocoon.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. 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.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, 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.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.components.flow.javascript.fom;
  
  import java.io.OutputStream;
  import java.util.Map;
  import java.util.List;
  import java.util.LinkedList;
  import java.util.Enumeration;
  import java.util.Iterator;
  
  import org.apache.avalon.framework.component.Component;
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.logger.Logger;
  import org.apache.cocoon.components.flow.ContinuationsManager;
  import org.apache.cocoon.components.flow.WebContinuation;
  import org.apache.cocoon.environment.Cookie;
  import org.apache.cocoon.environment.Environment;
  import org.apache.cocoon.environment.ObjectModelHelper;
  import org.apache.cocoon.environment.Request;
  import org.apache.cocoon.environment.Response;
  import org.apache.cocoon.environment.Session;
  import org.mozilla.javascript.JavaScriptException;
  import org.mozilla.javascript.Script;
  import org.mozilla.javascript.Scriptable;
  import org.mozilla.javascript.ScriptableObject;
  import org.mozilla.javascript.Undefined;
  import org.mozilla.javascript.Wrapper;
  import org.mozilla.javascript.NativeArray;
  import org.mozilla.javascript.continuations.Continuation;
  import org.apache.avalon.framework.logger.Logger;
  
  /**
   * Implementation of FOM (Flow Object Model).
   *
   * @since 2.1 
   * @author <a href="mailto:coliver.at.apache.org">Christopher Oliver</a>
   * @author <a href="mailto:reinhard.at.apache.org">Reinhard P�tz</a>
   * @version CVS $Id: FOM_Cocoon.java,v 1.1 2003/07/08 05:48:52 coliver Exp $
   */
  
  public class FOM_Cocoon extends ScriptableObject {
  
      private FOM_JavaScriptInterpreter interpreter;
  
      private Environment environment;
      private ComponentManager componentManager;
      private Logger logger;
  
      private FOM_Request request;
      private FOM_Response response;
      private FOM_Session session;
      private FOM_Context context;
      private Scriptable parameters;
      private FOM_Log log;
  
      private WebContinuation lastContinuation;
  
      public String getClassName() {
          return "FOM_Cocoon";
      }
  
      // Called by FOM_JavaScriptInterpreter
      static void init(Scriptable scope) throws Exception {
          defineClass(scope, FOM_Cocoon.class);
          defineClass(scope, FOM_Request.class);
          defineClass(scope, FOM_Response.class);
          defineClass(scope, FOM_Cookie.class);
          defineClass(scope, FOM_Session.class);
          defineClass(scope, FOM_Context.class);
          defineClass(scope, FOM_Log.class);
          defineClass(scope, FOM_WebContinuation.class);
      }
  
      void setup(FOM_JavaScriptInterpreter interp,
                        Environment env, 
                        ComponentManager manager,
                        Logger logger) {
          this.interpreter = interp;
          this.environment = env;
          this.componentManager = manager;
          this.logger = logger;
      }
  
      void invalidate() {
          this.request = null;
          this.response = null;
          this.session = null;
          this.context = null;
          this.log = null;
          this.logger = null;
          this.componentManager = null;
          this.environment = null;
          this.interpreter = null;
      }
  
  
      private FOM_WebContinuation forwardTo(String uri, Object bizData,
                                            Continuation continuation) 
          throws Exception {
          WebContinuation wk = null;
          if (continuation != null) {
              ContinuationsManager contMgr = (ContinuationsManager)
                  componentManager.lookup(ContinuationsManager.ROLE);
              wk = lastContinuation = 
                  contMgr.createWebContinuation(continuation,
                                                lastContinuation,
                                                0);
          }
          
          String redUri = uri;
          
          if(! uri.startsWith( "cocoon://" ) ) {
              redUri = "cocoon://" + this.environment.getURIPrefix() + uri;
          }
          
          interpreter.forwardTo(getParentScope(), this, redUri,
                                bizData, wk, environment);
  
          FOM_WebContinuation result = null;
          if (wk != null) {
              result = new FOM_WebContinuation(wk);
              result.setParentScope(getParentScope());
              result.setPrototype(getClassPrototype(getParentScope(),
                                                    result.getClassName()));
          }
          return result;
      }
  
      public FOM_WebContinuation jsFunction_sendPage(String uri, 
                                                     Object obj, 
                                                     Object continuation) 
          throws Exception {
          return forwardTo(uri, obj, (Continuation)unwrap(continuation)); 
      }
                                      
  
      public void jsFunction_processPipelineTo(String uri,
                                               Object map,
                                               Object outputStream) 
          throws Exception {
          if (!(unwrap(outputStream) instanceof OutputStream)) {
              throw new JavaScriptException("expected a java.io.OutputStream instead of " + outputStream);
          }
          interpreter.process(getParentScope(), this, uri, map, 
                              (OutputStream)unwrap(outputStream), 
                              environment);
      }
  
      public void jsFunction_redirectTo(String uri) throws Exception {
          environment.redirect(false, uri);
      }
  
  /*
  
   NOTE (SM): These are the hooks to the future FOM Event Model that will be
   designed in the future. It has been postponed because we think
   there are more important things to do at the moment, but these
   are left here to indicate that they are planned.
   
      public void jsFunction_addEventListener(String eventName, 
                                              Object function) {
          // what is this?
      }
      
      public void jsFunction_removeEventListener(String eventName,
                                                 Object function) {
          // what is this?
      }
      
  */
  
      /**
       * Access components.
       * 
       * TODO: Do we want to restrict the access of sitemap components? (RP)
       * TODO: Do we want to raise an error or return null? (RP)
       */  
      public Object jsFunction_getComponent(String id) 
          throws Exception { 
          return componentManager.lookup(id);
      }
      
      /**
       * Release pooled components.
       * 
       * @param component - an <code>Object</code> that is an instance 
       * of <code>org.apache.avalon.framework.component.Component</code>
       */
      public void jsFunction_releaseComponent( Object component ) throws Exception {
          try {
              this.componentManager.release( (Component) unwrap(component) );
          } catch( ClassCastException cce ) {
              throw new JavaScriptException( "Only components can be released!" );
          }
      }
  
      /**
       * Load the script file specified as argument.
       *
       * @param filename a <code>String</code> value
       * @return an <code>Object</code> value
       * @exception JavaScriptException if an error occurs
       */
      public Object jsFunction_load( String filename ) 
          throws Exception {
          org.mozilla.javascript.Context cx = 
              org.mozilla.javascript.Context.getCurrentContext();
          Scriptable scope = getParentScope();
          Script script = interpreter.compileScript( cx, 
                                                     environment,
                                                     filename );
          return script.exec( cx, scope );
      }    
          
      public static class FOM_Request extends ScriptableObject {
  
          Request request;
  
          public FOM_Request() {
              // prototype ctor
          }
  
          public FOM_Request(Object request) {
              this.request = (Request)unwrap(request);
          }
  
          public String getClassName() {
              return "FOM_Request";
          }
  
          public Object jsFunction_get(String name) {
              return request.get(name);
          }
  
          public Object jsFunction_getAttribute(String name) {
              return request.getAttribute(name);
          }
  
          public void jsFunction_removeAttribute(String name) {
              request.removeAttribute(name);
          }
  
          public void jsFunction_setAttribute(String name,
                                              Object value) {
              request.setAttribute(name, unwrap(value));
          }
  
          public Object get(String name, Scriptable start) {
              Object result = super.get(name, start);
              if (result == NOT_FOUND && request != null) {
                  result = request.getParameter(name);
                  if (result == null) {
                      result = NOT_FOUND;
                  }
              }
              return result;
          }
  
          public Object[] getIds() {
              if (request != null) {
                  List list = new LinkedList();
                  Enumeration e = request.getAttributeNames();
                  while (e.hasMoreElements()) {
                      list.add(e.nextElement());
                  }
                  Object[] result = new Object[list.size()];
                  list.toArray(result);
                  return result;
              }
              return super.getIds();
          }
  
          public String jsFunction_getCharacterEncoding() {
              return request.getCharacterEncoding();
          }
  
          public void jsFunction_setCharacterEncoding(String value) 
              throws Exception {
              request.setCharacterEncoding(value);
          }
  
          public int jsFunction_getContentLength() {
              return request.getContentLength();
          }
  
          public String jsFunction_getContentType() {
              return request.getContentType();
          }
  
          public String jsFunction_getParameter(String name) {
              return request.getParameter(name);
          }
  
          public Object jsFunction_getParameterValues(String name) {
              return request.getParameterValues(name);
          }
  
          public Object jsFunction_getParameterNames() {
              return request.getParameterNames();
          }
  
          public String jsFunction_getAuthType() {
              return request.getAuthType();
          }
          
          public String jsFunction_getProtocol() {
              return request.getProtocol();
          }
      }
  
      public static class FOM_Cookie extends ScriptableObject {
  
          Cookie cookie;
  
          public FOM_Cookie() {
              // prototype ctor
          }
  
          public FOM_Cookie(Object cookie) {
              this.cookie = (Cookie)unwrap(cookie);
          }
  
          public String getClassName() {
              return "FOM_Cookie";
          }
  
          public String jsGet_name() {
              return cookie.getName();
          }
  
          public int jsGet_version() {
              return cookie.getVersion();
          }
  
          public void jsSet_version(int value) {
              cookie.setVersion(value);
          }
  
          public String jsGet_value() {
              return cookie.getValue();
          }
  
          public void jsSet_value(String value) {
              cookie.setValue(value);
          }
  
          public void jsSet_comment(String purpose) {
              cookie.setComment(purpose);
          }
  
          public String jsGet_comment() {
              return cookie.getComment();
          }
  
          public void jsSet_domain(String pattern) {
              cookie.setDomain(pattern);
          }
  
          public String jsGet_domain() {
              return cookie.getDomain();
          }
  
          public void jsSet_maxAge(int value) {
              cookie.setMaxAge(value);
          }
  
          public int jsGet_maxAge() {
              return cookie.getMaxAge();
          }
  
          public void jsSet_path(String value) {
              cookie.setPath(value);
          }
  
          public String jsGet_path() {
              return cookie.getPath();
          }
  
          public void jsSet_secure(boolean value) {
              cookie.setSecure(value);
          }
  
          public boolean jsGet_secure() {
              return cookie.getSecure();
          }
      }
  
      public static class FOM_Response extends ScriptableObject {
  
          Response response;
  
          public FOM_Response() {
              // prototype ctor
          }
  
          public FOM_Response(Object response) {
              this.response = (Response)unwrap(response);
          }
  
          public String getClassName() {
              return "FOM_Response";
          }
  
          public Object jsFunction_createCookie(String name, String value) {
              FOM_Cookie result = 
                  new FOM_Cookie(response.createCookie(name, value));
              result.setParentScope(getParentScope());
              result.setPrototype(getClassPrototype(this, "FOM_Cookie"));
              return result;
          }
  
          public void jsFunction_addCookie(Object cookie) 
              throws JavaScriptException {
              if (!(cookie instanceof FOM_Cookie)) {
                  throw new JavaScriptException("expected a Cookie instead of " + cookie);
              }
              FOM_Cookie fom_cookie = (FOM_Cookie)cookie;
              response.addCookie(fom_cookie.cookie);
          }
  
          public boolean jsFunction_containsHeader(String name) {
              return response.containsHeader(name);
          }
  
          public void jsFunction_setHeader(String name, String value) {
              response.setHeader(name, value);
          }
  
          public void jsFunction_addHeader(String name, String value) {
              response.addHeader(name, value);
          }
      }
  
      public static class FOM_Session extends ScriptableObject {
  
          Session session;
  
          public FOM_Session() {
              // prototype ctor
          }
  
          public FOM_Session(Object session) {
              this.session = (Session)unwrap(session);
          }
  
          public String getClassName() {
              return "FOM_Session";
          }
  
          public Object[] getIds() {
              if (session != null) {
                  List list = new LinkedList();
                  Enumeration e = session.getAttributeNames();
                  while (e.hasMoreElements()) {
                      list.add(e.nextElement());
                  }
                  Object[] result = new Object[list.size()];
                  list.toArray(result);
                  return result;
              }
              return super.getIds();
          }
  
          public Object get(String name, Scriptable start) {
              Object result = super.get(name, start);
              if (result == NOT_FOUND && session != null) {
                  result = session.getAttribute(name);
                  if (result == null) {
                      result = NOT_FOUND;
                  }
              }
              return result;
          }
  
          public Object jsFunction_getAttribute(String name) {
              return session.getAttribute(name);
          }
          
          public void jsFunction_setAttribute(String name, Object value) {
              session.setAttribute(name, value);
          }
  
          public void jsFunction_removeAttribute(String name) {
              session.removeAttribute(name);
          }
  
          public Object jsFunction_getAttributeNames() {
              return session.getAttributeNames();
          }
  
          public void jsFunction_invalidate() {
              session.invalidate();
          }
  
          public boolean jsFunction_isNew() {
              return session.isNew();
          }
  
          public String jsFunction_getId() {
              return session.getId();
          }
  
          public long jsFunction_getCreationTime() {
              return session.getCreationTime();
          }
  
          public long jsFunction_getLastAccessedTime() {
              return session.getLastAccessedTime();
          }
  
          public void jsFunction_setMaxInactiveInterval(int interval) {
              session.setMaxInactiveInterval(interval);
          }
  
          public int jsFunction_getMaxInactiveInterval() {
              return session.getMaxInactiveInterval();
          }
      }
  
      public static class FOM_Context extends ScriptableObject {
  
          org.apache.cocoon.environment.Context context;
  
          public FOM_Context() {
              // prototype ctor
          }
  
          public FOM_Context(Object context) {
              this.context = (org.apache.cocoon.environment.Context)unwrap(context);
          }
  
          public String getClassName() {
              return "FOM_Context";
          }
  
          public Object jsFunction_getAttribute(String name) {
              return context.getAttribute(name);
          }
  
          public void jsFunction_setAttribute(String name, Object value) {
              context.setAttribute(name, unwrap(value));
          }
  
          public void jsFunction_removeAttribute(String name) {
              context.removeAttribute(name);
          }
  
          public Object jsFunction_getAttributeNames() {
              return context.getAttributeNames();
          }
  
          public Object jsFunction_getInitParameter(String name) {
              return context.getInitParameter(name);
          }
  
          public Object[] getIds() {
              if (context != null) {
                  List list = new LinkedList();
                  Enumeration e = context.getAttributeNames();
                  while (e.hasMoreElements()) {
                      list.add(e.nextElement());
                  }
                  Object[] result = new Object[list.size()];
                  list.toArray(result);
                  return result;
              }
              return super.getIds();
          }
  
          public Object get(String name, Scriptable start) {
              Object value = super.get(name, start);
              if (value == NOT_FOUND && context != null) {
                  value = context.getAttribute(name);
                  if (value == null) {
                      value = NOT_FOUND;
                  }
              }
              return value;
          }
      }
  
      public static class FOM_Log extends ScriptableObject {
  
          private Logger logger;
  
          public FOM_Log() {
          }
  
          public FOM_Log(Object logger) {
              this.logger = (Logger)unwrap(logger);
          }
  
          public String getClassName() {
              return "FOM_Log";
          }
          
          public void jsFunction_debug(String message) {
              logger.debug(message);
          }
          
          public void jsFunction_info(String message) {
              logger.info(message);
          }
          
          public void jsFunction_warn(String message) {
              logger.warn(message);
          }
          
          public void jsFunction_error(String message) {
              logger.error(message);
          }
  
          public boolean jsFunction_isDebugEnabled() {
              return logger.isDebugEnabled();
          }
  
          public boolean jsFunction_isInfoEnabled() {
              return logger.isInfoEnabled();
          }
  
          public boolean jsFunction_isWarnEnabled() {
              return logger.isWarnEnabled();
          }
  
          public boolean jsFunction_isErrorEnabled() {
              return logger.isErrorEnabled();
          }
      }
  
      public static class FOM_WebContinuation extends ScriptableObject {
          
          WebContinuation wk;
  
          public FOM_WebContinuation() {
          }
  
          public FOM_WebContinuation(Object wk) {
              this.wk = (WebContinuation)unwrap(wk);
          }
  
          public String getClassName() {
              return "FOM_WebContinuation";
          }
  
          public String jsGet_id() {
              return wk.getId();
          }
  
          public FOM_WebContinuation jsFunction_getParent() {
              WebContinuation parent = wk.getParentContinuation();
              if (parent == null) return null;
              FOM_WebContinuation pwk = new FOM_WebContinuation(parent);
              pwk.setParentScope(getParentScope());
              pwk.setPrototype(getClassPrototype(getParentScope(), 
                                                 pwk.getClassName()));
              return pwk;
          }
  
          public NativeArray jsFunction_getChildren() throws Exception {
              List list = wk.getChildren();
              NativeArray arr = 
                  (NativeArray)org.mozilla.javascript.Context.getCurrentContext().newObject(getParentScope(), 
                                                 "Array",
                                                 new Object[]{new Integer(list.size())});
              Iterator iter = list.iterator();
              for (int i = 0; iter.hasNext(); i++) {
                  WebContinuation child = (WebContinuation)iter.next();
                  FOM_WebContinuation cwk = new FOM_WebContinuation(child);
                  cwk.setParentScope(getParentScope());
                  cwk.setPrototype(getClassPrototype(getParentScope(), 
                                                     cwk.getClassName()));
                  arr.put(i, arr, cwk);
              }
              return arr;
          }
  
          public void jsFunction_invalidate() throws Exception {
              ContinuationsManager contMgr = null;
              FOM_Cocoon cocoon = 
                  (FOM_Cocoon)getProperty(getTopLevelScope(this),
                                          "cocoon");
              ComponentManager componentManager = 
                  cocoon.getComponentManager();
              contMgr = (ContinuationsManager)
                  componentManager.lookup(ContinuationsManager.ROLE);
              contMgr.invalidateWebContinuation(wk);
          }
  
          public WebContinuation getWebContinuation() {
              return wk;
          }
      }
  
      public FOM_Request jsGet_request() {
          if (request != null) {
              return request;
          }
          if (environment == null) {
              // context has been invalidated
              return null;
          }
          Map objectModel = environment.getObjectModel();
          request = new FOM_Request(ObjectModelHelper.getRequest(objectModel));
          request.setParentScope(getParentScope());
          request.setPrototype(getClassPrototype(getParentScope(), "FOM_Request"));
          return request;
      }
  
      public FOM_Response jsGet_response() {
          if (response != null) {
              return response;
          }
          if (environment == null) {
              // context has been invalidated
              return null;
          }
          Map objectModel = environment.getObjectModel();
          response = 
              new FOM_Response(ObjectModelHelper.getResponse(objectModel));
          response.setParentScope(getParentScope());
          response.setPrototype(getClassPrototype(this, "FOM_Response"));
          return response;
      }
  
      public FOM_Log jsGet_log() {
          if (log != null) {
              return log;
          }
          log = new FOM_Log(logger);
          log.setParentScope(getParentScope());
          log.setPrototype(getClassPrototype(this, "FOM_Log"));
          return log;
      }
  
      public FOM_Context jsGet_context() {
          if (context != null) {
              return context;
          }
          Map objectModel = environment.getObjectModel();
          context = 
              new FOM_Context(ObjectModelHelper.getContext(objectModel));
          context.setParentScope(getParentScope());
          context.setPrototype(getClassPrototype(this, "FOM_Context"));
          return context;
      }
  
      public FOM_Session jsGet_session() {
          if (session != null) {
              return session;
          }
          if (environment == null) {
              // session has been invalidated
              return null;
          }
          Map objectModel = environment.getObjectModel();
          session = 
              new FOM_Session(ObjectModelHelper.getRequest(objectModel).getSession(true));
          session.setParentScope(getParentScope());
          session.setPrototype(getClassPrototype(this, "FOM_Session"));
          return session;
      }
  
      /**
       * Get Sitemap parameters
       *
       * @return a <code>Scriptable</code> value whose properties represent 
       * the Sitemap parameters from <map:call>
       */
      public Scriptable jsGet_parameters() {
  	return parameters;
      }
  
      void setParameters(Scriptable value) {
  	parameters = value;
      }
  
      // unwrap Wrapper's and convert undefined to null
      private static Object unwrap(Object obj) {
          if (obj instanceof Wrapper) {
              obj = ((Wrapper)obj).unwrap();
          } else if (obj == Undefined.instance) {
              obj = null;
          }
          return obj;
      }
  
      // Make everything available to JavaScript objects implemented in Java:
  
      public Request getRequest() {
          return jsGet_request().request;
      }
  
      public Session getSession() {
          return jsGet_session().session;
      }
  
      public Response getResponse() {
          return jsGet_response().response;
      }
  
      public org.apache.cocoon.environment.Context getContext() {
          return jsGet_context().context;
      }
  
      public Environment getEnvironment() {
          return environment;
      }
  
      public ComponentManager getComponentManager() {
          return componentManager;
      }
  
      public FOM_JavaScriptInterpreter getInterpreter() {
          return interpreter;
      }
  
      public FOM_WebContinuation makeWebContinuation(WebContinuation wk) {
          if (wk == null) return null;
          FOM_WebContinuation result = new FOM_WebContinuation(wk);
          result.setParentScope(getParentScope());
          result.setPrototype(getClassPrototype(getParentScope(), 
                                                result.getClassName()));
          return result;
      }
  }
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptFlowHelper.java
  
  Index: FOM_JavaScriptFlowHelper.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. 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.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, 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.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.components.flow.javascript.fom;
  
  import org.apache.cocoon.components.flow.FlowHelper;
  import org.mozilla.javascript.Scriptable;
  import org.apache.cocoon.environment.ObjectModelHelper;
  import org.apache.cocoon.environment.Request;
  import java.util.Map;
  
  /**
   * Provides the interface between the JavaScript flow controller layer and the 
   * view layer. A view can obtain the JavaScript "live connect" objects (that
   * allow access to Java constructors) through this interface, as well as
   * the FOM objects.
   */
  
  public class FOM_JavaScriptFlowHelper extends FlowHelper {
  
      public static final String PACKAGES_OBJECT =
          "cocoon.flow.js.packages";
      public static final String JAVA_PACKAGE_OBJECT =
          "cocoon.flow.js.packages.java";
      public static final String FOM_REQUEST = 
          "cocoon.flow.js.fom.FOM_Request";
      public static final String FOM_RESPONSE = 
          "cocoon.flow.js.fom.FOM_Response";
      public static final String FOM_SESSION = 
          "cocoon.flow.js.fom.FOM_Session";
      public static final String FOM_CONTEXT = 
          "cocoon.flow.js.fom.FOM_Context";
      public static final String FOM_WEB_CONTINUATION = 
          "cocoon.flow.js.fom.FOM_WebContinuation";
  
      /** 
       * Return the JS "Packages" property (that gives access to Java
       * packages) for use by the view layer
       * @param objectModel The Cocoon Environment's object model
       * @return The Packages property
       */
      public static Scriptable getPackages(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(PACKAGES_OBJECT);
      }
  
      /** 
       * Set the JS "Packages" property in the current request
       * @param objectModel The Cocoon Environment's object model
       */
      public static void setPackages(Map objectModel, Scriptable pkgs) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(PACKAGES_OBJECT, pkgs);
      }
  
      /** 
       * Return the JS "java" property (that gives access to the "java"
       * package) for use by the view layer
       * @param objectModel The Cocoon Environment's object model
       * @return The java package property
       */
      public static Scriptable getJavaPackage(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(JAVA_PACKAGE_OBJECT);
      }
  
      /** 
       * Set the JS "java" property in the current request
       * @param objectModel The Cocoon Environment's object model
       */
      public static void setJavaPackage(Map objectModel, Scriptable javaPkg) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(JAVA_PACKAGE_OBJECT, javaPkg);
      }
  
      public static Scriptable getFOM_Request(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(FOM_REQUEST);
      }
  
      public static void setFOM_Request(Map objectModel, Scriptable fom_request) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(FOM_REQUEST, fom_request);
      }
  
      public static Scriptable getFOM_Response(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(FOM_RESPONSE);
      }
  
      public static void setFOM_Response(Map objectModel, Scriptable fom_response) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(FOM_RESPONSE, fom_response);
      }
  
      public static Scriptable getFOM_Session(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(FOM_SESSION);
      }
  
      public static void setFOM_Session(Map objectModel, Scriptable fom_session) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(FOM_SESSION, fom_session);
      }
  
      public static Scriptable getFOM_Context(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(FOM_CONTEXT);
      }
  
      public static void setFOM_Context(Map objectModel, Scriptable fom_context) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(FOM_CONTEXT, fom_context);
      }
  
      public static Scriptable getFOM_WebContinuation(Map objectModel) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          return (Scriptable)request.getAttribute(FOM_WEB_CONTINUATION);
      }
  
      public static void setFOM_WebContinuation(Map objectModel, 
                                                Scriptable fom_webContinuation) {
          Request request = ObjectModelHelper.getRequest(objectModel);
          request.setAttribute(FOM_WEB_CONTINUATION, fom_webContinuation);
      }
  }
  
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/flow/javascript/fom/FOM_JavaScriptInterpreter.java
  
  Index: FOM_JavaScriptInterpreter.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. 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.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, 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.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.components.flow.javascript.fom;
  
  import java.io.BufferedReader;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.Reader;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import org.apache.avalon.framework.CascadingRuntimeException;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.configuration.Configurable;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.cocoon.ProcessingException;
  import org.apache.cocoon.ResourceNotFoundException;
  import org.apache.cocoon.components.flow.AbstractInterpreter;
  import org.apache.cocoon.components.flow.Interpreter;
  import org.apache.cocoon.components.flow.InvalidContinuationException;
  import org.apache.cocoon.components.flow.WebContinuation;
  import org.apache.cocoon.components.flow.javascript.JSErrorReporter;
  import org.apache.cocoon.components.flow.javascript.ScriptablePointerFactory;
  import org.apache.cocoon.components.flow.javascript.ScriptablePropertyHandler;
  import org.apache.cocoon.environment.Environment;
  import org.apache.cocoon.environment.ObjectModelHelper;
  import org.apache.cocoon.environment.Request;
  import org.apache.cocoon.environment.Session;
  import org.apache.commons.jxpath.JXPathIntrospector;
  import org.apache.commons.jxpath.ri.JXPathContextReferenceImpl;
  import org.apache.excalibur.source.Source;
  import org.mozilla.javascript.*;
  import org.mozilla.javascript.continuations.Continuation;
  import org.mozilla.javascript.tools.ToolErrorReporter;
  import org.mozilla.javascript.tools.shell.Global;
  import org.apache.cocoon.components.flow.javascript.fom.FOM_Cocoon.FOM_WebContinuation;
  /**
   * Interface with the JavaScript interpreter.
   *
   * @author <a href="mailto:ovidiu@apache.org">Ovidiu Predescu</a>
   * @author <a href="mailto:crafterm@apache.org">Marcus Crafter</a>
   * @since March 25, 2002
   * @version CVS $Id: FOM_JavaScriptInterpreter.java,v 1.1 2003/07/08 05:48:52 coliver Exp $
   */
  public class FOM_JavaScriptInterpreter extends AbstractInterpreter
      implements Configurable, Initializable
  {
  
      /**
       * LAST_EXEC_TIME
       * A long value is stored under this key in each top level JavaScript
       * thread scope object. When you enter a context any scripts whose
       * modification time is later than this value will be recompiled and reexecuted,
       * and this value will be updated to the current time.
       */
      private final static String LAST_EXEC_TIME = "__PRIVATE_LAST_EXEC_TIME__";
  
      /**
       * Key for storing a JavaScript global scope object in the Cocoon session
       */
      public static final String USER_GLOBAL_SCOPE = "FOM JavaScript GLOBAL SCOPE";
  
      // This is the only optimization level that supports continuations
      // in the Christoper Oliver's Rhino JavaScript implementation
      static int OPTIMIZATION_LEVEL = -2;
  
      /**
       * When was the last time we checked for script modifications. Used
       * only if {@link #reloadScripts} is true.
       */
      protected long lastTimeCheck = 0;
  
      /**
       * Shared global scope for scripts and other immutable objects
       */
      Global scope;
  
      /**
       * List of <code>String</code> objects that represent files to be
       * read in by the JavaScript interpreter.
       */
      List topLevelScripts = new ArrayList();
  
      class ScriptSourceEntry {
          final private Source source;
          private Script script;
          private long compileTime;
  
          public ScriptSourceEntry(Source source) {
              this.source = source;
          }
  
          public ScriptSourceEntry(Source source, Script script, long t) {
              this.source = source;
              this.script = script;
              this.compileTime = t;
          }
  
          public Source getSource() {
              return source;
          }
  
          public Script getScript(Context context, Scriptable scope,
                                               boolean refresh)
              throws Exception {
              if (refresh) {
                  source.refresh();
              }
              if (script == null || compileTime < source.getLastModified()) {
                  script = compileScript(context, scope, source);
                  compileTime = source.getLastModified();
              }
              return script;
          }
      }
      /**
       * Mapping of String objects (source uri's) to ScriptSourceEntry's
       *
       */
      Map compiledScripts = new HashMap();
  
      JSErrorReporter errorReporter;
      boolean enableDebugger = false;
      /**
       * JavaScript debugger: there's only one of these: it can debug multiple
       * threads executing JS code.
       */
      static org.mozilla.javascript.tools.debugger.Main debugger;
  
      static synchronized org.mozilla.javascript.tools.debugger.Main getDebugger() {
          if (debugger == null) {
              final org.mozilla.javascript.tools.debugger.Main db
                  = new org.mozilla.javascript.tools.debugger.Main("Cocoon Flow Debugger");
              db.pack();
              java.awt.Dimension size =
                  java.awt.Toolkit.getDefaultToolkit().getScreenSize();
              size.width *= 0.75;
              size.height *= 0.75;
              db.setSize(size);
              db.setExitAction(new Runnable() {
                      public void run() {
                          db.setVisible(false);
                      }
                  });
              db.setOptimizationLevel(OPTIMIZATION_LEVEL);
              db.setVisible(true);
              debugger = db;
              Context.addContextListener(debugger);
          }
          return debugger;
      }
  
      public void configure(Configuration config)
          throws ConfigurationException
      {
          super.configure(config);
  
          String loadOnStartup
              = config.getChild("load-on-startup", true).getValue(null);
          if (loadOnStartup != null) {
              register(loadOnStartup);
          }
  
          String debugger
              = config.getChild("debugger").getValue(null);
          if ("enabled".equalsIgnoreCase(debugger)) {
              enableDebugger = true;
          }
      }
  
      public void initialize()
          throws Exception
      {
          if (enableDebugger) {
              if (getLogger().isDebugEnabled()) {
                  getLogger().debug("Flow debugger enabled, creating");
              }
              getDebugger().doBreak();
          }
          Context context = Context.enter();
          context.setOptimizationLevel(OPTIMIZATION_LEVEL);
          context.setCompileFunctionsWithDynamicScope(true);
          context.setGeneratingDebug(true);
          // add support for Rhino objects to JXPath
          JXPathIntrospector.registerDynamicClass(org.mozilla.javascript.Scriptable.class,
                                                  ScriptablePropertyHandler.class);
          JXPathContextReferenceImpl.addNodePointerFactory(new ScriptablePointerFactory());
  
          try {
              scope = new Global(context);
              // Access to Cocoon internal objects
              FOM_Cocoon.init(scope);
              errorReporter = new JSErrorReporter(getLogger());
          }
          catch (Exception e) {
              Context.exit();
              e.printStackTrace();
              throw e;
          }
      }
  
      /**
       * Returns the JavaScript scope, a Scriptable object, from the user
       * session instance. Each URI prefix, as returned by the {@link
       * org.apache.cocoon.environment.Environment#getURIPrefix} method,
       * can have a scope associated with it.
       *
       * @param environment an <code>Environment</code> value
       * @return a <code>Scriptable</code> value
       */
      private Scriptable getSessionScope(Environment environment)
          throws Exception {
          Map objectModel = environment.getObjectModel();
          Request request = ObjectModelHelper.getRequest(objectModel);
          Session session = request.getSession(true);
          Scriptable scope = null;
          HashMap userScopes = (HashMap)session.getAttribute(USER_GLOBAL_SCOPE);
          if (userScopes != null) {
              String uriPrefix = environment.getURIPrefix();
              scope = (Scriptable)userScopes.get(uriPrefix);
          }
          if (scope == null) {
              return setSessionScope(environment, createThreadScope());
          }
          return scope;
      }
  
      /**
       * Associates a JavaScript scope, a Scriptable object, with the URI
       * prefix of the current sitemap, as returned by the {@link
       * org.apache.cocoon.environment.Environment#getURIPrefix} method.
       *
       * @param environment an <code>Environment</code> value
       * @param scope a <code>Scriptable</code> value
       */
      private Scriptable setSessionScope(Environment environment, Scriptable scope)
          throws Exception {
          Map objectModel = environment.getObjectModel();
          Request request = ObjectModelHelper.getRequest(objectModel);
          Session session = request.getSession(true);
  
          HashMap userScopes = (HashMap)session.getAttribute(USER_GLOBAL_SCOPE);
          if (userScopes == null) {
              userScopes = new HashMap();
              session.setAttribute(USER_GLOBAL_SCOPE, userScopes);
          }
  
          String uriPrefix = environment.getURIPrefix();
          userScopes.put(uriPrefix, scope);
          return scope;
      }
  
      private Scriptable createThreadScope() 
          throws Exception {
          org.mozilla.javascript.Context context = 
              org.mozilla.javascript.Context.getCurrentContext();
  
          Scriptable thrScope = context.newObject(scope);
  
          thrScope.setPrototype(scope);
          // We want 'thrScope' to be a new top-level scope, so set its
          // parent scope to null. This means that any variables created
          // by assignments will be properties of "thrScope".
          thrScope.setParentScope(null);
          
          // Put in the thread scope the Cocoon object, which gives access
          // to the interpreter object, and some Cocoon objects. See
          // FOM_Cocoon for more details.
          Object args[] = {};
          FOM_Cocoon cocoon = (FOM_Cocoon)
              context.newObject(thrScope, "FOM_Cocoon", args);
          cocoon.setParentScope(thrScope);
          thrScope.put("cocoon", thrScope, cocoon);
          ((ScriptableObject)thrScope).defineProperty(LAST_EXEC_TIME,
                                                      new Long(0),
                                                      ScriptableObject.DONTENUM |
                                                      ScriptableObject.PERMANENT);
          
          return thrScope;
      }
  
      /**
       * Returns a new Scriptable object to be used as the global scope
       * when running the JavaScript scripts in the context of a request.
       *
       * <p>If you want to maintain the state of global variables across
       * multiple invocations of <code>&lt;map:call
       * function="..."&gt;</code>, you need to invoke from the JavaScript
       * script <code>cocoon.createSession()</code>. This will place the
       * newly create Scriptable object in the user's session, where it
       * will be retrieved from at the next invocation of {@link #callFunction}.</p>
       *
       * @param environment an <code>Environment</code> value
       * @return a <code>Scriptable</code> value
       * @exception Exception if an error occurs
       */
      private void setupContext(Environment environment,
                                org.mozilla.javascript.Context context,
                                Scriptable thrScope)
          throws Exception {
          // Try to retrieve the scope object from the session instance. If
          // no scope is found, we create a new one, but don't place it in
          // the session.
          //
          // When a user script "creates" a session using
          // cocoon.createSession() in JavaScript, the thrScope is placed in
          // the session object, where it's later retrieved from here. This
          // behaviour allows multiple JavaScript functions to share the
          // same global scope.
  
          FOM_Cocoon cocoon = (FOM_Cocoon)thrScope.get("cocoon", thrScope);
          long lastExecTime = ((Long)thrScope.get(LAST_EXEC_TIME,
                                             thrScope)).longValue();
          // We need to setup the FOM_Cocoon object according to the current
          // request. Everything else remains the same.
          cocoon.setup(this, environment, manager, getLogger());
  
          // Check if we need to compile and/or execute scripts
          synchronized (compiledScripts) {
              List execList = new ArrayList();
              boolean needsRefresh = false;
              if (reloadScripts) {
                  long now = System.currentTimeMillis();
                  if (now >= lastTimeCheck + checkTime) {
                      needsRefresh = true;
                  }
                  lastTimeCheck = now;
              }
              // If we've never executed scripts in this scope or
              // if reload-scripts is true and the check interval has expired
              // or if new scripts have been specified in the sitemap,
              // then create a list of scripts to compile/execute
              if (lastExecTime == 0 || needsRefresh || needResolve.size() > 0) {
                  topLevelScripts.addAll(needResolve);
                  if (!needsRefresh) {
                      execList.addAll(needResolve);
                  } else {
                      execList.addAll(topLevelScripts);
                  }
                  needResolve.clear();
              }
              thrScope.put(LAST_EXEC_TIME, thrScope,
                           new Long(System.currentTimeMillis()));
              // Compile all the scripts first. That way you can set breakpoints
              // in the debugger before they execute.
              for (int i = 0, size = execList.size(); i < size; i++) {
                  String sourceURI = (String)execList.get(i);
                  ScriptSourceEntry entry =
                      (ScriptSourceEntry)compiledScripts.get(sourceURI);
                  if (entry == null) {
                      Source src = environment.resolveURI(sourceURI);
                      entry = new ScriptSourceEntry(src);
                      compiledScripts.put(sourceURI, entry);
                  }
                  // Compile the script if necessary
                  entry.getScript(context, this.scope, needsRefresh);
              }
              // Execute the scripts if necessary
              for (int i = 0, size = execList.size(); i < size; i++) {
                  String sourceURI = (String)execList.get(i);
                  ScriptSourceEntry entry =
                      (ScriptSourceEntry)compiledScripts.get(sourceURI);
                  long lastMod = entry.getSource().getLastModified();
                  Script script = entry.getScript(context, this.scope, false);
                  if (lastExecTime == 0 || lastMod > lastExecTime) {
                      script.exec(context, thrScope);
                  }
              }
          }
      }
  
      /**
       * Compile filename as JavaScript code
       * @param cx Rhino context
       * @param environment source resolver
       * @param fileName resource uri
       * @return compiled script
       */
  
      public Script compileScript(Context cx,
                                  Environment environment,
                                  String fileName) throws Exception {
          Source src = environment.resolveURI(fileName);
          if (src == null) {
              throw new ResourceNotFoundException(fileName + ": not found");
          }
          synchronized (compiledScripts) {
              ScriptSourceEntry entry =
                  (ScriptSourceEntry)compiledScripts.get(src.getURI());
              Script compiledScript = null;
              if (entry == null) {
                  compiledScripts.put(src.getURI(),
                                      entry = new ScriptSourceEntry(src));
              }
              compiledScript = entry.getScript(cx, this.scope, false);
              return compiledScript;
          }
      }
  
      private Script compileScript(Context cx, Scriptable scope,
                                   Source src) throws Exception {
          InputStream is = src.getInputStream();
          if (is == null) {
              throw new ResourceNotFoundException(src.getURI() + ": not found");
          }
          Reader reader = new BufferedReader(new InputStreamReader(is));
          Script compiledScript = cx.compileReader(scope, reader,
                                                   src.getURI(),
                                                   1, null);
          return compiledScript;
      }
  
      /**
       * Calls a JavaScript function, passing <code>params</code> as its
       * arguments. In addition to this, it makes available the parameters
       * through the <code>cocoon.parameters</code> JavaScript array
       * (indexed by the parameter names).
       *
       * @param funName a <code>String</code> value
       * @param params a <code>List</code> value
       * @param environment an <code>Environment</code> value
       * @exception Exception if an error occurs
       */
      public void callFunction(String funName, List params,
                               Environment environment)
          throws Exception
      {
          Context context = Context.enter();
          context.setOptimizationLevel(OPTIMIZATION_LEVEL);
          context.setGeneratingDebug(true);
          context.setCompileFunctionsWithDynamicScope(true);
          context.setErrorReporter(errorReporter);
          FOM_Cocoon cocoon = null;
          Scriptable thrScope = getSessionScope(environment);
          synchronized (thrScope) {
              try {
                  setupContext(environment, context, thrScope);
                  cocoon = (FOM_Cocoon)thrScope.get("cocoon", thrScope);
                  if (enableDebugger) {
                      if (!getDebugger().isVisible()) {
                          // only raise the debugger window if it isn't already visible
                          getDebugger().setVisible(true);
                      }
                  }
                  int size = (params != null ? params.size() : 0);
                  Object[] funArgs = new Object[size];
  		Scriptable parameters = context.newObject(thrScope);
                  if (size != 0) {
                      for (int i = 0; i < size; i++) {
                          Interpreter.Argument arg = (Interpreter.Argument)params.get(i);
                          funArgs[i] = arg.value;
                          if (arg.name == null) arg.name = "";
                          parameters.put(arg.name, parameters, arg.value);
                      }
                  }
                  cocoon.setParameters(parameters);
                  Object fun = ScriptableObject.getProperty(thrScope, funName);
                  if (fun == Scriptable.NOT_FOUND) {
                      fun = funName; // this will produce a better error message
                  }
                  ScriptRuntime.call(context, fun, thrScope, 
                                     funArgs, thrScope);
              } catch (JavaScriptException ex) {
                  EvaluatorException ee =
                      Context.reportRuntimeError(ToolErrorReporter.getMessage("msg.uncaughtJSException",
                                                                              ex.getMessage()));
                  Throwable unwrapped = unwrap(ex);
                  if (unwrapped instanceof ProcessingException) {
                      throw (ProcessingException)unwrapped;
                  }
                  
                  throw new CascadingRuntimeException(ee.getMessage(), unwrapped);
              } catch (EcmaError ee) {
                  String msg = ToolErrorReporter.getMessage("msg.uncaughtJSException", ee.toString());
                  if (ee.getSourceName() != null) {
                      Context.reportRuntimeError(msg,
                                                 ee.getSourceName(),
                                                 ee.getLineNumber(),
                                                 ee.getLineSource(),
                                                 ee.getColumnNumber());
                  } else {
                      Context.reportRuntimeError(msg);
                  }
                  throw new CascadingRuntimeException(ee.getMessage(), ee);
              } finally {
                  cocoon.invalidate();
                  Context.exit();
              }
          }
      }
  
      public void handleContinuation(String id, List params,
                                     Environment environment)
          throws Exception
      {
          WebContinuation wk = continuationsMgr.lookupWebContinuation(id);
  
          if (wk == null) {
  
              /*
               * Throw an InvalidContinuationException to be handled inside the
               * <map:handle-errors> sitemap element.
               */
              throw new InvalidContinuationException("The continuation ID " + id + " is invalid.");
          }
  
          Context context = Context.enter();
          context.setOptimizationLevel(OPTIMIZATION_LEVEL);
          context.setGeneratingDebug(true);
          context.setCompileFunctionsWithDynamicScope(true);
  
          // Obtain the continuation object from it, and setup the
          // FOM_Cocoon object associated in the dynamic scope of the saved
          // continuation with the environment and context objects.
          Continuation k = (Continuation)wk.getContinuation();
          Scriptable kScope = k.getParentScope();
          synchronized (kScope) {
              FOM_Cocoon cocoon = (FOM_Cocoon)kScope.get("cocoon", kScope);
              cocoon.setup(this, environment, manager, getLogger());
              if (enableDebugger) {
                  getDebugger().setVisible(true);
              }
              int size = (params != null ? params.size() : 0);
              Scriptable parameters = context.newObject(kScope);
              if (size != 0) {
  		for (int i = 0; i < size; i++) {
  		    Interpreter.Argument arg = (Interpreter.Argument)params.get(i);
  		    parameters.put(arg.name, parameters, arg.value);
  		}
  	    }
              cocoon.setParameters(parameters);
              Object[] args = new Object[] {k, 
                                            cocoon.makeWebContinuation(wk)};
              try {
                  ScriptableObject.callMethod(cocoon, 
                                              "handleContinuation", 
                                              args);
              } catch (JavaScriptException ex) {
                  EvaluatorException ee =
                      Context.reportRuntimeError(ToolErrorReporter.getMessage("msg.uncaughtJSException",
                                                                              ex.getMessage()));
                  Throwable unwrapped = unwrap(ex);
                  if (unwrapped instanceof ProcessingException) {
                      throw (ProcessingException)unwrapped;
                  }
                  
                  throw new CascadingRuntimeException(ee.getMessage(), unwrapped);
              } catch (EcmaError ee) {
                  String msg = ToolErrorReporter.getMessage("msg.uncaughtJSException", ee.toString());
                  if (ee.getSourceName() != null) {
                      Context.reportRuntimeError(msg,
                                                 ee.getSourceName(),
                                                 ee.getLineNumber(),
                                                 ee.getLineSource(),
                                                 ee.getColumnNumber());
                  } else {
                      Context.reportRuntimeError(msg);
                  }
                  throw new CascadingRuntimeException(ee.getMessage(), ee);
              } finally {
                  cocoon.invalidate();
                  Context.exit();
              }
          }
      }
  
      private Throwable unwrap(JavaScriptException e) {
          Object value = e.getValue();
          while (value instanceof Wrapper) {
              value = ((Wrapper)value).unwrap();
          }
          if (value instanceof Throwable) {
              return (Throwable)value;
          }
          return e;
      }
  
      public void forwardTo(Scriptable scope, FOM_Cocoon cocoon,
                            String uri, Object bizData,
                            WebContinuation continuation,
                            Environment environment)
          throws Exception {
          setupView(scope, cocoon , environment, 
                    cocoon.makeWebContinuation(continuation));
          super.forwardTo(uri, bizData, continuation, environment);
      }
  
      // package access as this is called by FOM_Cocoon
      boolean process(Scriptable scope, FOM_Cocoon cocoon,
                      String uri, Object bizData, 
                      OutputStream out, Environment environment)
          throws Exception {
          setupView(scope, cocoon, environment, null);
          return super.process(uri, bizData, out, environment);
      }
      
      private void setupView(Scriptable scope,
                             FOM_Cocoon cocoon,
                             Environment environment,
                             FOM_WebContinuation kont) {
          Map objectModel = environment.getObjectModel();
          // Make the JS live-connect objects available to the view layer
          FOM_JavaScriptFlowHelper.setPackages(objectModel,
                                     (Scriptable)ScriptableObject.getProperty(scope,
                                                                              "Packages"));
          FOM_JavaScriptFlowHelper.setJavaPackage(objectModel,
                                                  (Scriptable)ScriptableObject.getProperty(scope,
                                                                                           "java"));
          // Make the FOM objects available to the view layer
          FOM_JavaScriptFlowHelper.setFOM_Request(objectModel,
                                              cocoon.jsGet_request());
          FOM_JavaScriptFlowHelper.setFOM_Response(objectModel,
                                               cocoon.jsGet_response());
          FOM_JavaScriptFlowHelper.setFOM_Session(objectModel,
                                              cocoon.jsGet_session());
          FOM_JavaScriptFlowHelper.setFOM_Context(objectModel,
                                                  cocoon.jsGet_context());
          if (kont != null) {
              FOM_JavaScriptFlowHelper.setFOM_WebContinuation(objectModel, 
                                                              kont);
          }
      }
  }
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/components/flow/javascript/fom/fom_system.js
  
  Index: fom_system.js
  ===================================================================
  FOM_Cocoon.suicide = new Continuation();
  
  FOM_Cocoon.prototype.sendPageAndWait = function(uri, bizData) {
      this.sendPage(uri, bizData, new Continuation());
      FOM_Cocoon.suicide();
  }
  
  FOM_Cocoon.prototype.handleContinuation = function(k, wk) {
      k(wk);
  }
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/generation/JXTemplateGenerator.java
  
  Index: JXTemplateGenerator.java
  ===================================================================
  /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. 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.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, 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.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <st...@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.generation;
  
  import java.beans.PropertyDescriptor;
  import java.io.CharArrayReader;
  import java.io.IOException;
  import java.io.StringReader;
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map;
  import java.util.Stack;
  
  import org.apache.avalon.framework.parameters.Parameters;
  import org.apache.cocoon.ProcessingException;
  import org.apache.cocoon.components.flow.FlowHelper;
  import org.apache.cocoon.components.flow.WebContinuation;
  import org.apache.cocoon.components.flow.javascript.JavaScriptFlow;
  import org.apache.cocoon.components.source.SourceUtil;
  import org.apache.cocoon.environment.ObjectModelHelper;
  import org.apache.cocoon.environment.Request;
  import org.apache.cocoon.environment.Response;
  import org.apache.cocoon.environment.SourceResolver;
  import org.apache.cocoon.transformation.AbstractTransformer;
  import org.apache.cocoon.xml.XMLConsumer;
  import org.apache.cocoon.xml.dom.DOMBuilder;
  import org.apache.cocoon.xml.dom.DOMStreamer;
  import org.apache.commons.jexl.ExpressionFactory;
  import org.apache.commons.jexl.JexlContext;
  import org.apache.commons.jexl.util.introspection.Info;
  import org.apache.commons.jexl.util.introspection.UberspectImpl;
  import org.apache.commons.jexl.util.introspection.VelMethod;
  import org.apache.commons.jexl.util.introspection.VelPropertyGet;
  import org.apache.commons.jexl.util.introspection.VelPropertySet;
  import org.apache.commons.jxpath.*;
  import org.apache.excalibur.source.Source;
  import org.apache.excalibur.source.SourceException;
  import org.mozilla.javascript.*;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  import org.xml.sax.Attributes;
  import org.xml.sax.ContentHandler;
  import org.xml.sax.Locator;
  import org.xml.sax.SAXException;
  import org.xml.sax.SAXParseException;
  import org.xml.sax.ext.LexicalHandler;
  import org.xml.sax.helpers.AttributesImpl;
  import org.xml.sax.helpers.LocatorImpl;
  
  /**
   * <p>(<em>JX</em> for <a href="http://jakarta.apache.org/commons/jxpath">Apache <em>JX</em>Path</a> 
   * and <a href="http://jakarta.apache.org/commons/jexl">Apache <em>J</em>e<em>x</em>l</a>).</p>
   * <p>Uses the namespace <code>http://cocoon.apache.org/templates/jx/1.0</code></p>
   * <p>Provides a generic page template with embedded JSTL and XPath 
   * expression substitution to access data sent by Cocoon Flowscripts.</p>
   * The embedded expression language allows a page author to access an 
   * object using a simplified syntax such as
   *
   *  <p><pre>
   *  &lt;site signOn="${accountForm.signOn}"&gt;
   *  </pre></p>
   *
   * <p>Embedded JSTL expressions are contained in <code>${}</code>.</p>
   * <p>Embedded XPath expressions are contained in <code>#{}</code>.</p>
   * <p>Note that since this generator uses 
   * <a href="http://jakarta.apache.org/commons/jxpath">Apache JXPath</a> 
   * and <a href="http://jakarta.apache.org/commons/jexl">Apache Jexl</a>, the 
   * referenced objects may be Java Beans, DOM, JDOM, or JavaScript objects from 
   * a Flowscript. In addition the following implicit objects are available as
   * both XPath and JSTL variables:</p>
   * <p>
   * <dl>
   * <dt><code>request</code> (<code>org.apache.cocoon.environment.Request</code>)</dt>
   * <dd>The Cocoon current request</dd>
   *
   * <dt><code>response</code> (<code>org.apache.cocoon.environment.Response</code>)</dt>
   * <dd>The Cocoon response associated with the current request</dd>
   *
   * <dt><code>session</code> (<code>org.apache.cocoon.environment.Session</code>)</dt>
   * <dd>The Cocoon session associated with the current request</dd>
   *
   * <dt><code>context</code> (<code>org.apache.cocoon.environment.Context</code>)</dt>
   * <dd>The Cocoon context associated with the current request</dd>
   *
   * <dt><code>parameters</code> (<code>org.apache.avalon.framework.parameters.Parameters</code>)</dt>
   * <dd>Any parameters passed to the generator in the pipeline</dd>
   * </dl>
   * </p>
   *
   * The current Web Continuation from the Flowscript 
   * is also available as a variable named <code>continuation</code>. You would 
   * typically access its <code>id</code>:
   *
   * <p><pre>
   *    &lt;form action="${continuation.id}"&gt;
   * </pre></p>
   *
   * <p>You can also reach previous continuations by using the 
   * <code>getContinuation()</code> function:</p>
   *
   * <p><pre>
   *     &lt;form action="${continuation.getContinuation(1).id}" >
   * </pre></p>
   *
   * <p>
   * <p>The <code>template</code> tag defines a new template:</p><pre>
   *    &lt;template&gt;
   *        body
   *    &lt;/template&gt;
   * </pre></p>
   *
   * <p>The <code>import</code> tag allows you to include another template 
   * within the current template. The content of the imported template is 
   * compiled and will be executed in place of the <code>import</code> tag:</p><pre>
   *    &lt;import uri="URI" [context="Expression"]/&gt;
   * </pre></p><p>The Cocoon source resolver is used to resolve <code>uri</code>. 
   * If <code>context</code> is present, then its value is used as the context 
   * for evaluating the imported template, otherwise the current context is 
   * used.</p>
   * <p>The <code>set</code> tag creates a local alias of an object. The 
   * <code>var</code> attribute specifies the name of a variable to assign the 
   * object to. The <code>value</code> attribute specifies the object (defaults 
   * to <code>body</code> if not present):</p>
   *
   * <pre>
   *    &lt;set var="Name" [value="Value"]&gt;
   *        [body]
   *    &lt;/set&gt;
   * </pre></p>
   *
   * <p>If used within a <code>macro</code> definition (see below) 
   * variables created by <code>set</code> are only visible within the body of 
   * the <code>macro</code>.</p>
   * <p>The <code>if</code> tag allows the conditional execution of its body 
   * according to value of a <code>test</code> attribute:</p>
   *
   * <p><pre>
   *   &lt;if test="Expression"&gt;
   *       body
   *   &lt;/if&gt;
   * </pre></p>
   *
   * <p>The <code>choose</code> tag performs conditional block execution by the 
   * embedded <code>when</code> sub tags. It renders the body of the first 
   * <code>when</code> tag whose <code>test</code> condition evaluates to true. 
   * If none of the <code>test</code> conditions of nested <code>when</code> tags
   * evaluate to <code>true</code>, then the body of an <code>otherwise</code> 
   * tag is evaluated, if present:</p>
   *
   * <p><pre>
   *  &lt;choose&gt;
   *    &lt;when test="Expression"&gt;
   *       body
   *    &lt;/when&gt;
   *    &lt;otherwise&gt;
   *       body
   *    &lt;/otherwise&gt;
   *  &lt;/choose&gt;
   * </pre></p>
   *
   * <p>The <code>out</code> tag evaluates an expression and outputs 
   * the result of the evaluation:</p>
   *
   * <p><pre>
   * &lt;out value="Expression"/&gt;
   * </pre></p>
   *
   * <p>The <code>forEach</code> tag allows you to iterate over a collection 
   * of objects:<p>
   *
   * <p><pre>
   *   &lt;forEach [var="Name"] [items="Expression"] 
                   [begin="Number"] [end="Number"] [step="Number"]&gt;
   *     body
   *  &lt;/forEach&gt;
   * </pre></p>
   *
   * <p>The <code>items</code> attribute specifies the list of items to iterate 
   * over. The <code>var</code> attribute specifies the name of a variable to 
   * hold the current item. The <code>begin</code> attribute specifies the 
   * element to start with (<code>0</code> = first item, 
   * <code>1</code> = second item, ...). 
   * If unspecified it defaults to <code>0</code>. The <code>end</code> 
   * attribute specifies the item to end with (<code>0</code> = first item, 
   * <code>1</code> = second item, ...). If unspecified it defaults to the last 
   * item in the list. Every <code>step</code> items are
   * processed (defaults to <code>1</code> if <code>step</code> is absent). 
   * Either <code>items</code> or both <code>begin</code> and <code>end</code> 
   * must be present.<p>
   *
   * <p>The <code>macro</code> tag allows you define a new custom tag.</p>
   *
   * <p><pre>
   * &lt;macro name="Name" [targetNamespace="Namespace"]&gt;
   *   &lt;parameter name="Name" [optional="Boolean"] [default="Value"]/&gt;*
   *   body
   * &lt/macro&gt;
   * </pre></p>
   *
   *<p> For example:</p>
   *
   *<p><pre>
   * &lt;c:macro name="d"&gt;
   *   &lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;
   * &lt;/c:macro&gt;
   * </pre></p>
   *
   * <p>The tag being defined in this example is <code>&lt;d&gt;</code> and it 
   * can be used like any other tag:</p>
   *
   * <p><pre>
   *   &lt;d/&gt;
   * </pre></p>
   *
   * <p>However, when this tag is used it will be replaced with a row containing 
   * a single empty data cell.</p>
   * <p> When such a tag is used, the attributes and content of the tag become 
   * available as variables in the body of the <code>macro</code>'s definition, 
   * for example:</p>
   *
   * <p><pre>
   * &lt;c:macro name="tablerows"&gt;
   *   &lt;c:parameter name="list"/&gt;
   *   &lt;c:parameter name="color"/&gt;
   *   &lt;c:forEach var="item" items="${list}"&gt;
   *     &lt;tr&gt;&lt;td bgcolor="${color}"&gt;${item}&lt;/td&gt;&lt;/tr&gt;
   *   &lt;/c:forEach&gt;
   * &lt;/c:macro&gt;
   * </pre></p>
   *
   * <p>The <code>parameter</code> tags in the macro definition define formal 
   * parameters, which are replaced with the actual attribute values of the 
   * tag when it is used. The content of the tag is also available as a special 
   * variable <code>${content}</code>.</p><p>Assuming you had this code in your 
   * flowscript:</p>
   * <code>var greatlakes = ["Superior", "Michigan", "Huron", "Erie", "Ontario"];</code>
   * </p><p><code> sendPage(uri, {greatlakes: greatlakes});</code>
   * </p><p>and a template like this:</p>
   *
   * <p><pre>
   * &lt;table&gt;
   *    &lt;tablerows list="${greatlakes}" color="blue"/&gt;
   * &lt;/table&gt;
   * </pre></p>
   *
   * <p>When the <code>tablerows</code> tag is used in this situation the 
   * following output would be generated:
   * </p>
   *<p><pre>
   * &lt;table&gt;
   *   &lt;tr&gt;&lt;td bgcolor="blue"&gt;Superior&lt;/td&gt;&lt;/tr&gt;
   *   &lt;tr&gt;&lt;td bgcolor="blue"&gt;Michigan&lt;/td&gt;&lt;/tr&gt;
   *   &lt;tr&gt;&lt;td bgcolor="blue"&gt;Huron&lt;/td&gt;&lt;/tr&gt;
   *   &lt;tr&gt;&lt;td bgcolor="blue"&gt;Erie&lt;/td&gt;&lt;/tr&gt;
   *   &lt;tr&gt;&lt;td bgcolor="blue"&gt;Ontario&lt;/td&gt;&lt;/tr&gt;
   * &lt;/table&gt;
   * </pre></p>
   */
  public class JXTemplateGenerator extends ComposerGenerator {
  
      private static final JXPathContextFactory 
          jxpathContextFactory = JXPathContextFactory.newInstance();
  
      private static final char[] EMPTY_CHARS = "".toCharArray();
  
      private static final Attributes EMPTY_ATTRS = new AttributesImpl();
  
      private static final Iterator EMPTY_ITER = new Iterator() {
              public boolean hasNext() {
                  return false;
              }
              public Object next() {
                  return null;
              }
              public void remove() {
              }
          };
  
      private static final Iterator NULL_ITER = new Iterator() {
              public boolean hasNext() {
                  return true;
              }
              public Object next() {
                  return null;
              }
              public void remove() {
              }
          };
      
      private static final Locator NULL_LOCATOR = new LocatorImpl();
  
      /**
       * Jexl Introspector that supports Rhino JavaScript objects
       * as well as Java Objects
       */
      static class JSIntrospector extends UberspectImpl {
          
          static class JSMethod implements VelMethod {
              
              Scriptable scope;
              String name;
              
              public JSMethod(Scriptable scope, String name) {
                  this.scope = scope;
                  this.name = name;
              }
              
              public Object invoke(Object thisArg, Object[] args)
                  throws Exception {
                  Context cx = Context.enter();
                  try {
                      Object result; 
                      Scriptable thisObj;
                      if (!(thisArg instanceof Scriptable)) {
                          thisObj = Context.toObject(thisArg, scope);
                      } else {
                          thisObj = (Scriptable)thisArg;
                      }
                      result = ScriptableObject.getProperty(thisObj, name);
                      Object[] newArgs = null;
                      if (args != null) {
                          newArgs = new Object[args.length];
                          for (int i = 0; i < args.length; i++) {
                              newArgs[i] = args[i];
                              if (args[i] != null && 
                                  !(args[i] instanceof Number) &&
                                  !(args[i] instanceof Boolean) &&
                                  !(args[i] instanceof String) &&
                                  !(args[i] instanceof Scriptable)) {
                                  newArgs[i] = Context.toObject(args[i], scope);
                              }
                          }
                      }
                      result = ScriptRuntime.call(cx, result, thisObj, 
                                                  newArgs, scope);
                      if (result == Undefined.instance ||
                          result == ScriptableObject.NOT_FOUND) {
                          result = null;
                      } else while (result instanceof Wrapper) {
                          result = ((Wrapper)result).unwrap();
                      }
                      return result;
                  } catch (JavaScriptException e) {
                      throw new java.lang.reflect.InvocationTargetException(e);
                  } finally {
                      Context.exit();
                  }
              }
              
              public boolean isCacheable() {
                  return false;
              }
              
              public String getMethodName() {
                  return name;
              }
              
              public Class getReturnType() {
                  return Object.class;
              }
              
          }
          
          static class JSPropertyGet implements VelPropertyGet {
              
              Scriptable scope;
              String name;
              
              public JSPropertyGet(Scriptable scope, String name) {
                  this.scope = scope;
                  this.name = name;
              }
              
              public Object invoke(Object thisArg) throws Exception {
                  Context.enter();
                  try {
                      Scriptable thisObj;
                      if (!(thisArg instanceof Scriptable)) {
                          thisObj = Context.toObject(thisArg, scope);
                      } else {
                          thisObj = (Scriptable)thisArg;
                      }
                      Object result = ScriptableObject.getProperty(thisObj, name);
                      if (result == Undefined.instance || 
                          result == ScriptableObject.NOT_FOUND) {
                          result = null;
                      } else while (result instanceof Wrapper) {
                          result = ((Wrapper)result).unwrap();
                      }
                      return result;
                  } finally {
                      Context.exit();
                  }
              }
              
              public boolean isCacheable() {
                  return false;
              }
              
              public String getMethodName() {
                  return name;
              }
              
          }
          
          static class JSPropertySet implements VelPropertySet {
              
              Scriptable scope;
              String name;
              
              public JSPropertySet(Scriptable scope, String name) {
                  this.scope = scope;
                  this.name = name;
              }
              
              public Object invoke(Object thisArg, Object rhs) throws Exception {
                  Context.enter();
                  try {
                      Scriptable thisObj;
                      Object arg = rhs;
                      if (!(thisArg instanceof Scriptable)) {
                          thisObj = Context.toObject(thisArg, scope);
                      } else {
                          thisObj = (Scriptable)thisArg;
                      }
                      if (arg != null && 
                          !(arg instanceof Number) &&
                          !(arg instanceof Boolean) &&
                          !(arg instanceof String) &&
                          !(arg instanceof Scriptable)) {
                          arg = Context.toObject(arg, scope);
                      }
                      ScriptableObject.putProperty(thisObj, name, arg);
                      return rhs;
                  } finally {
                      Context.exit();
                  }
              }
              
              public boolean isCacheable() {
                  return false;
              }
              
              public String getMethodName() {
                  return name;        
              }
          }
          
          static class NativeArrayIterator implements Iterator {
              
              NativeArray arr;
              int index;
              
              public NativeArrayIterator(NativeArray arr) {
                  this.arr = arr;
                  this.index = 0;
              }
              
              public boolean hasNext() {
                  return index < (int)arr.jsGet_length();
              }
              
              public Object next() {
                  Context.enter();
                  try {
                      Object result = arr.get(index++, arr);
                      if (result == Undefined.instance ||
                          result == ScriptableObject.NOT_FOUND) {
                          result = null;
                      } else while (result instanceof Wrapper) {
                          result = ((Wrapper)result).unwrap();
                      }
                      return result;
                  } finally {
                      Context.exit();
                  }
              }
              
              public void remove() {
                  arr.delete(index);
              }
          }
          
          static class ScriptableIterator implements Iterator {
              
              Scriptable scope;
              Object[] ids;
              int index;
              
              public ScriptableIterator(Scriptable scope) {
                  this.scope = scope;
                  this.ids = scope.getIds();
                  this.index = 0;
              }
              
              public boolean hasNext() {
                  return index < ids.length;
              }
              
              public Object next() {
                  Context.enter();
                  try {
                      Object result = 
                          ScriptableObject.getProperty(scope, 
                                                       ids[index++].toString());
                      if (result == Undefined.instance ||
                          result == ScriptableObject.NOT_FOUND) {
                          result = null;
                      } else while (result instanceof Wrapper) {
                          result = ((Wrapper)result).unwrap();
                      }
                      return result;
                  } finally {
                      Context.exit();
                  }
              }
              
              public void remove() {
                  Context.enter();
                  try {
                      scope.delete(ids[index].toString());
                  } finally {
                      Context.exit();
                  }
              }
          }
          
          public Iterator getIterator(Object obj, Info i)
              throws Exception {
              if (!(obj instanceof Scriptable)) {
                  return super.getIterator(obj, i);
              }
              if (obj instanceof NativeArray) {
                  return new NativeArrayIterator((NativeArray)obj);
              }
              return new ScriptableIterator((Scriptable)obj);
          }
          
          public VelMethod getMethod(Object obj, String methodName, 
                                     Object[] args, Info i)
              throws Exception {
              if (!(obj instanceof Scriptable)) {
                  return super.getMethod(obj, methodName, args, i);
              }
              return new JSMethod((Scriptable)obj, methodName);
          }
          
          public VelPropertyGet getPropertyGet(Object obj, String identifier, 
                                               Info i)
              throws Exception {
              if (!(obj instanceof Scriptable)) {
                  return super.getPropertyGet(obj, identifier, i);
              }
              return new JSPropertyGet((Scriptable)obj, identifier);
          }
          
          public VelPropertySet getPropertySet(Object obj, String identifier, 
                                               Object arg, Info i)
              throws Exception {
              if (!(obj instanceof Scriptable)) {
                  return super.getPropertySet(obj, identifier, arg, i);
              }
              return new JSPropertySet((Scriptable)obj, identifier);
          }
      }
  
      static class MyJexlContext 
          extends HashMap implements JexlContext {
          public Map getVars() {
              return this;
          }
          public void setVars(Map map) {
              putAll(map);
          }
          public Object get(Object key) {
              Object result = super.get(key);
              if (result != null) {
                  return result;
              }
              MyJexlContext c = closure;
              for (; c != null; c = c.closure) {
                  result = c.get(key);
                  if (result != null) {
                      return result;
                  }
              }
              return result;
          }
          MyJexlContext closure;
          MyJexlContext() {
          }
          MyJexlContext(MyJexlContext closure) {
              this.closure = closure;
          }
      }
  
      static class MyVariables implements Variables {
  
          Map localVariables = new HashMap();
  
          static final String[] VARIABLES = new String[] {
              "continuation",
              "flowContext",
              "request",
              "response",
              "context",
              "session",
              "parameters"
          };
  
          Object bean, kont, request, response,
              session, context, parameters;
  
          MyVariables(Object bean, WebContinuation kont,
                      Request request, Response response,
                      org.apache.cocoon.environment.Context context,
                      Parameters parameters) {
              this.bean = bean;
              this.kont = kont;
              this.request = request;
              this.session = request.getSession(false);
              this.response = response;
              this.context = context;
              this.parameters = parameters;
          }
  
          public boolean isDeclaredVariable(String varName) {
              for (int i = 0; i < VARIABLES.length; i++) {
                  if (varName.equals(VARIABLES[i])) {
                      return true;
                  }
              }
              return localVariables.containsKey(varName);
          }
          
          public Object getVariable(String varName) {
              if (varName.equals("continuation")) {
                  return kont;
              } else if (varName.equals("flowContext")) {
                  return bean;
              } else if (varName.equals("request")) {
                  return request;
              } else if (varName.equals("response")) {
                  return response;
              } else if (varName.equals("session")) {
                  return session;
              } else if (varName.equals("context")) {
                  return context;
              } else if (varName.equals("parameters")) {
                  return parameters;
              }
              return localVariables.get(varName);
          }
          
          public void declareVariable(String varName, Object value) {
              localVariables.put(varName, value);
          }
          
          public void undeclareVariable(String varName) {
              localVariables.remove(varName);
          }
      }
  
      static {
          // Hack: there's no _nice_ way to add my introspector to Jexl right now
          try {
              Field field = 
                  org.apache.commons.jexl.util.Introspector.class.getDeclaredField("uberSpect");
              field.setAccessible(true);
              field.set(null, new JSIntrospector());
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
  
      final static String NS = 
          "http://apache.org/cocoon/templates/jx/1.0";
  
      final static String TEMPLATE = "template";
      final static String FOR_EACH = "forEach";
      final static String IF = "if";
      final static String CHOOSE = "choose";
      final static String WHEN = "when";
      final static String OTHERWISE = "otherwise";
      final static String OUT = "out";
      final static String IMPORT = "import";
      final static String SET = "set";
      final static String MACRO = "macro";
      final static String PARAMETER = "parameter";
  
  
      /**
       * Compile a single Jexl expr (contained in ${}) or XPath expression
       * (contained in #{}) 
       */
  
      private static Expression compileExpr(String expr, String errorPrefix, 
                                            Locator location) 
          throws SAXParseException {
          try {
              return compileExpr(expr);
          } catch (Exception exc) {
              throw new SAXParseException(errorPrefix + exc.getMessage(),
                                          location, exc);
          } catch (Error err) {
              throw new SAXParseException(errorPrefix + err.getMessage(),
                                          location, null);
          }
      }
  
      private static Expression compileExpr(String inStr) throws Exception {
          try {
              if (inStr == null) return null;
              StringReader in = new StringReader(inStr.trim());
              int ch;
              StringBuffer expr = new StringBuffer();
              boolean xpath = false;
              boolean inExpr = false;
              while ((ch = in.read()) != -1) {
                  char c = (char)ch;
                  if (inExpr) {
                      if (c == '\\') {
                          ch = in.read();
                          if (ch == -1) {
                              expr.append('\\');
                          } else {
                              expr.append((char)ch);
                          }
                      } else if (c == '}') {
                          String str = expr.toString();
                          return compile(str, xpath);
                      } else {
                          expr.append(c);
                      }
                  } else {
                      if (c == '$' || c == '#') {
                          ch = in.read();
                          if (ch == '{') {
                              inExpr = true;
                              xpath = c == '#';
                              continue;
                          }
                      }
                      // hack: invalid expression?
                      // just return the original and swallow exception
                      return new Expression(inStr, null);
                  }
              }
          } catch (IOException ignored) {
              ignored.printStackTrace();
          }
          return new Expression(inStr, null);
      }
      // Compile an integer expression (returns either a Compiled Expression
      // or an Integer literal)
      private static Expression compileInt(String val, String msg, Locator location) 
          throws SAXException {
          Expression res = compileExpr(val, msg, location);
          if (res == null) return null;
          if (res.compiledExpression != null) {
              res.compiledExpression = Integer.valueOf(res.raw);
          }
          return res;
      }
  
      private static Expression compile(final String variable, boolean xpath) 
          throws Exception {
          Object compiled;
          if (xpath) {
              compiled = JXPathContext.compile(variable);
          } else {
              compiled = ExpressionFactory.createExpression(variable);
          }
          return new Expression(variable, compiled);
      }
  
      private Object getValue(Expression expr, JexlContext jexlContext,
                              JXPathContext jxpathContext) 
          throws Exception {
          Object compiled = expr.compiledExpression;
          try {
              if (compiled instanceof CompiledExpression) {
                  CompiledExpression e = (CompiledExpression)compiled;
                  return e.getValue(jxpathContext);
              } else if (compiled instanceof org.apache.commons.jexl.Expression) {
                  org.apache.commons.jexl.Expression e = 
                      (org.apache.commons.jexl.Expression)compiled;
                  return e.evaluate(jexlContext);
              }
              return compiled;
          } catch (InvocationTargetException e) {
              Throwable t = e.getTargetException();
              if (t instanceof Exception) {
                  throw (Exception)t;
              }
              throw (Error)t;
          }
      }
  
      private int getIntValue(Expression expr, JexlContext jexlContext,
                              JXPathContext jxpathContext) 
          throws Exception {
          Object res = getValue(expr, jexlContext, jxpathContext);
          if (res instanceof Number) {
              return ((Number)res).intValue();
          }
          return 0;
      }
  
      // Hack: try to prevent JXPath from converting result to a String
      private Object getNode(Expression expr, JexlContext jexlContext,
                             JXPathContext jxpathContext) 
          throws Exception {
          try {
              Object compiled = expr.compiledExpression;
              if (compiled instanceof CompiledExpression) {
                  CompiledExpression e = (CompiledExpression)compiled;
                  return e.getPointer(jxpathContext, expr.raw).getNode();
              } else if (compiled instanceof org.apache.commons.jexl.Expression) {
                  org.apache.commons.jexl.Expression e = 
                      (org.apache.commons.jexl.Expression)compiled;
                  return e.evaluate(jexlContext);
              }
              return compiled;
          } catch (InvocationTargetException e) {
              Throwable t = e.getTargetException();
              if (t instanceof Exception) {
                  throw (Exception)t;
              }
              throw (Error)t;
          }
      }
  
      static class Event {
          final Locator location;
          Event next; // in document order
          Event(Locator locator) {
              this.location = 
                  locator == null ? NULL_LOCATOR : new LocatorImpl(locator);
          }
  
          public String locationString() {
              String result = "";
              String systemId = location.getSystemId();
              if (systemId != null) {
                  result += systemId + ", ";
              }
              result += "Line " + location.getLineNumber();
              int col = location.getColumnNumber();
              if (col > 0) {
                  result += "." + col;
              }
              return result;
          }
          
      }
  
  
      static class TextEvent extends Event {
          TextEvent(Locator location, 
                    char[] chars, int start, int length) 
              throws SAXException {
              super(location);
              StringBuffer buf = new StringBuffer();
              this.raw = new char[length];
              System.arraycopy(chars, start, this.raw, 0, length);
              CharArrayReader in = new CharArrayReader(chars, start, length);
              int ch;
              boolean inExpr = false;
              boolean xpath = false;
              try {
                  top: while ((ch = in.read()) != -1) {
                      char c = (char)ch;
                      processChar: while (true) {
                          if (inExpr) {
                              if (c == '\\') {
                                  ch = in.read();
                                  if (ch == -1) {
                                      buf.append('\\');
                                  } else {
                                      buf.append((char)ch);
                                  } 
                              } else if (c == '}') {
                                  String str = buf.toString();
                                  Object compiledExpression;
                                  try {
                                      if (xpath) {
                                          compiledExpression = 
                                              JXPathContext.compile(str);
                                      } else {
                                          compiledExpression = 
                                              ExpressionFactory.createExpression(str);
                                      }
                                  } catch (Exception exc) {
                                      throw new SAXParseException(exc.getMessage(),
                                                                  location,
                                                                  exc);
                                  }
                                  substitutions.add(new Expression(str,
                                                                   compiledExpression));
                                  buf.setLength(0);
                                  inExpr = false;
                              } else {
                                  buf.append(c);
                              }
                          } else {
                              if (c == '\\') {
                                  ch = in.read();
                                  if (ch == -1) {
                                      buf.append('\\');
                                  } else {
                                      buf.append((char)ch);
                                  }
                              } else if (c == '$' || c == '#') {
                                  ch = in.read();
                                  if (ch == '{') {
                                      xpath = c == '#';
                                      inExpr = true;
                                      if (buf.length() > 0) {
                                          char[] charArray = 
                                              new char[buf.length()];
                                          
                                          buf.getChars(0, buf.length(),
                                                       charArray, 0);
                                          substitutions.add(charArray);
                                          buf.setLength(0);
                                      }
                                      continue top;
                                  } else {
                                      buf.append(c);
                                      if (ch != -1) {
                                          c = (char)ch;
                                          continue processChar;
                                      }
                                  }
                              } else {
                                  buf.append(c);
                              }
                          }
                          break;
                      }
                  }
              } catch (IOException ignored) {
                  // won't happen
                  ignored.printStackTrace();
              }
              if (buf.length() > 0) {
                  char[] charArray = 
                      new char[buf.length()];
                  buf.getChars(0, buf.length(), charArray, 0);
                  substitutions.add(charArray);
              } else if (substitutions.size() == 0) {
                  substitutions.add(EMPTY_CHARS);
              }
          }
          final List substitutions = new LinkedList();
          final char[] raw;
      }
  
      static class Characters extends TextEvent {
          Characters(Locator location, 
                     char[] chars, int start, int length) 
              throws SAXException {
              super(location, chars, start, length);
          }
  
      }
  
      static class StartDocument extends Event {
          StartDocument(Locator location) {
              super(location);
          }
          long compileTime;
          EndDocument endDocument; // null if document fragment
      }
  
      static class EndDocument extends Event {
          EndDocument(Locator location) {
              super(location);
          }
      }
  
      static class EndElement extends Event {
          EndElement(Locator location, 
                     StartElement startElement) {
              super(location);
              this.startElement = startElement;
          }
          final StartElement startElement;
      }
  
      static class EndPrefixMapping extends Event {
          EndPrefixMapping(Locator location, String prefix) {
              super(location);
              this.prefix = prefix;
          }
          final String prefix;
      }
      
      static class IgnorableWhitespace extends TextEvent {
          IgnorableWhitespace(Locator location, 
                              char[] chars, int start, int length) 
              throws SAXException {
              super(location, chars, start, length);
          }
      }
  
      static class ProcessingInstruction extends Event {
          ProcessingInstruction(Locator location,
                                String target, String data) {
              super(location);
              this.target = target;
              this.data = data;
          }
          final String target;
          final String data;
      }
  
      static class SkippedEntity extends Event {
          SkippedEntity(Locator location, String name) {
              super(location);
              this.name = name;
          }
          final String name;
      }
  
      abstract static class AttributeEvent {
          AttributeEvent(String namespaceURI, String localName, String raw,
                         String type) {
              this.namespaceURI = namespaceURI;
              this.localName = localName;
              this.raw = raw;
              this.type = type;
          }
          final String namespaceURI;
          final String localName;
          final String raw;
          final String type;
      }
      
      static class CopyAttribute extends AttributeEvent {
          CopyAttribute(String namespaceURI, 
                        String localName,
                        String raw,
                        String type, String value) {
              super(namespaceURI, localName, raw, type);
              this.value = value;
          }
          final String value;
      }
      
      static class Subst {
      }
      
      static class Literal extends Subst {
          Literal(String val) {
              this.value = val;
          }
          final String value;
      }
      
      static class Expression extends Subst {
          Expression(String raw, Object expr) {
              this.raw = raw;
              this.compiledExpression = expr;
          }
          String raw;
          Object compiledExpression;
      }
  
      static class SubstituteAttribute extends AttributeEvent {
          SubstituteAttribute(String namespaceURI,
                              String localName,
                              String raw,
                              String type, List substs) {
              super(namespaceURI, localName, raw, type);
              this.substitutions = substs;
          }
          final List substitutions;
      }
  
      static class StartElement extends Event {
          StartElement(Locator location, String namespaceURI,
                       String localName, String raw,
                       Attributes attrs) 
              throws SAXException {
              super(location);
              this.namespaceURI = namespaceURI;
              this.localName = localName;
              this.raw = raw;
              this.qname = "{"+namespaceURI+"}"+localName;
              StringBuffer buf = new StringBuffer();
              for (int i = 0, len = attrs.getLength(); i < len; i++) {
                  String uri = attrs.getURI(i);
                  String local = attrs.getLocalName(i);
                  String qname = attrs.getQName(i);
                  String type = attrs.getType(i);
                  String value = attrs.getValue(i);
                  StringReader in = new StringReader(value);
                  int ch;
                  buf.setLength(0);
                  boolean inExpr = false;
                  List substEvents = new LinkedList();
                  boolean xpath = false;
                  try {
                      top: while ((ch = in.read()) != -1) {
                          char c = (char)ch;
                          processChar: while (true) {
                              if (inExpr) {
                                  if (c == '\\') {
                                      ch = in.read();
                                      if (ch == -1) {
                                          buf.append('\\');
                                      } else {
                                          buf.append((char)ch);
                                      }
                                  } else if (c == '}') {
                                      String str = buf.toString();
                                      Expression compiledExpression;
                                      try {
                                          compiledExpression =
                                              compile(str, xpath);
                                      } catch (Exception exc) {
                                          throw new SAXParseException(exc.getMessage(),
                                                                      location,
                                                                      exc);
                                      } catch (Error err) {
                                          throw new SAXParseException(err.getMessage(),
                                                                      location,
                                                                      null);
                                          
                                      } 
                                      substEvents.add(compiledExpression);
                                      buf.setLength(0);
                                      inExpr = false;
                                  } else {
                                      buf.append(c);
                                  }
                              } else {
                                  if (c == '\\') {
                                      ch = in.read();
                                      if (ch == -1) {
                                          buf.append('\\');
                                      } else {
                                          buf.append((char)ch);
                                      }
                                  } if (c == '$' || c == '#') {
                                      ch = in.read();
                                      if (ch == '{') {
                                          if (buf.length() > 0) {
                                              substEvents.add(new Literal(buf.toString()));
                                              buf.setLength(0);
                                          }
                                          inExpr = true;
                                          xpath = c == '#';
                                          continue top;
                                      } else {
                                          buf.append(c);
                                          if (ch != -1) {
                                              c = (char)ch;
                                              continue processChar;
                                          }
                                      }
                                  } else {
                                      buf.append(c);
                                  }
                              }
                              break;
                          } 
                      }
                  } catch (IOException ignored) {
                      ignored.printStackTrace();
                  }
                  if (buf.length() > 0) {
                      if (substEvents.size() == 0) {
                          attributeEvents.add(new CopyAttribute(uri,
                                                                local,
                                                                qname,
                                                                type,
                                                                value));
                      } else {
                          substEvents.add(new Literal(buf.toString()));
                          attributeEvents.add(new SubstituteAttribute(uri,
                                                                      local,
                                                                      qname,
                                                                      type,
                                                                      substEvents));
                      }
                  } else {
                      if (substEvents.size() > 0) {
                          attributeEvents.add(new SubstituteAttribute(uri,
                                                                      local,
                                                                      qname,
                                                                      type,
                                                                      substEvents));
                      } else {
                          attributeEvents.add(new CopyAttribute(uri, local,
                                                                qname, type,
                                                                 ""));
                      }
                  }
              }
              this.attributes = new AttributesImpl(attrs);
          }
          final String namespaceURI;
          final String localName;
          final String raw;
          final String qname;
          final List attributeEvents = new LinkedList();
          final Attributes attributes;
          EndElement endElement;
      }
  
      static class StartPrefixMapping extends Event {
          StartPrefixMapping(Locator location, String prefix,
                             String uri) {
              super(location);
              this.prefix = prefix;
              this.uri = uri;
          }
          final String prefix;
          final String uri;
      }
  
      static class Comment extends TextEvent {
          Comment(Locator location, char[] chars,
                  int start, int length)
              throws SAXException {
              super(location, chars, start, length);
          }
      }
  
      static class EndCDATA extends Event {
          EndCDATA(Locator location) {
              super(location);
          }
      }
  
      static class EndDTD extends Event {
          EndDTD(Locator location) {
              super(location);
          }
      }
  
      static class EndEntity extends Event {
          EndEntity(Locator location, String name) {
              super(location);
              this.name = name;
          }
          final String name;
      }
  
      static class StartCDATA extends Event {
          StartCDATA(Locator location) {
              super(location);
          }
      }
  
      static class StartDTD extends Event {
          StartDTD(Locator location, String name, 
                   String publicId, String systemId) {
              super(location);
              this.name = name;
              this.publicId = publicId;
              this.systemId = systemId;
          }
          final String name;
          final String publicId;
          final String systemId;
      }
      
      static class StartEntity extends Event {
          public StartEntity(Locator location, String name) {
              super(location);
              this.name = name;
          }
          final String name;
      }
  
      static class StartInstruction extends Event {
          StartInstruction(StartElement startElement) {
              super(startElement.location);
              this.startElement = startElement;
          }
          final StartElement startElement;
          EndInstruction endInstruction;
      }
      
      static class EndInstruction extends Event {
          EndInstruction(Locator locator, 
                         StartInstruction startInstruction) {
              super(locator);
              this.startInstruction = startInstruction;
              startInstruction.endInstruction = this;
          }
          final StartInstruction startInstruction;
      }
  
      static class StartForEach extends StartInstruction {
          StartForEach(StartElement raw,
                       Expression items, String var,
                       Expression begin, Expression end, Expression step) {
              super(raw);
              this.items = items;
              this.var = var;
              this.begin = begin;
              this.end = end;
              this.step = step;
          }
          final Expression items;
          final String var;
          final Expression begin;
          final Expression end;
          final Expression step;
      }
      
      static class StartIf extends StartInstruction {
          StartIf(StartElement raw, Expression test) {
              super(raw);
              this.test = test;
          }
          final Expression test;
      }
  
  
      static class StartChoose extends StartInstruction {
          StartChoose(StartElement raw) {
              super(raw);
          }
          StartWhen firstChoice;
          StartOtherwise otherwise;
      }
  
      static class StartWhen extends StartInstruction {
          StartWhen(StartElement raw, Expression test) {
              super(raw);
              this.test = test;
          }
          final Expression test;
          StartWhen nextChoice;
      }
  
      static class StartOtherwise extends StartInstruction {
          StartOtherwise(StartElement raw) {
              super(raw);
          }
      }
  
      static class StartOut extends StartInstruction {
          StartOut(StartElement raw, Expression expr) {
              super(raw);
              this.compiledExpression = expr;
          }
          final Expression compiledExpression;
      }
  
      static class StartImport extends StartInstruction {
          StartImport(StartElement raw, AttributeEvent uri, 
                      Expression select) {
              super(raw);
              this.uri = uri;
              this.select = select;
          }
          final AttributeEvent uri;
          final Expression select;
      }
  
      static class StartTemplate extends StartInstruction {
          StartTemplate(StartElement raw) {
              super(raw);
          }
      }
  
      static class StartDefine extends StartInstruction {
          StartDefine(StartElement raw, String namespace, String name) {
              super(raw);
              this.namespace = namespace;
              this.name = name;
              this.qname = "{"+namespace+"}"+name;
              this.parameters = new HashMap();
          }
          final String name;
          final String namespace;
          final String qname;
          final Map parameters;
          Event body;
          void finish() throws SAXException {
              Event e = next;
              boolean params = true;
              while (e != this.endInstruction) {
                  if (e instanceof StartParameter) {
                      StartParameter startParam = (StartParameter)e;
                      if (!params) {
                          throw new SAXParseException("<parameter> not allowed here: \""+startParam.name +"\"", startParam.location, null);
                      }
                      Object prev = 
                          parameters.put(startParam.name, startParam);
                      if (prev != null) {
                          throw new SAXParseException("duplicate parameter: \""+startParam.name +"\"", location, null);
                      }
                      e = startParam.endInstruction.next;
                  } else if (e instanceof IgnorableWhitespace) {
                  } else if (e instanceof Characters) {
                      // check for whitespace
                      char[] ch = ((TextEvent)e).raw;
                      for (int i = 0; i < ch.length; i++) {
                          if (!Character.isWhitespace(ch[i])) {
                              if (params) {
                                  params = false;
                                  body = e;
                              }
                              break;
                          }
                      }
                  } else {
                      if (params) {
                          params = false;
                          body = e;
                      }
                  }
                  e = e.next;
              }
              if (this.body == null) {
                  this.body = this.endInstruction;
              }
          }
      }
  
      static class StartParameter extends StartInstruction {
          StartParameter(StartElement raw, String name, String optional,
                         String default_) {
              super(raw);
              this.name = name;
              this.optional = optional;
              this.default_ = default_;
          }
          final String name;
          final String optional;
          final String default_;
      }
  
      static class StartSet extends StartInstruction {
          StartSet(StartElement raw, String var, Expression value) {
              super(raw);
              this.var = var;
              this.value = value;
          }
          final String var;
          final Expression value;
      }
  
      static class Parser implements ContentHandler, LexicalHandler {
  
          StartDocument startEvent;
          Event lastEvent;
          Stack stack = new Stack();
          Locator locator;
          Locator charLocation;
          StringBuffer charBuf;
  
          public Parser() {
          }
  
          StartDocument getStartEvent() {
              return startEvent;
          }
  
          private void addEvent(Event ev) throws SAXException {
              if (ev == null) {
                  throw new NullPointerException("null event");
              }
              if (lastEvent == null) {
                  lastEvent = startEvent = new StartDocument(locator);
              } else {
                  flushChars();
              }
              lastEvent.next = ev;
              lastEvent = ev;
          }
  
          void flushChars() throws SAXException {
              if (charBuf != null) {
                  char[] chars = new char[charBuf.length()];
                  charBuf.getChars(0, charBuf.length(), chars, 0);
                  Characters ev = new Characters(charLocation,
                                                 chars, 0, chars.length);
                  lastEvent.next = ev;
                  lastEvent = ev;
                  charLocation = null;
                  charBuf = null;
              }
          }
  
          public void characters(char[] ch, int start, int length) 
              throws SAXException {
              if (charBuf == null) {
                  charBuf = new StringBuffer();
                  if (locator != null) {
                      charLocation = new LocatorImpl(locator);
                  } else {
                      charLocation = NULL_LOCATOR;
                  }
              }
              charBuf.append(ch, start, length);
          }
  
          public void endDocument() throws SAXException {
              StartDocument startDoc = (StartDocument)stack.pop();
              EndDocument endDoc = new EndDocument(locator);
              startDoc.endDocument = endDoc;
              addEvent(endDoc);
          }
  
          public void endElement(String namespaceURI,
                                 String localName,
                                 String raw) 
              throws SAXException {
              Event start = (Event)stack.pop();
              Event newEvent = null;
              if (NS.equals(namespaceURI)) {
                  StartInstruction startInstruction = 
                      (StartInstruction)start;
                  EndInstruction endInstruction = 
                      new EndInstruction(locator, startInstruction);
                  newEvent = endInstruction;
                  if (start instanceof StartWhen) {
                      StartWhen startWhen = (StartWhen)start;
                      StartChoose startChoose = (StartChoose)stack.peek();
                      if (startChoose.firstChoice != null) {
                          StartWhen w = startChoose.firstChoice;
                          while (w.nextChoice != null) {
                              w = w.nextChoice;
                          }
                          w.nextChoice = startWhen;
                      } else {
                          startChoose.firstChoice = startWhen;
                      }
                  } else if (start instanceof StartOtherwise) {
                      StartOtherwise startOtherwise = 
                          (StartOtherwise)start;
                      StartChoose startChoose = (StartChoose)stack.peek();
                      startChoose.otherwise = startOtherwise;
                  } 
              } else {
                  StartElement startElement = (StartElement)start;
                  newEvent = startElement.endElement = 
                      new EndElement(locator, startElement);
              }
              addEvent(newEvent);
              if (start instanceof StartDefine) {
                  StartDefine startDefine = (StartDefine)start;
                  startDefine.finish();
              }
          }
  
          
          public void endPrefixMapping(String prefix) throws SAXException {
              EndPrefixMapping endPrefixMapping = 
                  new EndPrefixMapping(locator, prefix);
              addEvent(endPrefixMapping);
          }
  
          public void ignorableWhitespace(char[] ch, int start, int length) 
              throws SAXException {
              Event ev = new IgnorableWhitespace(locator, ch, start, length);
              addEvent(ev);
          }
  
          public void processingInstruction(String target, String data) 
              throws SAXException {
              Event pi = new ProcessingInstruction(locator, target, data);
              addEvent(pi);
          }
  
          public void setDocumentLocator(Locator locator) {
              this.locator = locator;
          }
  
          public void skippedEntity(String name) throws SAXException {
              addEvent(new SkippedEntity(locator, name));
          }
  
          public void startDocument() {
              startEvent = new StartDocument(locator);
              lastEvent = startEvent;
              stack.push(lastEvent);
          }
          
  
  
          public void startElement(String namespaceURI,
                                   String localName,
                                   String qname,
                                   Attributes attrs) 
              throws SAXException {
              Event newEvent = null;
              StartElement startElement = 
                  new StartElement(locator, namespaceURI,
                                   localName, qname, attrs);
              if (NS.equals(namespaceURI)) {
                  if (localName.equals(FOR_EACH)) {
                      String items = attrs.getValue("items");
                      String select = attrs.getValue("select");
                      Expression begin = compileInt(attrs.getValue("begin"),
                                                    FOR_EACH, locator);
                      Expression end = compileInt(attrs.getValue("end"),
                                                  FOR_EACH, locator);
                      Expression step = compileInt(attrs.getValue("step"),
                                                   FOR_EACH,
                                                   locator);
                      String var = attrs.getValue("var");
                      if (items == null) {
                          if (select == null && (begin == null || end == null)) {
                              throw new SAXParseException("forEach: \"select\", \"items\", or both \"begin\" and \"end\" must be specified", locator, null);
                          }
                      } else if (select != null) {
                          throw new SAXParseException("forEach: only one of \"select\" or \"items\" may be specified", locator, null);
                      }
                      Expression expr;
                      expr = compileExpr(items == null ? select : items,
                                         null, locator);
                      StartForEach startForEach = 
                          new StartForEach(startElement, expr, 
                                           var, begin, end, step);
                      newEvent = startForEach;
                  } else if (localName.equals(CHOOSE)) {
                      StartChoose startChoose = new StartChoose(startElement);
                      newEvent = startChoose;
                  } else if (localName.equals(WHEN)) {
                      if (stack.size() == 0 ||
                          !(stack.peek() instanceof StartChoose)) {
                          throw new SAXParseException("<when> must be within <choose>", locator, null);
                      }
                      String test = attrs.getValue("test");
                      if (test == null) {
                          throw new SAXParseException("when: \"test\" is required", locator, null);
                      }
                      Expression expr;
                      expr = compileExpr(test, "when: \"test\": ", locator);
                      StartWhen startWhen = new StartWhen(startElement, expr);
                      newEvent = startWhen;
                  } else if (localName.equals(OUT)) {
                      String value = attrs.getValue("value");
                      if (value == null) {
                          throw new SAXParseException("out: \"value\" is required", locator, null);
                      }
                      Expression expr = compileExpr(value, 
                                                "out: \"value\": ", 
                                                locator);
                      newEvent = new StartOut(startElement, expr);
                  } else if (localName.equals(OTHERWISE)) {
                      if (stack.size() == 0 ||
                          !(stack.peek() instanceof StartChoose)) {
                          throw new SAXParseException("<otherwise> must be within <choose>", locator, null);
                      }
                      StartOtherwise startOtherwise = 
                          new StartOtherwise(startElement);
                      newEvent = startOtherwise;
                  } else if (localName.equals(IF)) {
                      String test = attrs.getValue("test");
                      if (test == null) {
                          throw new SAXParseException("if: \"test\" is required", locator, null);
                      }
                      Expression expr = 
                          compileExpr(test, "if: \"test\": ", locator);
                      StartIf startIf = 
                          new StartIf(startElement, expr);
                      newEvent = startIf;
                  } else if (localName.equals(MACRO)) {
                      // <macro name="myTag" targetNamespace="myNamespace">
                      // <parameter name="paramName" required="Boolean" default="value"/>
                      // body
                      // </macro>
                      String namespace = attrs.getValue("targetNamespace");
                      if (namespace == null) {
                          namespace = "";
                      }
                      String name = attrs.getValue("name");
                      if (name == null) {
                          throw new SAXParseException("macro: \"name\" is required", locator, null);
                      }
                      StartDefine startDefine = 
                          new StartDefine(startElement, namespace, name); 
                      newEvent = startDefine;
                  } else if (localName.equals(PARAMETER)) {
                      boolean syntaxErr = false;
                      if (stack.size() < 1 ||
                          !(stack.peek() instanceof StartDefine)) {
                          syntaxErr = true;
                      } else {
                          StartDefine startDefine = (StartDefine)stack.peek();
                          String name = attrs.getValue("name");
                          String optional = attrs.getValue("optional");
                          String default_ = attrs.getValue("default");
                          if (name == null) {
                              throw new SAXParseException("parameter: \"name\" is required", locator, null);
                          }
                          StartParameter startParameter = 
                              new StartParameter(startElement, 
                                                 name, optional, default_);
                          newEvent = startParameter;
                      }
                      if (syntaxErr) {
                          throw new SAXParseException("<parameter> not allowed here", locator, null);
                      }
                  } else if (localName.equals(SET)) {
                      String var = attrs.getValue("var");
                      String value = attrs.getValue("value");
                      Expression valueExpr = null;
                      if (value != null) {
                          valueExpr = 
                              compileExpr(value, "set: \"value\":",
                                          locator);
                      } 
                      StartSet startSet = new StartSet(startElement, var, valueExpr);
                      newEvent = startSet;
                  } else if (localName.equals(IMPORT)) {
                      // <import uri="${root}/foo/bar.xml" context="${foo}"/>
                      AttributeEvent uri = null;
                      Iterator iter = startElement.attributeEvents.iterator();
                      while (iter.hasNext()) {
                          AttributeEvent e = (AttributeEvent)iter.next();
                          if (e.localName.equals("uri")) {
                              uri = e;
                              break;
                          }
                      }
                      if (uri == null) {
                          throw new SAXParseException("import: \"uri\" is required", locator, null);
                      }
                      // If "context" is present then its value will be used
                      // as the context object in the imported template
                      String select = attrs.getValue("context");
                      Expression expr = null;
                      if (select != null) {
                          expr = 
                              compileExpr(select, "import: \"context\": ",
                                          locator);
                      }
                      StartImport startImport = 
                          new StartImport(startElement, uri, expr);
                      newEvent = startImport;
                  } else if (localName.equals(TEMPLATE)) {
                      StartTemplate startTemplate =
                          new StartTemplate(startElement);
                      newEvent = startTemplate;
                  } else {
                      throw new SAXParseException("unrecognized tag: " + localName, locator, null);
                  }
              } else {
                  newEvent = startElement;
              }
              stack.push(newEvent);
              addEvent(newEvent);
          }
          
          public void startPrefixMapping(String prefix, String uri) 
              throws SAXException {
              addEvent(new StartPrefixMapping(locator, prefix, uri));
          }
  
          public void comment(char ch[], int start, int length) 
              throws SAXException {
              addEvent(new Comment(locator, ch, start, length));
          }
  
          public void endCDATA() throws SAXException {
              addEvent(new EndCDATA(locator));
          }
  
          public void endDTD() throws SAXException {
              addEvent(new EndDTD(locator));
          }
  
          public void endEntity(String name) throws SAXException {
              addEvent(new EndEntity(locator, name));
          }
  
          public void startCDATA() throws SAXException {
              addEvent(new StartCDATA(locator));
          }
  
          public void startDTD(String name, String publicId, String systemId) 
              throws SAXException {
              addEvent(new StartDTD(locator, name, publicId, systemId));
          }
          
          public void startEntity(String name) throws SAXException {
              addEvent(new StartEntity(locator, name));
          }
      }
  
      /**
       * Adapter that makes this generator usable as a transformer
       * (Note there is a performance penalty for this however: 
       * you effectively recompile the template for every instance document)
       */
  
      public static class TransformerAdapter extends AbstractTransformer {
          static class TemplateConsumer extends Parser implements XMLConsumer {
  
              public TemplateConsumer(SourceResolver resolver, Map objectModel,
                                      String src, Parameters parameters) 
                  throws ProcessingException, SAXException, IOException {
                  this.gen = new JXTemplateGenerator();
                  this.gen.setup(resolver, objectModel, null, parameters);
              }
  
              public void endDocument() throws SAXException {
                  super.endDocument();
                  gen.execute(gen.getConsumer(),
                              gen.getJexlContext(),
                              gen.getJXPathContext(),
                              getStartEvent(), null);
              }
  
              void setConsumer(XMLConsumer consumer) {
                  gen.setConsumer(consumer);
              }
  
              JXTemplateGenerator gen;
          }
  
          TemplateConsumer templateConsumer;
  
          public void recycle() {
              super.recycle();
              templateConsumer = null;
          }
  
          public void setup(SourceResolver resolver, Map objectModel,
                            String src, Parameters parameters)
              throws ProcessingException, SAXException, IOException {
              templateConsumer = new TemplateConsumer(resolver, objectModel,
                                                      src,
                                                      parameters);
          }
  
          public void setConsumer(XMLConsumer xmlConsumer) {
              super.setConsumer(templateConsumer);
              templateConsumer.setConsumer(xmlConsumer);
          }
      }
  
      private XMLConsumer consumer;
      private JXPathContext jxpathContext;
      private MyJexlContext globalJexlContext;
      private Variables variables;
      private static Map cache = new HashMap();
      private Source inputSource;
      private Map definitions;
  
      private JXPathContext getJXPathContext() {
          return jxpathContext;
      }
  
      private MyJexlContext getJexlContext() {
          return globalJexlContext;
      }
  
      private XMLConsumer getConsumer() {
          return consumer;
      }
  
      public void recycle() {
          super.recycle();
          consumer = null;
          jxpathContext = null;
          globalJexlContext = null;
          variables = null;
          inputSource = null;
          definitions = null;
      }
  
      public void setup(SourceResolver resolver, Map objectModel,
                        String src, Parameters parameters)
          throws ProcessingException, SAXException, IOException {
  
          super.setup(resolver, objectModel, src, parameters);
          if (src != null) {
              try {
                  this.inputSource = resolver.resolveURI(src);
              } catch (SourceException se) {
                  throw SourceUtil.handle("Error during resolving of '" + src + "'.", se);
              }
              long lastMod = inputSource.getLastModified();
              String uri = inputSource.getURI();
              synchronized (cache) {
                  StartDocument startEvent = (StartDocument)cache.get(uri);
                  if (startEvent != null &&
                      lastMod > startEvent.compileTime) {
                      cache.remove(uri);
                  }
              }
          }
          Object bean = FlowHelper.getContextObject(objectModel);
          WebContinuation kont = FlowHelper.getWebContinuation(objectModel);
          setContexts(bean, kont,
                      ObjectModelHelper.getRequest(objectModel),
                      ObjectModelHelper.getResponse(objectModel),
                      ObjectModelHelper.getContext(objectModel),
                      parameters,
                      objectModel);
          definitions = new HashMap();
      }
      
      private void fillContext(Object contextObject, Map map) {
          if (contextObject == null) return;
          // Hack: I use jxpath to populate the context object's properties
          // in the jexl context
          final JXPathBeanInfo bi = 
              JXPathIntrospector.getBeanInfo(contextObject.getClass());
          if (bi.isDynamic()) {
              Class cl = bi.getDynamicPropertyHandlerClass();
              try {
                  DynamicPropertyHandler h = (DynamicPropertyHandler) cl.newInstance();
                  String[] result = h.getPropertyNames(contextObject);
                  for (int i = 0; i < result.length; i++) {
                      try {
                          map.put(result[i], h.getProperty(contextObject, result[i]));
                      } catch (Exception exc) {
                          exc.printStackTrace();
                      }
                  }
              } catch (Exception ignored) {
                  ignored.printStackTrace();
              }
          } else {
              PropertyDescriptor[] props =  bi.getPropertyDescriptors();
              for (int i = 0; i < props.length; i++) {
                  try {
                      Method read = props[i].getReadMethod();
                      if (read != null) {
                          map.put(props[i].getName(), 
                                  read.invoke(contextObject, null));
                      }
                  } catch (Exception ignored) {
                      ignored.printStackTrace();
                  }
              }
          }
      }
  
      private void setContexts(Object contextObject,
                               WebContinuation kont,
                               Request request,
                               Response response,
                               org.apache.cocoon.environment.Context app,
                               Parameters parameters,
                               Map objectModel) {
          if (variables == null) {
              variables = new MyVariables(contextObject,
                                          kont,
                                          request,
                                          response,
                                          app,
                                          parameters);
          }
          Map map;
          if (contextObject instanceof Map) {
              map = (Map)contextObject;
          } else {
              map = new HashMap();
              fillContext(contextObject, map);
          }
          jxpathContext = jxpathContextFactory.newContext(null, contextObject);
          jxpathContext.setVariables(variables);
          globalJexlContext = new MyJexlContext();
          globalJexlContext.setVars(map);
          map = globalJexlContext.getVars();
          if (contextObject != null) {
              map.put("flowContext", contextObject);
              map.put("continuation", kont);
              // FIXME (VG): Is this required (what it's used for - examples)?
              // Here I use Rhino's live-connect objects to allow Jexl to call
              // java constructors
              Object javaPkg = JavaScriptFlow.getJavaPackage(objectModel);
              Object pkgs = JavaScriptFlow.getPackages(objectModel);
              map.put("java", javaPkg);
              map.put("Packages", pkgs);
          }
          map.put("request", request);
          map.put("response", response);
          map.put("context", app);
          Object session = request.getSession(false);
          if (session != null) {
              map.put("session", session);
          }
      }
  
      public void setConsumer(XMLConsumer consumer) {
          this.consumer = consumer;
      }
  
      public void generate() 
          throws IOException, SAXException, ProcessingException {
          StartDocument startEvent;
          synchronized (cache) {
              startEvent = (StartDocument)cache.get(inputSource.getURI());
          }
          if (startEvent == null) {
              long compileTime = inputSource.getLastModified();
              Parser parser = new Parser();
              SourceUtil.parse(this.manager, this.inputSource, parser);
              startEvent = parser.getStartEvent();
              startEvent.compileTime = compileTime;
              synchronized (cache) {
                  cache.put(inputSource.getURI(), startEvent);
              }
          }
          execute(consumer,
                  globalJexlContext, jxpathContext, 
                  startEvent, null);
      }
  
      interface CharHandler {
          public void characters(char[] ch, int offset, int length)
              throws SAXException;
      }
  
      private void characters(JexlContext jexlContext,
                              JXPathContext jxpathContext, 
                              TextEvent event,
                              CharHandler handler) throws SAXException {
          Iterator iter = event.substitutions.iterator();
          while (iter.hasNext()) {
              Object subst = iter.next();
              char[] chars;
              if (subst instanceof char[]) {
                  chars = (char[])subst;
              } else {
                  Expression expr = (Expression)subst;
                  try {
                      Object val = getValue(expr, jexlContext, jxpathContext);
                      if (val != null) {
                          chars = val.toString().toCharArray();
                      } else {
                          chars = EMPTY_CHARS;
                      }
                  } catch (Exception e) {
                      throw new SAXParseException(e.getMessage(),
                                                  event.location,
                                                  e);
                  } catch (Error err) {
                      throw new SAXParseException(err.getMessage(),
                                                  event.location,
                                                  null);
                  }
              }
              handler.characters(chars, 0, chars.length);
          }
      }
  
      private void executeRaw(final XMLConsumer consumer,
                              Event startEvent, Event endEvent) 
          throws SAXException {
          Event ev = startEvent;
          while (ev != endEvent) {
              consumer.setDocumentLocator(ev.location);
              if (ev instanceof Characters) {
                  TextEvent text = (TextEvent)ev;
                  consumer.characters(text.raw, 0, text.raw.length);
              } else if (ev instanceof EndDocument) {
                  consumer.endDocument();
              } else if (ev instanceof StartElement) {
                  StartElement startElement = 
                      (StartElement)ev;
                  consumer.startElement(startElement.namespaceURI,
                                        startElement.localName,
                                        startElement.raw,
                                        startElement.attributes);
              } else if (ev instanceof EndElement) {
                  EndElement endElement = (EndElement)ev;
                  StartElement startElement = 
                      (StartElement)endElement.startElement;
                  consumer.endElement(startElement.namespaceURI,
                                      startElement.localName,
                                      startElement.raw);
              } else if (ev instanceof EndPrefixMapping) {
                  EndPrefixMapping endPrefixMapping = 
                      (EndPrefixMapping)ev;
                  consumer.endPrefixMapping(endPrefixMapping.prefix);
              } else if (ev instanceof IgnorableWhitespace) {
                  TextEvent text = (TextEvent)ev;
                  consumer.ignorableWhitespace(text.raw, 0, text.raw.length);
              } else if (ev instanceof ProcessingInstruction) {
                  ProcessingInstruction pi = (ProcessingInstruction)ev;
                  consumer.processingInstruction(pi.target, pi.data);
              } else if (ev instanceof SkippedEntity) {
                  SkippedEntity skippedEntity = (SkippedEntity)ev;
                  consumer.skippedEntity(skippedEntity.name);
              } else if (ev instanceof StartDocument) {
                  StartDocument startDoc = (StartDocument)ev;
                  if (startDoc.endDocument != null) {
                      // if this isn't a document fragment
                      consumer.startDocument();
                  }
              } else if (ev instanceof StartPrefixMapping) {
                  StartPrefixMapping startPrefixMapping = 
                      (StartPrefixMapping)ev;
                  consumer.startPrefixMapping(startPrefixMapping.prefix, 
                                              startPrefixMapping.uri);
              } else if (ev instanceof Comment) {
                  TextEvent text = (TextEvent)ev;
                  consumer.comment(text.raw, 0, text.raw.length);
              } else if (ev instanceof EndCDATA) {
                  consumer.endCDATA();
              } else if (ev instanceof EndDTD) {
                  consumer.endDTD();
              } else if (ev instanceof EndEntity) {
                  consumer.endEntity(((EndEntity)ev).name);
              } else if (ev instanceof StartCDATA) {
                  consumer.startCDATA();
              } else if (ev instanceof StartDTD) {
                  StartDTD startDTD = (StartDTD)ev;
                  consumer.startDTD(startDTD.name,
                                    startDTD.publicId,
                                    startDTD.systemId);
              } else if (ev instanceof StartEntity) {
                  consumer.startEntity(((StartEntity)ev).name);
              } else if (ev instanceof StartInstruction) {
                  StartInstruction startInstruction = (StartInstruction)ev;
                  StartElement startElement = startInstruction.startElement;
                  consumer.startElement(startElement.namespaceURI,
                                        startElement.localName,
                                        startElement.raw,
                                        startElement.attributes);
              } else if (ev instanceof EndInstruction) {
                  EndInstruction endInstruction = (EndInstruction)ev;
                  StartInstruction startInstruction = 
                      endInstruction.startInstruction;
                  StartElement startElement = startInstruction.startElement;
                  consumer.endElement(startElement.namespaceURI,
                                      startElement.localName,
                                      startElement.raw);
              }
              ev = ev.next;
          }
      } 
  
      private void executeDOM(final XMLConsumer consumer,
                              MyJexlContext jexlContext,
                              JXPathContext jxpathContext,
                              Node node) throws SAXException {
          Parser parser = new Parser();
          DOMStreamer streamer = new DOMStreamer(parser);
          streamer.stream(node);
          execute(consumer,
                  jexlContext,
                  jxpathContext,
                  parser.getStartEvent(), null);
      }
  
      private void call(Locator location,
                        String messagePrefix,
                        final XMLConsumer consumer,
                        MyJexlContext jexlContext,
                        JXPathContext jxpathContext,
                        Event startEvent, Event endEvent) 
          throws SAXException {
          try {
              execute(consumer, jexlContext,
                      jxpathContext, startEvent, endEvent);
          } catch (SAXParseException exc) {
              throw new SAXParseException(messagePrefix +": " +exc.getMessage(),
                                          location, 
                                          exc);
          }
      }
  
      private void execute(final XMLConsumer consumer,
                           MyJexlContext jexlContext,
                           JXPathContext jxpathContext,
                           Event startEvent, Event endEvent) 
          throws SAXException {
          Event ev = startEvent;
          while (ev != endEvent) {
              consumer.setDocumentLocator(ev.location);
              if (ev instanceof Characters) {
                  TextEvent text = (TextEvent)ev;
                  Iterator iter = text.substitutions.iterator();
                  while (iter.hasNext()) {
                      Object subst = iter.next();
                      char[] chars;
                      if (subst instanceof char[]) {
                          chars = (char[])subst;
                      } else {
                          Expression expr = (Expression)subst;
                          try {
                              Object val = 
                                  getNode(expr, jexlContext, jxpathContext);
                              if (val instanceof Node) {
                                  executeDOM(consumer,
                                             jexlContext,
                                             jxpathContext,
                                             (Node)val);
                                  continue;
                              } else if (val instanceof NodeList) {
                                  NodeList nodeList = (NodeList)val;
                                  for (int i = 0, len = nodeList.getLength();
                                       i < len; i++) {
                                      Node n = nodeList.item(i);
                                      executeDOM(consumer, jexlContext,
                                                 jxpathContext, n);
                                  }
                                  continue;
                              } else if (val instanceof Node[]) {
                                  Node[] nodeList = (Node[])val;
                                  for (int i = 0, len = nodeList.length;
                                       i < len; i++) {
                                      Node n = nodeList[i];
                                      executeDOM(consumer, jexlContext,
                                                 jxpathContext, n);
                                  }
                                  continue;
                              }
                              if (val != null) {
                                  chars = val.toString().toCharArray();
                              } else {
                                  chars = EMPTY_CHARS;
                              }
                          } catch (Exception e) {
                              throw new SAXParseException(e.getMessage(),
                                                          ev.location,
                                                          e);
                          } catch (Error err) {
                              throw new SAXParseException(err.getMessage(),
                                                          ev.location,
                                                          null);
                          }
                      }
                      consumer.characters(chars, 0, chars.length);
                  }
              } else if (ev instanceof EndDocument) {
                  consumer.endDocument();
              } else if (ev instanceof EndElement) {
                  EndElement endElement = (EndElement)ev;
                  StartElement startElement = 
                      (StartElement)endElement.startElement;
                  consumer.endElement(startElement.namespaceURI,
                                      startElement.localName,
                                      startElement.raw);
              } else if (ev instanceof EndPrefixMapping) {
                  EndPrefixMapping endPrefixMapping = 
                      (EndPrefixMapping)ev;
                  consumer.endPrefixMapping(endPrefixMapping.prefix);
              } else if (ev instanceof IgnorableWhitespace) {
                  TextEvent text = (TextEvent)ev;
                  characters(jexlContext, 
                             jxpathContext, 
                             text, 
                             new CharHandler() {
                                 public void characters(char[] ch, int offset,
                                                        int len) 
                                     throws SAXException {
                                     consumer.ignorableWhitespace(ch, offset, len);
                                 }
                             });
              } else if (ev instanceof ProcessingInstruction) {
                  ProcessingInstruction pi = (ProcessingInstruction)ev;
                  consumer.processingInstruction(pi.target, pi.data);
              } else if (ev instanceof SkippedEntity) {
                  SkippedEntity skippedEntity = (SkippedEntity)ev;
                  consumer.skippedEntity(skippedEntity.name);
              } else if (ev instanceof StartDocument) {
                  StartDocument startDoc = (StartDocument)ev;
                  if (startDoc.endDocument != null) {
                      // if this isn't a document fragment
                      consumer.startDocument();
                  }
              } else if (ev instanceof StartIf) {
                  StartIf startIf = (StartIf)ev;
                  Object val;
                  try {
                      val = getValue(startIf.test, jexlContext, jxpathContext);
                  } catch (Exception e) {
                      throw new SAXParseException(e.getMessage(),
                                                  ev.location,
                                                  e);
                  } catch (Error err) {
                      throw new SAXParseException(err.getMessage(),
                                                  ev.location,
                                                  null);
                  }
                  boolean result = false;
                  if (val instanceof Boolean) {
                      result = ((Boolean)val).booleanValue();
                  } else {
                      result = (val != null);
                  }
                  if (!result) {
                      ev = startIf.endInstruction.next;
                      continue;
                  }
              } else if (ev instanceof StartForEach) {
                  StartForEach startForEach = (StartForEach)ev;
                  final Object items = startForEach.items;
                  Iterator iter = null;
                  boolean xpath = false;
                  int begin, end, step;
                  try {
                      if (items == null) {
                          iter = NULL_ITER;
                      } else {
                          Expression expr = (Expression)items;
                          if (expr.compiledExpression instanceof CompiledExpression) {
                              CompiledExpression compiledExpression = 
                                  (CompiledExpression)expr.compiledExpression;
                              Object val = 
                                  compiledExpression.getPointer(jxpathContext,
                                                                expr.raw).getNode();
                              // Hack: workaround for JXPath bug
                              if (val instanceof NativeArray) {
                                  iter = 
                                      new JSIntrospector.NativeArrayIterator((NativeArray)val);
                              } else {
                                  iter = 
                                      compiledExpression.iteratePointers(jxpathContext);
                                  xpath = true;
                              }
                          } else if (expr.compiledExpression instanceof org.apache.commons.jexl.Expression) {
                              org.apache.commons.jexl.Expression e = 
                                  (org.apache.commons.jexl.Expression)expr.compiledExpression;
                              Object result = e.evaluate(jexlContext);
                              if (result != null) {
                                  iter =
                                      org.apache.commons.jexl.util.Introspector.getUberspect().getIterator(result, new Info(ev.location.getSystemId(),
                                                                                                                            ev.location.getLineNumber(),
                                                                                                                            ev.location.getColumnNumber()));
                                  
                              }
                              if (iter == null) {
                                  iter = EMPTY_ITER;
                              }
                          } else {
                              // literal value
                              iter = new Iterator() {
                                      
                                  Object val = items;
                                  
                                  public boolean hasNext() {
                                      return val != null;
                                  }
                                  
                                  public Object next() {
                                      Object res = val;
                                      val = null;
                                      return res;
                                  }
                                  
                                  public void remove() {
                                  }
                              };
                          }
                      }
                      begin = startForEach.begin == null ? 0 :
                          getIntValue(startForEach.begin, jexlContext, jxpathContext);
                      end = startForEach.end == null ? Integer.MAX_VALUE : 
                          getIntValue(startForEach.end, jexlContext, 
                                      jxpathContext);
                      step = startForEach.step == null ? 1 : 
                          getIntValue(startForEach.step, jexlContext,
                                      jxpathContext);
                  } catch (Exception exc) {
                      throw new SAXParseException(exc.getMessage(),
                                                  ev.location,
                                                  exc);
                  } catch (Error err) {
                      throw new SAXParseException(err.getMessage(),
                                                  ev.location,
                                                  null);
                  }
                  int i;
                  MyJexlContext localJexlContext = 
                      new MyJexlContext(jexlContext);
                  for (i = 0; i < begin && iter.hasNext(); i++) {
                      iter.next();
                  }
                  for (; i <= end && iter.hasNext(); i++) {
                      Object value;
                      JXPathContext localJXPathContext = null;
                      value = iter.next();
                      if (value instanceof Pointer) {
                          Pointer ptr = (Pointer)value;
                          localJXPathContext = 
                              jxpathContext.getRelativeContext(ptr);
                          try {
                              value = ptr.getNode();
                          } catch (Exception exc) {
                              throw new SAXParseException(exc.getMessage(),
                                                          ev.location,
                                                          null);
                          }
                      } else {
                          localJXPathContext =
                              jxpathContextFactory.newContext(null, value);
                      }
                      localJXPathContext.setVariables(variables);
                      if (startForEach.var != null) {
                          localJexlContext.put(startForEach.var, value);
                      }
                      execute(consumer,
                              localJexlContext,
                              localJXPathContext,
                              startForEach.next,
                              startForEach.endInstruction);
                      for (int skip = step-1; 
                           skip > 0 && iter.hasNext(); --skip) {
                          iter.next();
                      }
                  }
                  ev = startForEach.endInstruction.next;
                  continue;
              } else if (ev instanceof StartChoose) {
                  StartChoose startChoose = (StartChoose)ev;
                  StartWhen startWhen = startChoose.firstChoice; 
                  for (;startWhen != null; startWhen = startWhen.nextChoice) {
                      Object val;
                      try {
                          val = getValue(startWhen.test, jexlContext,
                                         jxpathContext);
                      } catch (Exception e) {
                          throw new SAXParseException(e.getMessage(),
                                                      ev.location,
                                                      e);
                      }
                      boolean result = false;
                      if (val instanceof Boolean) {
                          result = ((Boolean)val).booleanValue();
                      } else {
                          result = (val != null);
                      }
                      if (result) {
                          execute(consumer,
                                  jexlContext, jxpathContext,
                                  startWhen.next, startWhen.endInstruction);
                          break;
                      }
                  }
                  if (startWhen == null) {
                      if (startChoose.otherwise != null) {
                          execute(consumer,
                                  jexlContext, jxpathContext,
                                  startChoose.otherwise.next,
                                  startChoose.otherwise.endInstruction);
                      }
                  }
                  ev = startChoose.endInstruction.next;
                  continue;
              } else if (ev instanceof StartSet) {
                  StartSet startSet = (StartSet)ev;
                  Object value = null;
                  if (startSet.value != null) {
                      try {
                          value = getNode(startSet.value,
                                          jexlContext, jxpathContext);
                      } catch (Exception exc) {
                          throw new SAXParseException(exc.getMessage(),
                                                      ev.location,
                                                      exc);
                      }
                  } else {
                      DOMBuilder builder = new DOMBuilder();
                      builder.startDocument();
                      builder.startElement(NS,
                                           "set",
                                           "set",
                                           EMPTY_ATTRS);
                      execute(builder, jexlContext, jxpathContext,
                              startSet.next, 
                              startSet.endInstruction);
                      builder.endElement(NS,
                                         "set",
                                         "set");
                      builder.endDocument();
                      Node node = builder.getDocument().getDocumentElement();
                      NodeList nodeList = node.getChildNodes();
                      int len = nodeList.getLength();
                      // JXPath doesn't handle NodeList, so convert
                      // it to an array
                      Node[] nodeArr = new Node[len];
                      for (int i = 0; i < len; i++) {
                          nodeArr[i] = nodeList.item(i);
                      }
                      value = nodeArr;
                  }
                  jxpathContext.getVariables().declareVariable(startSet.var, 
                                                               value);
                  jexlContext.put(startSet.var, value);
                  ev = startSet.endInstruction.next;
                  continue;
              } else if (ev instanceof StartElement) {
                  StartElement startElement = (StartElement)ev;
                  StartDefine def = 
                      (StartDefine)definitions.get(startElement.qname);
                  if (def != null) {
                      Map attributeMap = new HashMap();
                      Iterator i = startElement.attributeEvents.iterator();
                      while (i.hasNext()) {
                          String attributeName;
                          Object attributeValue;
                          AttributeEvent attrEvent = (AttributeEvent)
                              i.next();
                          attributeName = attrEvent.localName;
                          if (attrEvent instanceof CopyAttribute) {
                              CopyAttribute copy =
                                  (CopyAttribute)attrEvent;
                              attributeValue = copy.value;
                          } else if (attrEvent instanceof 
                                     SubstituteAttribute) {
                              SubstituteAttribute substEvent =
                                  (SubstituteAttribute)attrEvent;
                              if (substEvent.substitutions.size() == 1 &&
                                  substEvent.substitutions.get(0) instanceof 
                                  Expression) {
                                  Expression expr = (Expression)
                                      substEvent.substitutions.get(0);
                                  Object val;
                                  try {
                                      val = 
                                          getNode(expr,
                                                  jexlContext,
                                                  jxpathContext);
                                  } catch (Exception e) {
                                      throw new SAXParseException(e.getMessage(),
                                                                  ev.location,
                                                                  e);
                                  }
                                  if (val == null) {
                                      val = "";
                                  }
                                  attributeValue = val;
                              } else {
                                  StringBuffer buf = new StringBuffer();
                                  Iterator ii = substEvent.substitutions.iterator();
                                  while (ii.hasNext()) {
                                      Subst subst = (Subst)ii.next();
                                      if (subst instanceof Literal) {
                                          Literal lit = (Literal)subst;
                                          buf.append(lit.value);
                                      } else if (subst instanceof Expression) {
                                          Expression expr = (Expression)subst;
                                          Object val;
                                          try {
                                              val = 
                                                  getValue(expr,
                                                           jexlContext,
                                                           jxpathContext);
                                          } catch (Exception e) {
                                              throw new SAXParseException(e.getMessage(),
                                                                          ev.location,
                                                                          e);
                                          }
                                          if (val == null) {
                                              val = "";
                                          }
                                          buf.append(val.toString());
                                      }
                                  }
                                  attributeValue = buf.toString();
                              }
                          } else {
                              throw new Error("this shouldn't have happened");
                          }
                          attributeMap.put(attributeName, attributeValue);
                      }
                      DOMBuilder builder = new DOMBuilder();
                      builder.startDocument();
                      builder.startElement(startElement.namespaceURI,
                                           startElement.localName,
                                           startElement.raw,
                                           EMPTY_ATTRS);
                      executeRaw(builder, 
                                 startElement.next, 
                                 startElement.endElement);
                      builder.endElement(startElement.namespaceURI,
                                         startElement.localName,
                                         startElement.raw);
                      builder.endDocument();
                      Node node = builder.getDocument().getDocumentElement();
                      MyVariables vars = 
                          (MyVariables)jxpathContext.getVariables();
                      Map saveLocals = vars.localVariables;
                      vars.localVariables = new HashMap();
                      MyJexlContext localJexlContext = 
                          new MyJexlContext(globalJexlContext);
                      // JXPath doesn't handle NodeList, so convert it to
                      // an array
                      NodeList children = node.getChildNodes();
                      int len = children.getLength();
                      Node[] arr = new Node[len];
                      for (int ii = 0; ii < len; ii++) {
                          arr[ii] = children.item(ii);
                      }
                      localJexlContext.put("content", arr);
                      vars.localVariables.put("content", arr);
                      Iterator iter = def.parameters.entrySet().iterator();
                      while (iter.hasNext()) {
                          Map.Entry e = (Map.Entry)iter.next();
                          String key = (String)e.getKey();
                          StartParameter startParam = 
                              (StartParameter)e.getValue();
                          Object default_ = startParam.default_;
                          Object val = attributeMap.get(key);
                          if (val == null) {
                              val = default_;
                          }
                          localJexlContext.put(key, val);
                          vars.localVariables.put(key, val);
                      }
                      JXPathContext localJXPathContext =
                          jxpathContextFactory.newContext(null, arr);
                      localJXPathContext.setVariables(vars);
                      call(ev.location,
                           startElement.localName,
                           consumer, 
                           localJexlContext, localJXPathContext,
                           def.body, def.endInstruction);
                      vars.localVariables = saveLocals;
                      ev = startElement.endElement.next;
                      continue;
                  }
                  Iterator i = startElement.attributeEvents.iterator();
                  AttributesImpl attrs = new AttributesImpl();
                  while (i.hasNext()) {
                      AttributeEvent attrEvent = (AttributeEvent)
                          i.next();
                      if (attrEvent instanceof CopyAttribute) {
                          CopyAttribute copy =
                              (CopyAttribute)attrEvent;
                          attrs.addAttribute(copy.namespaceURI,
                                             copy.localName,
                                             copy.raw,
                                             copy.type,
                                             copy.value);
                      } else if (attrEvent instanceof 
                                 SubstituteAttribute) {
                          StringBuffer buf = new StringBuffer();
                          SubstituteAttribute substEvent =
                              (SubstituteAttribute)attrEvent;
                          Iterator ii = substEvent.substitutions.iterator();
                          while (ii.hasNext()) {
                              Subst subst = (Subst)ii.next();
                              if (subst instanceof Literal) {
                                  Literal lit = (Literal)subst;
                                  buf.append(lit.value);
                              } else if (subst instanceof Expression) {
                                  Expression expr = (Expression)subst;
                                  Object val;
                                  try {
                                      val = 
                                          getValue(expr,
                                                   jexlContext,
                                                   jxpathContext);
                                  } catch (Exception e) {
                                      throw new SAXParseException(e.getMessage(),
                                                                  ev.location,
                                                                  e);
                                  }
                                  if (val == null) {
                                      val = "";
                                  }
                                  buf.append(val.toString());
                              }
                          }
                          attrs.addAttribute(attrEvent.namespaceURI,
                                             attrEvent.localName,
                                             attrEvent.raw,
                                             attrEvent.type,
                                             buf.toString());
                      }
                  }
                  consumer.startElement(startElement.namespaceURI,
                                        startElement.localName,
                                        startElement.raw,
                                        attrs); 
                  
              } else if (ev instanceof StartPrefixMapping) {
                  StartPrefixMapping startPrefixMapping = 
                      (StartPrefixMapping)ev;
                  consumer.startPrefixMapping(startPrefixMapping.prefix, 
                                              startPrefixMapping.uri);
              } else if (ev instanceof Comment) {
                  TextEvent text = (TextEvent)ev;
                  final StringBuffer buf = new StringBuffer();
                  characters(jexlContext, 
                             jxpathContext, 
                             text, 
                             new CharHandler() {
                                 public void characters(char[] ch, int offset,
                                                        int len) 
                                     throws SAXException {
                                     buf.append(ch, offset, len);
                                 }
                             });
                  char[] chars = new char[buf.length()];
                  buf.getChars(0, chars.length, chars, 0);
                  consumer.comment(chars, 0, chars.length);
              } else if (ev instanceof EndCDATA) {
                  consumer.endCDATA();
              } else if (ev instanceof EndDTD) {
                  consumer.endDTD();
              } else if (ev instanceof EndEntity) {
                  consumer.endEntity(((EndEntity)ev).name);
              } else if (ev instanceof StartCDATA) {
                  consumer.startCDATA();
              } else if (ev instanceof StartDTD) {
                  StartDTD startDTD = (StartDTD)ev;
                  consumer.startDTD(startDTD.name,
                                    startDTD.publicId,
                                    startDTD.systemId);
              } else if (ev instanceof StartEntity) {
                  consumer.startEntity(((StartEntity)ev).name);
              } else if (ev instanceof StartOut) {
                  StartOut startOut = (StartOut)ev;
                  Object val;
                  try {
                      val = getNode(startOut.compiledExpression,
                                    jexlContext,
                                    jxpathContext);
                      if (val instanceof Node) {
                          executeDOM(consumer,
                                     jexlContext,
                                     jxpathContext,
                                     (Node)val);
                      } else if (val instanceof NodeList) {
                          NodeList nodeList = (NodeList)val;
                          for (int i = 0, len = nodeList.getLength();
                               i < len; i++) {
                              Node n = nodeList.item(i);
                              executeDOM(consumer, jexlContext,
                                         jxpathContext, n);
                          }
                      } else if (val instanceof Node[]) {
                          Node[] nodeList = (Node[])val;
                          for (int i = 0, len = nodeList.length;
                               i < len; i++) {
                              Node n = nodeList[i];
                              executeDOM(consumer, jexlContext,
                                         jxpathContext, n);
                          }
                      } else {
                          if (val == null) {
                              val = "";
                          }
                          char[] ch = val.toString().toCharArray();
                          consumer.characters(ch, 0, ch.length);
                      }
                  } catch (Exception e) {
                      throw new SAXParseException(e.getMessage(),
                                                  ev.location,
                                                  e);
                  }
              } else if (ev instanceof StartTemplate) {
              } else if (ev instanceof StartDefine) {
                  StartDefine startDefine = (StartDefine)ev;
                  definitions.put(startDefine.qname, startDefine);
                  ev = startDefine.endInstruction.next;
                  continue;
              } else if (ev instanceof StartImport) {
                  StartImport startImport = (StartImport)ev;
                  String uri;
                  AttributeEvent e = startImport.uri;
                  if (e instanceof CopyAttribute) {
                      CopyAttribute copy = (CopyAttribute)e;
                      uri = copy.value;
                  } else {
                      StringBuffer buf = new StringBuffer();
                      SubstituteAttribute substAttr = (SubstituteAttribute)e;
                      Iterator i = substAttr.substitutions.iterator();
                      while (i.hasNext()) {
                          Subst subst = (Subst)i.next();
                          if (subst instanceof Literal) {
                              Literal lit = (Literal)subst;
                              buf.append(lit.value);
                          } else if (subst instanceof Expression) {
                              Expression expr = (Expression)subst;
                              Object val;
                              try {
                                  val = 
                                      getValue(expr,
                                               jexlContext,
                                               jxpathContext);
                              } catch (Exception exc) {
                                  throw new SAXParseException(exc.getMessage(),
                                                              ev.location,
                                                              exc);
                              } catch (Error err) {
                                  throw new SAXParseException(err.getMessage(),
                                                              ev.location,
                                                              null);
                              }
                              if (val == null) {
                                  val = "";
                              }
                              buf.append(val.toString());
                          }
                      }
                      uri = buf.toString();
                      
                  }
                  Source input;
                  try {
                      input = resolver.resolveURI(uri);
                  } catch (Exception exc) {
                      throw new SAXParseException(exc.getMessage(),
                                                  ev.location,
                                                  exc);
                  }
                  long lastMod = input.getLastModified();
                  StartDocument doc;
                  synchronized (cache) {
                      doc = (StartDocument)cache.get(input.getURI());
                      if (doc != null) {
                          if (doc.compileTime < lastMod) {
                              doc = null; // recompile
                          }
                      }
                  }
                  if (doc == null) {
                      try {
                          Parser parser = new Parser();
                          SourceUtil.parse(this.manager, input, parser);
                          doc = parser.getStartEvent();
                          doc.compileTime = lastMod;
                      } catch (Exception exc) {
                          throw new SAXParseException(exc.getMessage(),
                                                      ev.location,
                                                      exc);
                      }
                      synchronized (cache) {
                          cache.put(input.getURI(), doc);
                      }
                  }
                  JXPathContext selectJXPath = jxpathContext;
                  MyJexlContext selectJexl = jexlContext;
                  if (startImport.select != null) {
                      try {
                          Object obj = getValue(startImport.select,
                                                jexlContext,
                                                jxpathContext);
                          selectJXPath = 
                              jxpathContextFactory.newContext(null, obj);
                          selectJXPath.setVariables(variables);
                          selectJexl = new MyJexlContext(globalJexlContext);
                          fillContext(obj, selectJexl);
                      } catch (Exception exc) {
                          throw new SAXParseException(exc.getMessage(),
                                                      ev.location,
                                                      exc);
                      } catch (Error err) {
                          throw new SAXParseException(err.getMessage(),
                                                      ev.location,
                                                      null);
                      }
                  }
                  execute(consumer, selectJexl, selectJXPath, doc.next, 
                          doc.endDocument);
                  ev = startImport.endInstruction.next;
                  continue;
              }
              ev = ev.next;
          }
      }
  }
  
  
  
  
  1.1                  cocoon-2.1/src/java/org/apache/cocoon/transformation/JXTemplateTransformer.java
  
  Index: JXTemplateTransformer.java
  ===================================================================
  package org.apache.cocoon.transformation;
  
  import org.apache.cocoon.generation.JXTemplateGenerator;
  
  /**
   * Transformer adapter for {@link org.apache.cocoon.generation.JXTemplateGenerator}
   */
  
  public class JXTemplateTransformer extends JXTemplateGenerator.TransformerAdapter {
  
      /**
       * This class is just a placeholder to provide a class name you
       * can easily reference from your Sitemap. All of its functionality is 
       * provided by its parent class.
       */
  }
  
  
  
  1.3       +1 -1      cocoon-2.1/src/scratchpad/webapp/samples/garbage/calc/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/scratchpad/webapp/samples/garbage/calc/sitemap.xmap,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- sitemap.xmap	6 Jul 2003 23:27:25 -0000	1.2
  +++ sitemap.xmap	8 Jul 2003 05:48:53 -0000	1.3
  @@ -9,7 +9,7 @@
     </map:components>
   
     <!-- indicates what flowscript to attach to this sitemap -->
  -  <map:flow language="FOM_JavaScript">
  +  <map:flow language="javascript">
       <map:script src="calc.js"/>
     </map:flow>
   
  
  
  
  1.4       +2 -2      cocoon-2.1/src/scratchpad/webapp/samples/jxforms/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/scratchpad/webapp/samples/jxforms/sitemap.xmap,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- sitemap.xmap	6 Jul 2003 23:27:25 -0000	1.3
  +++ sitemap.xmap	8 Jul 2003 05:48:53 -0000	1.4
  @@ -6,7 +6,7 @@
       <map:generators default="file">
         <map:generator name="jxforms" src="org.apache.cocoon.generation.JXFormsGenerator" logger="jxforms.sitemap.generator"/>
       </map:generators>
  -    <map:flow-interpreters default="FOM_JavaScript"/>
  +    <map:flow-interpreters default="javascript"/>
       <map:serializers default="html"/>
       <map:matchers default="wildcard"/>
     </map:components>
  @@ -38,7 +38,7 @@
     </map:resources>
   
     <!-- =========================== Pipelines ================================= -->
  -  <map:flow language="FOM_JavaScript">
  +  <map:flow language="javascript">
        <map:script src="flow/feedbackWizard.js"/>
     </map:flow>
   
  
  
  
  1.20      +2 -2      cocoon-2.1/src/scratchpad/webapp/samples/petstore/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/scratchpad/webapp/samples/petstore/sitemap.xmap,v
  retrieving revision 1.19
  retrieving revision 1.20
  diff -u -r1.19 -r1.20
  --- sitemap.xmap	6 Jul 2003 23:27:25 -0000	1.19
  +++ sitemap.xmap	8 Jul 2003 05:48:53 -0000	1.20
  @@ -12,7 +12,7 @@
           <map:generator label="content,data" logger="sitemap.generator.jx" name="jx" src="org.apache.cocoon.generation.JXTemplateGenerator"/>
         <map:generator name="jxforms" src="org.apache.cocoon.generation.JXFormsGenerator" logger="sitemap.generator.jxforms"/>
       </map:generators>
  -    <map:flow-interpreters default="FOM_JavaScript"/>
  +    <map:flow-interpreters default="javascript"/>
       <map:serializers default="html"/>
       <map:matchers default="wildcard"/>
     </map:components>
  @@ -50,7 +50,7 @@
     </map:resources>
   
     <!-- =========================== Pipelines ================================= -->
  -    <map:flow language="FOM_JavaScript">
  +    <map:flow language="javascript">
         <map:script src="flow/PetStoreImpl.js"/>
         <map:script src="flow/petstore.js"/>
       </map:flow>
  
  
  
  1.22      +9 -1      cocoon-2.1/src/webapp/WEB-INF/cocoon.xconf
  
  Index: cocoon.xconf
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/WEB-INF/cocoon.xconf,v
  retrieving revision 1.21
  retrieving revision 1.22
  diff -u -r1.21 -r1.22
  --- cocoon.xconf	21 Jun 2003 13:49:14 -0000	1.21
  +++ cocoon.xconf	8 Jul 2003 05:48:53 -0000	1.22
  @@ -79,7 +79,15 @@
         |       whether support for the JavaScript debugger should be enabled
         |       in the control flow.
         +-->
  -  <flow-interpreters default="JavaScript" logger="flow">
  +  <flow-interpreters default="javascript" logger="flow">
  +    <!-- FOM (Flow Object Model) -->
  +    <component-instance name="javascript" class="org.apache.cocoon.components.flow.javascript.fom.FOM_JavaScriptInterpreter">
  +      <load-on-startup>resource://org/apache/cocoon/components/flow/javascript/fom/fom_system.js</load-on-startup>
  +      <reload-scripts>true</reload-scripts>
  +      <check-time>4000</check-time>
  +      <!--  <debugger>enabled</debugger> -->  <!-- JavaScript Debugger support -->
  +    </component-instance>
  +    <!-- (Deprecated) Original -->
       <component-instance name="JavaScript" class="org.apache.cocoon.components.flow.javascript.JavaScriptInterpreter">
         <load-on-startup>resource://org/apache/cocoon/components/flow/javascript/system.js</load-on-startup>
         <reload-scripts>true</reload-scripts>