/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/jsp/PageContextImpl.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.jsp;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.el.ELContext;
import javax.el.ELContextEvent;
import javax.el.ELContextListener;
import javax.el.ELResolver;
import javax.el.ValueExpression;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.ErrorData;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.SkipPageException;
import javax.servlet.jsp.el.ExpressionEvaluator;
import javax.servlet.jsp.el.VariableResolver;
import javax.servlet.jsp.jstl.core.Config;
import javax.servlet.jsp.jstl.fmt.LocalizationContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.JspFragment;

import org.w3c.dom.Node;

import com.caucho.el.ExprEnv;
import com.caucho.jsp.cfg.JspPropertyGroup;
import com.caucho.jsp.el.ExpressionEvaluatorImpl;
import com.caucho.jsp.el.ImplicitVariableMapper;
import com.caucho.jsp.el.JspApplicationContextImpl;
import com.caucho.jsp.el.PageContextAttributeMap;
import com.caucho.jsp.el.PageContextELResolver;
import com.caucho.jsp.el.ServletELContext;
import com.caucho.jstl.JstlPageContext;
import com.caucho.server.http.AbstractResponseStream;
import com.caucho.server.http.CauchoRequest;
import com.caucho.server.http.CauchoResponse;
import com.caucho.server.http.RequestAdapter;
import com.caucho.server.http.ToCharResponseAdapter;
import com.caucho.server.webapp.RequestDispatcherImpl;
import com.caucho.server.webapp.WebApp;
import com.caucho.util.CharBuffer;
import com.caucho.util.DisplayableException;
import com.caucho.util.HashMapImpl;
import com.caucho.util.L10N;
import com.caucho.util.NullEnumeration;
import com.caucho.vfs.ClientDisconnectException;
import com.caucho.vfs.FlushBuffer;
import com.caucho.vfs.Path;
import com.caucho.vfs.TempCharBuffer;
import com.caucho.xpath.VarEnv;

public class PageContextImpl extends PageContext
  implements ExprEnv, JstlPageContext, VariableResolver {
  private static final Logger log
    = Logger.getLogger(PageContextImpl.class.getName());
  private static final L10N L = new L10N(PageContextImpl.class);

  private WebApp _webApp;

  private JspWriterAdapter _jspAdapter = new JspWriterAdapter();
  private JspServletOutputStream _jspOutputStream
    = new JspServletOutputStream(this);

  private Map<String,Object> _attributes;
  private Servlet _servlet;
  private HttpServletRequest _request;

  private ServletResponse _servletResponse;
  private CauchoResponse _response;
  private ToCharResponseAdapter _responseAdapter;

  private HttpSession _session;
  private JspWriter _topOut;
  private JspWriter _out;
  private String _errorPage;
  protected boolean _isFilled;

  private AbstractResponseStream _responseStream;

  private BodyResponseStream _bodyResponseStream;

  private int _bufferSize = 8192;
  private BodyContentImpl _bodyOut;

  private Locale _locale;
  private BundleManager _bundleManager;

  private VarEnv _varEnv;
  private Node _nodeEnv;

  private final CharBuffer _cb = new CharBuffer();

  private PageELContext _elContextValue;
  private PageELContext _elContext;

  private ELResolver _elResolver;
  private javax.el.FunctionMapper _functionMapper;
  private PageVariableMapper _variableMapper;
  private HashMap<String,Method> _functionMap;

  private ExpressionEvaluatorImpl _expressionEvaluator;

  private ELContextListener[] _elContextListeners;

  PageContextImpl()
  {
    _attributes = new HashMapImpl<String,Object>();

    _bodyResponseStream = new BodyResponseStream();
    _bodyResponseStream.start();
  }

  public PageContextImpl(WebApp webApp, Servlet servlet)
  {
    this();

    if (webApp == null)
      throw new NullPointerException();

    _webApp = webApp;

    _servlet = servlet;

    if (servlet instanceof Page) {
      Page page = (Page) servlet;

      _functionMap = page._caucho_getFunctionMap();
    }
    else
      _functionMap = null;

  }

  public PageContextImpl(WebApp webApp, HashMap<String,Method> functionMap)
  {
    _webApp = webApp;

    _functionMap = functionMap;
  }

  @Override
  public void initialize(Servlet servlet,
                         ServletRequest request,
                         ServletResponse response,
                         String errorPage,
                         boolean needsSession,
                         int bufferSize,
                         boolean autoFlush)
  {
    HttpSession session = null;

    if (needsSession)
      session = ((HttpServletRequest) request).getSession(true);

    WebApp app = (WebApp) request.getServletContext();

    _webApp = app;

    initialize(servlet, app, request, response,
               errorPage, session, bufferSize, autoFlush,
               false);
  }

  public void initialize(Servlet servlet,
                         WebApp app,
                         ServletRequest request,
                         ServletResponse response,
                         String errorPage,
                         HttpSession session,
                         int bufferSize,
                         boolean autoFlush,
                         boolean isPrintNullAsBlank)
  {
    _webApp = app;

    _servlet = servlet;
    _request = (HttpServletRequest) request;
    _servletResponse = response;

    if (response instanceof CauchoResponse
        && bufferSize <= TempCharBuffer.SIZE) {
      _response = (CauchoResponse) response;
      _responseAdapter = null;
    }
    else {
      // JSP.12.2.3 - JSP must use PrintWriter
      _responseAdapter = ToCharResponseAdapter.create((HttpServletResponse) response);
      _response = _responseAdapter;

      try {
        // jsp/017m
        response.setBufferSize(bufferSize);
      } catch (Exception e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    _responseStream = _response.getResponseStream();
    _topOut = _jspAdapter;
    _responseStream.setAutoFlush(autoFlush);
    _jspAdapter.init(null, _response, _responseStream);
    _jspAdapter.setPrintNullAsBlank(isPrintNullAsBlank);

    if (bufferSize != TempCharBuffer.SIZE) {
      try {
        _responseStream.setBufferSize(bufferSize);
      } catch (Exception e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    // needed for includes from static pages

    _bufferSize = bufferSize;
    _session = session;

    _out = _topOut;

    _errorPage = errorPage;
    _locale = null;

    // jsp/1059, jsp/3147
    // XXX: recycling is important for performance reasons
    _elContext = null;
    if (_elContextValue != null)
      _elContextValue.clear();

    //if (_attributes.size() > 0)
    //  _attributes.clear();
    _isFilled = false;
    _nodeEnv = null;

    if (servlet instanceof Page) {
      Page page = (Page) servlet;

      _functionMap = page._caucho_getFunctionMap();
    }
    else
      _functionMap = null;
  }

  protected void init()
  {
    // XXX: important for performance reasons
    // jsp/1059, jsp/3147
    _elContext = null;
    if (_elContextValue != null)
      _elContextValue.clear();
  }

  protected void setOut(JspWriter out)
  {
    _out = out;
  }

  protected void clearAttributes()
  {
    _attributes.clear();
  }

  /**
   * Returns the page attribute with the given name.
   *
   * @param name the attribute name.
   *
   * @return the attribute's value.
   */
  public Object getAttribute(String name)
  {
    if (name == null)
      throw new NullPointerException(L.l("getAttribute must have a non-null name"));

    Object value = _attributes.get(name);
    if (value != null)
      return value;
    else if (! _isFilled) {
      fillAttribute();
      value = _attributes.get(name);
    }

    if (value != null) {
    }
    else if (name.equals(OUT)) {
      // jsp/162d
      return _out;
    }

    return value;
  }

  /**
   * Sets the page attribute with the given name.
   *
   * @param name the attribute name.
   * @param attribute the new value
   */
  public void setAttribute(String name, Object attribute)
  {
    if (name == null)
      throw new NullPointerException(L.l("setAttribute must have a non-null name"));

    if (attribute != null)
      _attributes.put(name, attribute);
    else
      _attributes.remove(name);
  }

  /**
   * Sets the page attribute with the given name.
   *
   * @param name the attribute name.
   * @param attribute the new value
   */
  public Object putAttribute(String name, Object attribute)
  {
    if (name == null)
      throw new NullPointerException(L.l("putAttribute must have a non-null name"));

    if (attribute != null)
      return _attributes.put(name, attribute);
    else
      return _attributes.remove(name);
  }

  /**
   * Removes a named attribute from the page context.
   *
   * @param name the name of the attribute to remove
   */
  public void removeAttribute(String name)
  {
    if (name == null)
      throw new NullPointerException(L.l("removeAttribute must have a non-null name"));

    _attributes.remove(name);
    // jsp/162b
    if (_request != null)
      _request.removeAttribute(name);

    if (_session != null) {
      try {
        _session.removeAttribute(name);
      } catch (IllegalStateException e) {
        // jsp/162f
        log.log(Level.FINE, e.toString(), e);
      }
    }

    if (_webApp != null)
      _webApp.removeAttribute(name);
  }

  private Enumeration<String> getAttributeNames()
  {
    if (! _isFilled)
      fillAttribute();

    return Collections.enumeration(_attributes.keySet());
  }

  /**
   * Fills the predefined page content _attributes with their values.
   */
  protected void fillAttribute()
  {
    _isFilled = true;
    _attributes.put(PAGE, _servlet);
    _attributes.put(PAGECONTEXT, this);
    _attributes.put(REQUEST, getCauchoRequest());
    _attributes.put(RESPONSE, getCauchoResponse());
    if (_servlet != null)
      _attributes.put(CONFIG, _servlet.getServletConfig());
    if (getSession() != null)
      _attributes.put(SESSION, getSession());
    _attributes.put(APPLICATION, getApplication());
  }

  public Object getAttribute(String name, int scope)
  {
    switch (scope) {
    case PAGE_SCOPE:
      return getAttribute(name);
    case REQUEST_SCOPE:
      return getCauchoRequest().getAttribute(name);
    case SESSION_SCOPE:
      {
        HttpSession session = getSession();
        return session != null ? session.getAttribute(name) : null;
      }
    case APPLICATION_SCOPE:
      return getApplication().getAttribute(name);

    default:
      throw new IllegalArgumentException();
    }
  }

  @Override
  public void setAttribute(String name, Object value, int scope)
  {
    switch (scope) {
    case PAGE_SCOPE:
      setAttribute(name, value);
      break;

    case REQUEST_SCOPE:
      getCauchoRequest().setAttribute(name, value);
      break;

    case SESSION_SCOPE:
      if (getSession() != null)
        getSession().setAttribute(name, value);
      break;

    case APPLICATION_SCOPE:
      getApplication().setAttribute(name, value);
      break;

    default:
      throw new IllegalArgumentException();
    }
  }

  @Override
  public void removeAttribute(String name, int scope)
  {
    if (name == null)
      throw new NullPointerException(L.l("removeAttribute must have a non-null name"));

    switch (scope) {
    case PAGE_SCOPE:
      if (name != null)
        _attributes.remove(name);
      break;

    case REQUEST_SCOPE:
      getCauchoRequest().removeAttribute(name);
      break;

    case SESSION_SCOPE:
      if (getSession() != null)
        getSession().removeAttribute(name);
      break;

    case APPLICATION_SCOPE:
      getApplication().removeAttribute(name);
      break;

    default:
      throw new IllegalArgumentException();
    }
  }

  public Enumeration<String> getAttributeNames(int scope)
  {
    switch (scope) {
    case PAGE_SCOPE:
      return getAttributeNames();

    case REQUEST_SCOPE:
      return getCauchoRequest().getAttributeNames();

    case SESSION_SCOPE:
      if (getSession() != null) {
        return getSession().getAttributeNames();
      }
      else
        return NullEnumeration.create();

    case APPLICATION_SCOPE:
      return getApplication().getAttributeNames();

    default:
      throw new IllegalArgumentException();
    }
  }

  public Enumeration<String> getAttributeNamesInScope(int scope)
  {
    return getAttributeNames(scope);
  }

  /**
   * Finds an attribute in any of the scopes from page to webApp.
   *
   * @param name the attribute name.
   *
   * @return the attribute value
   */
  @Override
  public Object findAttribute(String name)
  {
    Object value;

    if ((value = getAttribute(name)) != null)
      return value;

    HttpServletRequest req = getCauchoRequest();

    if (req != null &&
        (value = getCauchoRequest().getAttribute(name)) != null)
      return value;

    HttpSession session = getSession();
    if (session != null) {
      try {
        value = session.getAttribute(name);
      } catch (IllegalStateException e) {
        // jsp/162e
        log.log(Level.FINE, e.toString(), e);
      }

      if (value != null)
        return value;
    }

    return getServletContext().getAttribute(name);
  }

  /**
   * Return the scope of the named attribute.
   *
   * @param name the name of the attribute.
   *
   * @return the scope of the attribute
   */
  public int getAttributesScope(String name)
  {
    if (getAttribute(name) != null)
      return PAGE_SCOPE;

    if (getCauchoRequest().getAttribute(name) != null)
      return REQUEST_SCOPE;

    HttpSession session = getSession();
    if (session != null && session.getAttribute(name) != null)
      return SESSION_SCOPE;

    if (getApplication().getAttribute(name) != null)
      return APPLICATION_SCOPE;

    return 0;
  }

  /**
   * Sets the attribute map.
   */
  public Map<String,Object> setMap(Map<String,Object> map)
  {
    Map<String,Object> oldMap = _attributes;
    _attributes = map;
    return oldMap;
  }

  /**
   * Returns the current writer.
   */
  public JspWriter getOut()
  {
    return _out;
  }

  /**
   * Pushes a new BodyContent onto the JspWriter stack.
   */
  public BodyContent pushBody()
  {
    BodyContentImpl body;
    if (_bodyOut != null) {
      body = _bodyOut;
      _bodyOut = null;
    }
    else
      body = BodyContentImpl.allocate();

    CauchoResponse response = getCauchoResponse();

    body.init(_out);

    _out = body;

    response.setForbidForward(true);
    try {
      _bodyResponseStream.flushBuffer();
    } catch (IOException e) {
    }
    _bodyResponseStream.start();
    _bodyResponseStream.setWriter(body);
    _bodyResponseStream.setEncoding(response.getCharacterEncoding());
    response.setResponseStream(_bodyResponseStream);

    return body;
  }

  /**
   * Pushes a new writer onto the JspWriter stack.
   */
  public JspWriter pushBody(Writer writer)
  {
    if (writer == _out)
      return null;

    JspWriter oldWriter = _out;

    StreamJspWriter jspWriter;

    jspWriter = new StreamJspWriter();
    jspWriter.init(_out, writer);

    _out = jspWriter;

    getCauchoResponse().setForbidForward(true);

    _bodyResponseStream.setWriter(writer);
    getCauchoResponse().setResponseStream(_bodyResponseStream);

    return oldWriter;
  }

  /**
   * Pops the BodyContent from the JspWriter stack.
   *
   * @return the enclosing writer
   */
  @Override
  public JspWriter popBody()
  {
    BodyContentImpl bodyOut = (BodyContentImpl) _out;
    _out = bodyOut.getEnclosingWriter();

    try {
      _bodyResponseStream.flushBuffer();
      //if (_writeStream != null)
      // _writeStream.flushBuffer();
    } catch (IOException e) {
      log.log(Level.WARNING, e.toString(), e);
    }

    if (_out instanceof StreamJspWriter) {
      StreamJspWriter writer = (StreamJspWriter) _out;

      _bodyResponseStream.setWriter(writer.getWriter());

      if (_response != null)
        _bodyResponseStream.setEncoding(_response.getCharacterEncoding());
    }
    else if (_out instanceof JspWriterAdapter) {
      if (getCauchoResponse() != null) {
        getCauchoResponse().setResponseStream(_responseStream);
        getCauchoResponse().setForbidForward(false);
      }
    }
    else if (_out instanceof BodyContentImpl) {
      BodyContentImpl body = (BodyContentImpl) _out;

      _bodyResponseStream.setWriter(body.getWriter());

      if (_response != null)
        _bodyResponseStream.setEncoding(_response.getCharacterEncoding());
    }

    return _out;
  }

  /**
   * Pops the BodyContent from the JspWriter stack.
   *
   * @return the enclosing writer
   */
  public JspWriter popAndReleaseBody()
    throws IOException
  {
    BodyContentImpl body = (BodyContentImpl) getOut();

    JspWriter out = popBody();

    releaseBody(body);

    return out;
  }

  public void releaseBody(BodyContentImpl out)
    throws IOException
  {
    if (_bodyOut == null) {
      out.releaseNoFree();
      _bodyOut = out;
    }
    else
      out.release();
  }

  /**
   * Pops the BodyContent from the JspWriter stack.
   *
   * @param oldWriter the old writer
   */
  public JspWriter setWriter(JspWriter oldWriter)
  {
    if (_out == oldWriter)
      return oldWriter;

    /*
    if (_out instanceof FlushBuffer) {
      try {
        ((FlushBuffer) _out).flushBuffer();
      } catch (IOException e) {
      }
    }
    */
    try {
      if (_out instanceof FlushBuffer)
        ((FlushBuffer) _out).flushBuffer();
    } catch (IOException e) {
    }

    _out = oldWriter;

    // jsp/18eg
    if (_out instanceof StreamJspWriter) {
      StreamJspWriter writer = (StreamJspWriter) _out;

      _bodyResponseStream.setWriter(writer.getWriter());
    }
    else if (_out instanceof JspWriterAdapter) {
      if (getCauchoResponse() != null) {
        getCauchoResponse().setResponseStream(_responseStream);
        getCauchoResponse().setForbidForward(false);
      }
    }
    else if (_out instanceof BodyContentImpl) {
      BodyContentImpl body = (BodyContentImpl) _out;

      _bodyResponseStream.setWriter(body.getWriter());
    }

    return oldWriter;

    // getCauchoResponse().setWriter(_os);
  }

  /**
   * Returns the top writer.
   */
  public PrintWriter getTopWriter()
    throws IOException
  {
    CauchoResponse response = getCauchoResponse();

    AbstractResponseStream currentStream = response.getResponseStream();

    response.setResponseStream(_responseStream);

    try {
      return response.getWriter();
    } finally {
      response.setResponseStream(currentStream);
    }
  }

  /**
   * Returns the response output stream.
   */
  ServletOutputStream getOutputStream()
  {
    try {
      return getCauchoResponse().getOutputStream();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Returns the underlying servlet for the page.
   */
  public Object getPage()
  {
    return _servlet;
  }

  /**
   * Returns the servlet request for the page.
   */
  @Override
  public HttpServletRequest getRequest()
  {
    return _request;
  }

  /**
   * Returns the servlet response for the page.
   */
  @Override
  public HttpServletResponse getResponse()
  {
    return getCauchoResponse();
  }

  /**
   * Returns the servlet response for the page.
   */
  public CauchoResponse getCauchoResponse()
  {
    return _response;
  }

  /**
   * Returns the servlet response for the page.
   */
  public HttpServletRequest getCauchoRequest()
  {
    return _request;
  }

  public HttpSession getSession()
  {
    if (_session == null) {
      HttpServletRequest req = getCauchoRequest();

      if (req != null)
        _session = req.getSession(false);
    }

    return _session;
  }

  /**
   * Returns the session, throwing an IllegalStateException if it's
   * not available.
   */
  public HttpSession getSessionScope()
  {
    if (_session == null)
      _session = getCauchoRequest().getSession(false);

    if (_session == null)
      throw new IllegalStateException(L.l("session is not available"));

    return _session;
  }

  public ServletConfig getServletConfig()
  {
    return _servlet.getServletConfig();
  }

  /**
   * Returns the page's servlet context.
   */
  public ServletContext getServletContext()
  {
    return _webApp;
  }

  /**
   * Returns the page's webApp.
   */
  public WebApp getApplication()
  {
    return _webApp;
  }

  /**
   * Returns the page's error page.
   */
  public String getErrorPage()
  {
    return _errorPage;
  }

  /**
   * Sets the page's error page.
   */
  public void setErrorPage(String errorPage)
  {
    _errorPage = errorPage;
  }

  public Exception getException()
  {
    return (Exception) getThrowable();
  }

  /**
   * Returns the Throwable stored by the error page.
   */
  public Throwable getThrowable()
  {
    Throwable exn = (Throwable) getCauchoRequest().getAttribute(EXCEPTION);

    if (exn == null)
      exn = (Throwable) getCauchoRequest().getAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE);
    if (exn == null)
      exn = (Throwable) getCauchoRequest().getAttribute("javax.servlet.jsp:jspException");

    return exn;
  }

  public void include(String relativeUrl)
    throws ServletException, IOException
  {
    include(relativeUrl, false);
  }

  /**
   * Include another servlet into the current output stream.
   *
   * @param relativeUrl url relative to the current request.
   */
  public void include(String relativeUrl, String query, boolean flush)
    throws ServletException, IOException
  {
    if (! "".equals(query)) {
      relativeUrl = encode(relativeUrl, query);
    }

    include(relativeUrl, flush);
  }

  public StringBuilder encode(String relativeUrl)
  {
    StringBuilder sb = new StringBuilder();

    sb.append(relativeUrl);

    if (relativeUrl.indexOf('?') >= 0)
      sb.append('&');
    else
      sb.append('?');

    return sb;
  }

  private String encode(String relativeUrl, String query)
  {
    StringBuilder sb = new StringBuilder();
    sb.append(relativeUrl);
    sb = encode(sb, query);
    return sb.toString();
  }

  public StringBuilder encode(StringBuilder sb, String query)
  {
    int len = query.length();

    for (int i = 0; i < len; i++) {
      char ch = query.charAt(i);

      switch (ch) {
      case ' ':
        sb.append('+');
        break;
      case '+':
        sb.append("%2b");
        break;
      case '%':
        sb.append("%25");
        break;
      case '&':
        sb.append("%26");
        break;
      default:
        sb.append(ch);
        break;
      }
    }

    return sb;
  }

  /**
   * Include another servlet into the current output stream.
   *
   * @param relativeUrl url relative to the current request.
   */
  public void include(String relativeUrl, boolean flush)
    throws ServletException, IOException
  {
    RequestDispatcher rd = null;

    HttpServletRequest req = (HttpServletRequest) getCauchoRequest();
    HttpServletResponse res = (HttpServletResponse) getResponse();

    if (relativeUrl != null && ! relativeUrl.startsWith("/")) {
      String path = RequestAdapter.getPageServletPath(req);

      String pathInfo = RequestAdapter.getPagePathInfo(req);

      String servletPath = req.getServletPath();

      if (path != null) {
        // jsp/15du vs jsp/15lk (tck)
        if (pathInfo != null && ! path.equals(servletPath))
          path += pathInfo;
      }
      else if (pathInfo != null)
        path = pathInfo;
      else
        path = "/";

      int p = path.lastIndexOf('/');
      if (p >= 0) {
        _cb.clear();
        _cb.append(path, 0, p + 1);
        _cb.append(relativeUrl);
        rd = getServletContext().getRequestDispatcher(_cb.toString());
      }
    }

    if (rd == null)
      rd = req.getRequestDispatcher(relativeUrl);

    if (rd == null)
      throw new ServletException(L.l("unknown including page `{0}'.",
                                     relativeUrl));

    // the FlushBuffer needs to happen to deal with OpenSymphony (Bug#1710)
    // jsp/17e9, 15lc, 15m4
    if (! flush) {
    }
    else if (_out instanceof FlushBuffer)
      ((FlushBuffer) _out).flushBuffer();
    else if (flush)
      _out.flush();

    rd.include(req, res);
  }

  /**
   * Include another servlet into the current output stream.
   *
   * @param relativeUrl url relative to the current request.
   */
  public void forward(String relativeUrl, String query)
    throws ServletException, IOException
  {
    if (! "".equals(query))
      relativeUrl = encode(relativeUrl, query);

    forward(relativeUrl);
  }

  /**
   * Forward a subrequest relative to the current url.  Absolute URLs
   * are relative to the context root.
   *
   * @param relativeUrl url relative to the current file
   */
  public void forward(String relativeUrl)
    throws ServletException, IOException
  {
    if (_bufferSize == 0) {
      // jsp/15m3, tck
      if (_out instanceof FlushBuffer)
        ((FlushBuffer) _out).flushBuffer();
      else
        _out.flush();
    }

    RequestDispatcher rd = null;

    HttpServletRequest req = (HttpServletRequest) getCauchoRequest();
    HttpServletResponse res = (HttpServletResponse) getResponse();

    if (res.isCommitted() && ! _webApp.isAllowForwardAfterFlush())
      throw new IllegalStateException(L.l("can't forward after writing HTTP headers"));
    else if (! _webApp.isAllowForwardAfterFlush())
      _out.clear();

    //jsp/183n jsp/18kl jsp/1625
    while (_out instanceof BodyContentImpl) {
      popBody();
    }

    if (relativeUrl != null && ! relativeUrl.startsWith("/")) {
      String servletPath = RequestAdapter.getPageServletPath(req);
      int p = servletPath.lastIndexOf('/');
      if (p >= 0) {
        _cb.clear();
        _cb.append(servletPath, 0, p + 1);
        _cb.append(relativeUrl);
        rd = getServletContext().getRequestDispatcher(_cb.toString());
      }
    }

    if (rd == null)
      rd = req.getRequestDispatcher(relativeUrl);

    if (rd == null)
      throw new ServletException(L.l("unknown forwarding page: `{0}'",
                                     relativeUrl));
    // rd.forward(req, res);
    rd.forward(req, _servletResponse);

    //_out.close();
    _responseStream.close();
  }

  /**
   * Handles an exception caught in the JSP page.
   *
   * @param e the caught exception
   */
  @Override
  public void handlePageException(Exception e)
    throws ServletException, IOException
  {
    handlePageException((Throwable) e);
  }

  /**
   * Handles an exception caught in the JSP page.
   *
   * @param e the caught exception
   */
  public void handlePageException(Throwable e)
    throws ServletException, IOException
  {
    if (e instanceof SkipPageException)
      return;

    HttpServletRequest request = getCauchoRequest();

    request.setAttribute("javax.servlet.jsp.jspException", e);

    CauchoResponse response = getCauchoResponse();

    response.setForbidForward(false);
    response.setResponseStream(_responseStream);
    response.killCache();
    response.setNoCache(true);

    if (e instanceof ClientDisconnectException)
      throw (ClientDisconnectException) e;

    if (! (_servlet instanceof Page)) {
    }
    else if (getApplication() == null
             || getApplication().getJsp() == null
             || ! getApplication().getJsp().isRecompileOnError()) {
    }
    else if (e instanceof OutOfMemoryError) {
    }
    else if (e instanceof Error) {
      try {
        Path workDir = getApplication().getRootDirectory().lookup("WEB-INF/work");
        String className = _servlet.getClass().getName();
        Path path = workDir.lookup(className.replace('.', '/') + ".class");

        log.warning("Removing " + path + " due to " + e);

        path.remove();
      } catch (Exception e1) {
      }
      Page page = (Page) _servlet;

      page._caucho_unload();
      if (! page.isDead()) {
        page.setDead();
        page.destroy();
      }
    }

    _topOut.clearBuffer();

    if (_errorPage != null) {
      if (log.isLoggable(Level.FINER)) {
        log.log(Level.FINER, e.toString(), e);
      }
      else if (e instanceof DisplayableException) {
        log.warning(e.getMessage());
      }
      else {
        log.log(Level.WARNING, e.toString(), e);
      }

      request.setAttribute(EXCEPTION, e);
      request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,
                           response.getStatus());
      request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, e);
      request.setAttribute(RequestDispatcher.ERROR_MESSAGE, e.getMessage());
      request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,
                           e.getClass());
      request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,
                           request.getRequestURI());

      // jsp/01ck
      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

      try {
        RequestDispatcher rd = getCauchoRequest().getRequestDispatcher(_errorPage);

        if (rd instanceof RequestDispatcherImpl) {
          getCauchoResponse().setHasError(true);

          ((RequestDispatcherImpl) rd).error(getCauchoRequest(), getCauchoResponse());
        }
        else {
          if (rd != null) {
            getCauchoResponse().killCache();
            getCauchoResponse().setNoCache(true);
            rd.forward(getCauchoRequest(), getCauchoResponse());
          }
          else {
            log.log(Level.FINE, e.toString(), e);
            throw new ServletException(L.l("`{0}' is an unknown error page.  The JSP errorPage directive must refer to a valid URL relative to the current web-app.",
                                           _errorPage));
          }
        }
        
        // jsp/01ck, server/02ei
        // response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      } catch (FileNotFoundException e2) {
        log.log(Level.WARNING, e.toString(), e2);
        throw new ServletException(L.l("`{0}' is an unknown error page.  The JSP errorPage directive must refer to a valid URL relative to the current web-app.",
                                       _errorPage));
      } catch (IOException e2) {
        log.log(Level.FINE, e.toString(), e2);
      }

      return;
    }

    /*
    if (_servlet instanceof Page && ! (e instanceof LineMapException)) {
      LineMap lineMap = ((Page) _servlet)._caucho_getLineMap();

      if (lineMap != null)
        e = new JspLineException(e, lineMap);
    }
    */

    if (e instanceof ServletException) {
      throw (ServletException) e;
    }
    else if (e instanceof IOException) {
      throw (IOException) e;
    }
    else if (e instanceof RuntimeException) {
      throw (RuntimeException) e;
    }
    else if (e instanceof Error) {
      throw (Error) e;
    }
    else {
      throw new ServletException(e);
    }
  }

  /**
   * Returns the error data
   */
  @Override
  public ErrorData getErrorData()
  {
    String uri = (String) getCauchoRequest().getAttribute(RequestDispatcher.ERROR_REQUEST_URI);

    if (uri == null)
      return null;

    Integer status = (Integer) getCauchoRequest().getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

    return new ErrorData(getThrowable(),
                         status == null ? 0 : status.intValue(),
                         (String) getCauchoRequest().getAttribute(RequestDispatcher.ERROR_REQUEST_URI),
                         (String) getCauchoRequest().getAttribute(RequestDispatcher.ERROR_SERVLET_NAME));
  }

  /**
   * Returns the variable resolver
   */
  public javax.servlet.jsp.el.VariableResolver getVariableResolver()
  {
    return this;
  }

  /**
   * Returns the expression evaluator
   */
  public ExpressionEvaluator getExpressionEvaluator()
  {
    if (_expressionEvaluator == null)
      _expressionEvaluator = new ExpressionEvaluatorImpl(getELContext());

    return _expressionEvaluator;
  }

  /**
   * Returns the expression evaluator
   */
  public ELContext getELContext()
  {
    if (_elContext != null)
      return _elContext;

    if (_elContextValue == null) {
      WebApp webApp = getApplication();

      JspApplicationContextImpl jspContext = webApp.getJspApplicationContext();

      if (_elResolver == null) {
        ELResolver[] resolverArray = jspContext.getELResolverArray();
        _elResolver = new PageContextELResolver(this, resolverArray);
      }

      if (_functionMapper == null)
        _functionMapper = new PageFunctionMapper();

      if (_variableMapper == null)
        _variableMapper = new PageVariableMapper();

      _elContextValue = new PageELContext();

      if (_elContextListeners == null)
        _elContextListeners = jspContext.getELListenerArray();
    }

    _elContext = _elContextValue;

    if (_elContextListeners.length > 0) {
      ELContextEvent event = new ELContextEvent(_elContext);

      for (int i = 0; i < _elContextListeners.length; i++) {
        _elContextListeners[i].contextCreated(event);
      }
    }

    return _elContext;
  }

  /**
   * Releases the context.
   */
  @Override
  public void release()
  {
    try {
      _servlet = null;

      if (_attributes.size() > 0)
        _attributes.clear();

      /* XXX:
      if (! autoFlush && response instanceof Response)
        ((Response) response).setDisableAutoFlush(false);
      */

      getCauchoResponse().setResponseStream(_responseStream);
      // getCauchoResponse().setFlushBuffer(null);

      _request = null;
      _webApp = null;
      _session = null;
      while (_out instanceof AbstractJspWriter) {
        if (_out instanceof AbstractJspWriter)
          _out = ((AbstractJspWriter) _out).popWriter();
      }

      _out = null;
      _topOut = null;
      _nodeEnv = null;

      _responseStream = null;

      ToCharResponseAdapter resAdapt = _responseAdapter;
      _responseAdapter = null;

      _servletResponse = null;
      _response = null;

      if (_elContext != null)
        _elContext.clear();

      _jspOutputStream.release();
      
      if (resAdapt != null) {
        // jsp/15l3
        resAdapt.finish();
        //_responseAdapter.close();

        ToCharResponseAdapter.free(resAdapt);
      }

      /*
        // server/137q
      if (! _hasException && responseStream != null)
        responseStream.close();
      */
    } catch (IOException e) {
      _out = null;
    }
  }

  /**
   * Returns the localized message appropriate for the current context.
   */
  public String getLocalizedMessage(String key,
                                    Object []args,
                                    String basename)
  {
    Object lc = basename;

    if (lc == null)
      lc = getAttribute("caucho.bundle");

    if (lc == null)
      lc = Config.find(this, Config.FMT_LOCALIZATION_CONTEXT);

    return getLocalizedMessage(lc, key, args, basename);
  }

  /**
   * Returns the localized message appropriate for the current context.
   */
  public String getLocalizedMessage(Object lc,
                                    String key,
                                    Object []args,
                                    String basename)
  {
    String bundleString = null;

    // jsp/1c51, jsp/1c54
    String prefix = (String) getAttribute("caucho.bundle.prefix");

    // jsp/1c3x
    if (key == null)
      key = "";

    if (prefix != null)
      key = prefix + key;

    if (lc == null) {
      lc = basename;

      if (lc == null || "".equals(lc))
        lc = getAttribute("caucho.bundle");
      if (lc == null)
        lc = Config.find(this, Config.FMT_LOCALIZATION_CONTEXT);
    }

    Locale locale = null;

    if (lc instanceof LocalizationContext) {
      ResourceBundle bundle = ((LocalizationContext) lc).getResourceBundle();
      locale = ((LocalizationContext) lc).getLocale();

      try {
        if (bundle != null)
          bundleString = bundle.getString(key);
      } catch (Exception e) {
      }
    }
    else if (lc instanceof String) {
      LocalizationContext loc = getBundle((String) lc);
      locale = loc.getLocale();

      ResourceBundle bundle = loc.getResourceBundle();

      try {
        if (bundle != null)
          bundleString = bundle.getString(key);
      } catch (Exception e) {
      }
    }

    if (bundleString == null)
      return "???" + key + "???";
    else if (args == null || args.length == 0)
      return bundleString;

    if (locale == null)
      locale = getLocale();

    if (locale != null)
      return new MessageFormat(bundleString, locale).format(args);
    else
      return new MessageFormat(bundleString).format(args);
  }

  /**
   * Returns the localized message appropriate for the current context.
   */
  public LocalizationContext getBundle(String name)
  {
    Object localeObj = Config.find(this, Config.FMT_LOCALE);
    LocalizationContext bundle = null;
    BundleManager manager = getBundleManager();

    if (localeObj instanceof Locale) {
      Locale locale = (Locale) localeObj;

      bundle = manager.getBundle(name, locale);
    }
    else if (localeObj instanceof String) {
      Locale locale = getLocale((String) localeObj, null);

      bundle = manager.getBundle(name, locale);
    }
    else {
      String acceptLanguage = getCauchoRequest().getHeader("Accept-Language");

      if (acceptLanguage != null) {
        String cacheKey = name + acceptLanguage;
        bundle = manager.getBundle(name, cacheKey, getCauchoRequest().getLocales());
      }
    }

    if (bundle != null)
      return bundle;

    Object fallback = Config.find(this, Config.FMT_FALLBACK_LOCALE);

    if (fallback instanceof Locale) {
      Locale locale = (Locale) fallback;

      bundle = manager.getBundle(name, locale);
    }
    else if (fallback instanceof String) {
      String localeName = (String) fallback;
      Locale locale = getLocale(localeName, null);

      bundle = manager.getBundle(name, locale);
    }

    if (bundle != null)
      return bundle;

    bundle = manager.getBundle(name);

    if (bundle != null)
      return bundle;
    else {
      return BundleManager.NULL_BUNDLE;
    }
  }

  /**
   * Returns the currently active locale.
   */
  public Locale getLocale()
  {
    if (_locale != null)
      return _locale;

    _locale = getLocaleImpl();

    if (_locale != null)
      getResponse().setLocale(_locale);

    return _locale;
  }

  /**
   * Returns the currently active locale.
   */
  private Locale getLocaleImpl()
  {
    Object localeObj = Config.find(this, Config.FMT_LOCALIZATION_CONTEXT);

    LocalizationContext lc;
    Locale locale = null;

    if (localeObj instanceof LocalizationContext) {
      lc = (LocalizationContext) localeObj;

      locale = lc.getLocale();

      if (locale != null)
        return locale;
    }

    localeObj = Config.find(this, Config.FMT_LOCALE);

      if (localeObj instanceof Locale)
        return (Locale) localeObj;
    else if (localeObj instanceof String)
      return getLocale((String) localeObj, null);

    lc = (LocalizationContext) getAttribute("caucho.bundle");

    if (lc != null)
      locale = lc.getLocale();

    if (locale != null)
      return locale;

    String acceptLanguage = getCauchoRequest().getHeader("Accept-Language");

    if (acceptLanguage != null) {
      Enumeration e = getCauchoRequest().getLocales();

      if (e != null && e.hasMoreElements())
        locale = (Locale) e.nextElement();
    }

    localeObj = Config.find(this, Config.FMT_FALLBACK_LOCALE);

    if (localeObj instanceof Locale)
      return (Locale) localeObj;
    else if (localeObj instanceof String)
      return getLocale((String) localeObj, null);
    else
      return null;
  }

  public static Locale getLocale(String value, String variant)
  {
    Locale locale = null;
    int len = value.length();

    CharBuffer cb = new CharBuffer();
    int i = 0;
    char ch = 0;
    for (; i < len && (ch = value.charAt(i)) != '_' && ch != '-'; i++)
      cb.append(ch);

    String language = cb.toString();

    if (ch == '_' || ch == '-') {
      cb.clear();

      for (i++; i < len && (ch = value.charAt(i)) != '_' && ch != '-'; i++)
        cb.append(ch);

      String country = cb.toString();

      if (variant != null && ! variant.equals(""))
        return new Locale(language, country, variant);
      else
        return new Locale(language, country);
    }
    else if (variant != null && ! variant.equals(""))
      return new Locale(language, "", variant);
    else
      return new Locale(language, "");
  }


  public static void printBody(BodyContentImpl body, boolean isEscaped)
    throws IOException
  {
    JspWriter out = body.getEnclosingWriter();
    CharBuffer string = body.getCharBuffer();
    char []cBuf = string.getBuffer();

    int length = string.length() - 1;

    for (; length >= 0; length--) {
      char ch = cBuf[length];
      if (ch != ' ' && ch != '\n' && ch != '\t' && ch != '\r')
        break;
    }
    length++;

    int i;

    for (i = 0; i < length; i++) {
      char ch = cBuf[i];
      if (ch != ' ' && ch != '\n' && ch != '\t' && ch != '\r')
        break;
    }

    if (! isEscaped) {
      out.write(cBuf, i, length - i);
      return;
    }

    for (; i < length; i++) {
      char ch = cBuf[i];

      switch (ch) {
      case '<':
        out.write("&lt;");
        break;
      case '>':
        out.write("&gt;");
        break;
      case '&':
        out.write("&amp;");
        break;
      case '\'':
        out.write("&#039;");
        break;
      case '"':
        out.write("&#034;");
        break;
      default:
        out.write((char) ch);
        break;
      }
    }
  }

  /**
   * Evaluates the fragment, returing the string value.
   */
  public String invoke(JspFragment fragment)
    throws JspException, IOException
  {
    if (fragment == null)
      return "";

    BodyContentImpl body = (BodyContentImpl) pushBody();

    try {
      fragment.invoke(body);
      return body.getString();
    } finally {
      popBody();
      body.release();
    }
  }

  /**
   * Evaluates the fragment, returing the string value.
   */
  public String invokeTrim(JspFragment fragment)
    throws JspException, IOException
  {
    if (fragment == null)
      return "";

    BodyContentImpl body = (BodyContentImpl) pushBody();

    try {
      fragment.invoke(body);
      return body.getTrimString();
    } finally {
      popBody();
      body.release();
    }
  }

  /**
   * Evaluates the fragment, returing a reader
   */
  public Reader invokeReader(JspFragment fragment)
    throws JspException, IOException
  {
    if (fragment == null)
      return null;

    BodyContentImpl body = (BodyContentImpl) pushBody();

    try {
      fragment.invoke(body);

      return body.getReader();
    } finally {
      popBody();
      body.release();
    }
  }

  /**
   * Parses a boolean value.
   */
  static public boolean toBoolean(String value)
  {
    if (value == null)
      return false;
    else if (value.equalsIgnoreCase("true"))
      return true;
    else if (value.equalsIgnoreCase("false"))
      return false;
    else
      return value.trim().equalsIgnoreCase("true");
  }

  /**
   * Set/Remove a page attribute.
   */
  public void defaultSetOrRemove(String var, Object value)
  {
    if (value != null)
      putAttribute(var, value);
    else
      removeAttribute(var);
  }

  /**
   * Set/Remove a page attribute.
   */
  public void pageSetOrRemove(String var, Object value)
  {
    if (value != null)
      _attributes.put(var, value);
    else
      _attributes.remove(var);
  }

  /**
   * Set/Remove a request attribute.
   */
  public void requestSetOrRemove(String var, Object value)
  {
    if (value != null)
      getCauchoRequest().setAttribute(var, value);
    else
      getCauchoRequest().removeAttribute(var);
  }

  /**
   * Set/Remove a session attribute.
   */
  public void sessionSetOrRemove(String var, Object value)
  {
    if (value != null)
      getSession().setAttribute(var, value);
    else
      getSession().removeAttribute(var);
  }

  /**
   * Set/Remove an webApp attribute.
   */
  public void applicationSetOrRemove(String var, Object value)
  {
    if (value != null)
      getApplication().setAttribute(var, value);
    else
      getApplication().removeAttribute(var);
  }

  /**
   * Returns true if the EL ignores exceptions
   */
  public boolean isIgnoreException()
  {
    JspPropertyGroup jsp = getApplication().getJsp();

    return (jsp == null || jsp.isIgnoreELException());
  }

  /**
   * Returns the XPath variable environment corresponding to this page
   */
  public VarEnv getVarEnv()
  {
    if (_varEnv == null)
      _varEnv = new PageVarEnv();

    return _varEnv;
  }

  /**
   * Returns the XPath node environment corresponding to this page
   */
  public Node getNodeEnv()
  {
    return _nodeEnv;
  }

  /**
   * Returns the XPath node environment corresponding to this page
   */
  public void setNodeEnv(Node node)
  {
    _nodeEnv = node;
  }

  /**
   * Creates an expression.
   */
  public ValueExpression createExpr(ValueExpression expr,
                                    String exprString,
                                    Class type)
  {
    if (_variableMapper == null || _variableMapper.isConstant())
      return expr;

    return JspUtil.createValueExpression(getELContext(), type, exprString);
  }

  /**
   * Finds an attribute in any of the scopes from page to webApp.
   *
   * @param name the attribute name.
   *
   * @return the attribute value
   */
  public Object resolveVariable(String name)
    throws javax.el.ELException
  {
    Object value = findAttribute(name);

    if (value != null)
      return value;

    ELContext elContext = getELContext();
    /*
    if (_elContext == null)
      _elContext = EL.getEnvironment();
    */

    return elContext.getELResolver().getValue(elContext, null, name);
  }

  /**
   * Returns the bundle manager.
   */
  private BundleManager getBundleManager()
  {
    if (_bundleManager == null)
      _bundleManager = BundleManager.create();

    return _bundleManager;
  }

  /**
   * Represents the XPath environment for this page.
   */
  public class PageVarEnv extends VarEnv {
    /**
     * Returns the value corresponding to the name.
     */
    public Object getValue(String name)
    {
      Object value = findAttribute(name);

      if (value != null)
        return value;

      int p = name.indexOf(':');
      if (p < 0) {
        // jsp/1m43, TCK
        throw new javax.el.ELException(L.l("'{0}' is an unknown variable", name));
      }

      String prefix = name.substring(0, p);
      String suffix = name.substring(p + 1);

      if (prefix.equals("param"))
        return getCauchoRequest().getParameter(suffix);
      else if (prefix.equals("header"))
        return ((HttpServletRequest) getCauchoRequest()).getHeader(suffix);
      else if (prefix.equals("cookie")) {
        Cookie cookie;
        HttpServletRequest request = (HttpServletRequest) getCauchoRequest();

        if (request instanceof CauchoRequest)
          cookie = ((CauchoRequest) request).getCookie(suffix);
        else
          cookie = null;

        if (cookie != null)
          return cookie.getValue();
        else
          return null;
      }
      else if (prefix.equals("initParam"))
        return getApplication().getInitParameter(suffix);
      else if (prefix.equals("pageScope")) {
        return getAttribute(suffix);
      }
      else if (prefix.equals("requestScope"))
        return getCauchoRequest().getAttribute(suffix);
      else if (prefix.equals("sessionScope"))
        return getSession().getAttribute(suffix);
      else if (prefix.equals("applicationScope"))
        return getApplication().getAttribute(suffix);
      else
        return null;
    }
  }

  static class StringArrayEnum implements Enumeration
  {
    private int _index;
    private String []_values;

    StringArrayEnum(String []values)
    {
      _values = values;
    }

    public boolean hasMoreElements()
    {
      return _index < _values.length;
    }

    public Object nextElement()
    {
      return _values[_index++];
    }
  }

  public class PageELContext extends ServletELContext {
    private HashMap<Class,Object> _contextMap;

    public PageELContext()
    {
    }

    @Override
    public Object getContext(Class key)
    {
      if (key == JspContext.class)
        return PageContextImpl.this;
      if (key == null)
        throw new NullPointerException();
      else if (_contextMap != null)
        return _contextMap.get(key);
      else
        return null;
    }

    @Override
    public void putContext(Class key, Object contextObject)
    {
      if (key == null)
        throw new NullPointerException();

      if (_contextMap == null)
        _contextMap = new HashMap<Class,Object>(8);

      _contextMap.put(key, contextObject);
    }

    public void clear()
    {
      if (_contextMap != null)
        _contextMap.clear();

      setLocale(null);
    }

    public PageContextImpl getPageContext()
    {
      return PageContextImpl.this;
    }

    @Override
    public ServletContext getApplication()
    {
      return PageContextImpl.this.getApplication();
    }

    @Override
    public Object getApplicationScope()
    {
      return new PageContextAttributeMap(PageContextImpl.this,
                                         APPLICATION_SCOPE);
    }

    @Override
    public HttpServletRequest getRequest()
    {
      return (HttpServletRequest) PageContextImpl.this.getRequest();
    }

    @Override
    public Object getRequestScope()
    {
      return new PageContextAttributeMap(PageContextImpl.this,
                                         REQUEST_SCOPE);
    }

    @Override
    public Object getSessionScope()
    {
      return new PageContextAttributeMap(PageContextImpl.this,
                                         SESSION_SCOPE);
    }

    public ELResolver getELResolver()
    {
      return _elResolver;
    }

    public javax.el.FunctionMapper getFunctionMapper()
    {
      return _functionMapper;
    }

    public javax.el.VariableMapper getVariableMapper()
    {
      return _variableMapper;
    }

    public String toString()
    {
      return getClass().getSimpleName() + "[" + PageContextImpl.this + "]";
    }
  }

  public class PageFunctionMapper extends javax.el.FunctionMapper {
    public Method resolveFunction(String prefix, String localName)
    {
      if (_functionMap != null)
        return _functionMap.get(prefix + ":" + localName);
      else
        return null;
    }
  }

  public class PageVariableMapper extends ImplicitVariableMapper {
    private HashMap<String,ValueExpression> _map;

    final boolean isConstant()
    {
      return _map == null || _map.size() == 0;
    }

    public ValueExpression resolveVariable(String var)
    {
      if (_map != null) {
        ValueExpression expr = _map.get(var);

        if (expr != null)
          return expr;
      }

      Object value = PageContextImpl.this.resolveVariable(var);

      if (value instanceof ValueExpression)
        return (ValueExpression) value;

      ValueExpression expr;

      expr = super.resolveVariable(var);

      return expr;
    }

    public ValueExpression setVariable(String var,
                                       ValueExpression expr)
    {
      // ValueExpression oldValue = getVariable(var);

      if (_map == null) {
        _map = new HashMap<String,ValueExpression>();
      }

      _map.put(var, expr);

      return expr;
    }

    public String toString()
    {
      return getClass().getSimpleName() + "[" + PageContextImpl.this + "]";
    }
  }
}
