/*
 * Copyright (c) 1998-2014 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.quercus.env;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.sql.DataSource;

import com.caucho.java.WorkDir;
import com.caucho.quercus.Location;
import com.caucho.quercus.QuercusContext;
import com.caucho.quercus.QuercusDieException;
import com.caucho.quercus.QuercusErrorException;
import com.caucho.quercus.QuercusException;
import com.caucho.quercus.QuercusExitException;
import com.caucho.quercus.QuercusModuleException;
import com.caucho.quercus.QuercusRuntimeException;
import com.caucho.quercus.expr.Expr;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.lib.ErrorModule;
import com.caucho.quercus.lib.VariableModule;
import com.caucho.quercus.lib.file.FileModule;
import com.caucho.quercus.lib.file.PhpProtocolWrapper;
import com.caucho.quercus.lib.file.PhpStderr;
import com.caucho.quercus.lib.file.PhpStdin;
import com.caucho.quercus.lib.file.PhpStdout;
import com.caucho.quercus.lib.file.ProtocolWrapper;
import com.caucho.quercus.lib.file.ZlibProtocolWrapper;
import com.caucho.quercus.lib.regexp.RegexpState;
import com.caucho.quercus.lib.string.StringModule;
import com.caucho.quercus.lib.string.StringUtility;
import com.caucho.quercus.module.IniDefinition;
import com.caucho.quercus.module.ModuleContext;
import com.caucho.quercus.module.ModuleStartupListener;
import com.caucho.quercus.page.QuercusPage;
import com.caucho.quercus.program.ClassDef;
import com.caucho.quercus.program.JavaClassDef;
import com.caucho.quercus.program.QuercusProgram;
import com.caucho.quercus.program.UndefinedFunction;
import com.caucho.quercus.resources.StreamContextResource;
import com.caucho.quercus.servlet.api.QuercusCookie;
import com.caucho.quercus.servlet.api.QuercusHttpServletRequest;
import com.caucho.quercus.servlet.api.QuercusHttpServletResponse;
import com.caucho.quercus.servlet.api.QuercusHttpSession;
import com.caucho.quercus.servlet.api.QuercusServletContext;
import com.caucho.util.CharBuffer;
import com.caucho.util.FreeList;
import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import com.caucho.util.LruCache;
import com.caucho.util.QDate;
import com.caucho.vfs.ByteToChar;
import com.caucho.vfs.Encoding;
import com.caucho.vfs.JarPath;
import com.caucho.vfs.MemoryPath;
import com.caucho.vfs.NullPath;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.TempBuffer;
import com.caucho.vfs.WriteStream;
import com.caucho.vfs.i18n.EncodingReader;

/**
 * Represents the Quercus environment.
 */
public class Env
{
  private static final L10N L = new L10N(Env.class);
  private static final Logger log = Logger.getLogger(Env.class.getName());

  public static final int B_ERROR = 0;
  public static final int B_WARNING = 1;
  public static final int B_PARSE = 2;
  public static final int B_NOTICE = 3;
  public static final int B_CORE_ERROR = 4;
  public static final int B_CORE_WARNING = 5;
  public static final int B_COMPILE_ERROR = 6;
  public static final int B_COMPILE_WARNING = 7;
  public static final int B_USER_ERROR = 8;
  public static final int B_USER_WARNING = 9;
  public static final int B_USER_NOTICE = 10;
  public static final int B_STRICT = 11;
  public static final int B_RECOVERABLE_ERROR = 12;
  public static final int B_DEPRECATED = 13;
  public static final int B_USER_DEPRECATED = 14;

  public static final int B_LAST = B_RECOVERABLE_ERROR;

  public static final int E_ERROR = 1 << B_ERROR;
  public static final int E_WARNING = 1 << B_WARNING;
  public static final int E_PARSE = 1 << B_PARSE;
  public static final int E_NOTICE = 1 << B_NOTICE;
  public static final int E_CORE_ERROR = 1 << B_CORE_ERROR;
  public static final int E_CORE_WARNING = 1 << B_CORE_WARNING;
  public static final int E_COMPILE_ERROR = 1 << B_COMPILE_ERROR;
  public static final int E_COMPILE_WARNING = 1 << B_COMPILE_WARNING;
  public static final int E_USER_ERROR = 1 << B_USER_ERROR;
  public static final int E_USER_WARNING = 1 << B_USER_WARNING;
  public static final int E_USER_NOTICE = 1 << B_USER_NOTICE;
  public static final int E_ALL = 32767; //(0x1111 1111 1111 11)
  public static final int E_STRICT = 1 << B_STRICT;
  public static final int E_RECOVERABLE_ERROR = 1 << B_RECOVERABLE_ERROR;
  public static final int E_DEPRECATED = 1 << B_DEPRECATED;
  public static final int E_USER_DEPRECATED = 1 << B_USER_DEPRECATED;

  public static final int E_DEFAULT = E_ALL & ~E_NOTICE;

  private static final int _SERVER = 1;
  private static final int _GET = 2;
  private static final int _POST = 3;
  private static final int _COOKIE = 4;
  private static final int _GLOBAL = 5;
  private static final int _REQUEST = 6;
  private static final int _SESSION = 7;
  private static final int HTTP_GET_VARS = 8;
  private static final int HTTP_POST_VARS = 9;
  private static final int HTTP_COOKIE_VARS = 10;
  private static final int PHP_SELF = 11;
  private static final int _FILES = 12;
  private static final int HTTP_POST_FILES = 13;
  private static final int _ENV = 14;
  private static final int HTTP_SERVER_VARS = 15;
  private static final int HTTP_RAW_POST_DATA = 16;

  private static final int ARGC = 17;
  private static final int ARGV = 18;

  private static final IntMap SPECIAL_VARS
    = new IntMap();

  private static final IntMap SPECIAL_VARS_U
    = new IntMap();

  private static final StringValue PHP_SELF_STRING
    = new ConstStringValue("PHP_SELF");

  private static final StringValue PHP_SELF_STRING_U
    = new UnicodeBuilderValue("PHP_SELF");

  private static final StringValue S_GET
    = new ConstStringValue("_GET");

  private static final StringValue S_GET_U
    = new UnicodeBuilderValue("_GET");

  private static final StringValue S_POST
    = new ConstStringValue("_POST");

  private static final StringValue S_POST_U
    = new UnicodeBuilderValue("_POST");

  private static final StringValue S_SESSION
    = new ConstStringValue("_SESSION");

  private static final StringValue S_SESSION_U
    = new UnicodeBuilderValue("_SESSION");

  private static final StringValue S_SERVER
    = new ConstStringValue("_SERVER");

  private static final StringValue S_SERVER_U
    = new UnicodeBuilderValue("_SERVER");

  private static final StringValue S_COOKIE
    = new ConstStringValue("_COOKIE");

  private static final StringValue S_COOKIE_U
    = new UnicodeBuilderValue("_COOKIE");

  private static final StringValue S_FILES
    = new ConstStringValue("_FILES");

  private static final StringValue S_FILES_U
    = new UnicodeBuilderValue("_FILES");

  private static final StringValue S_ARGV
    = new ConstStringValue("argv");

  private static final StringValue S_ARGV_U
    = new UnicodeBuilderValue("argv");

  public static final Value []EMPTY_VALUE = new Value[0];

  private static ThreadLocal<Env> _threadEnv = new ThreadLocal<Env>();

  private static final FreeList<AbstractFunction[]> _freeFunList
    = new FreeList<AbstractFunction[]>(256);

  private static final FreeList<ClassDef[]> _freeClassDefList
    = new FreeList<ClassDef[]>(256);

  private static final FreeList<QuercusClass[]> _freeClassList
    = new FreeList<QuercusClass[]>(256);

  private static final FreeList<Value[]> _freeConstList
    = new FreeList<Value[]>(256);

  private static final FreeList<QDate> _freeGmtDateList
    = new FreeList<QDate>(256);

  private static final FreeList<QDate> _freeLocalDateList
    = new FreeList<QDate>(256);

  private static final LruCache<String,StringValue> _internStringMap
    = new LruCache<String,StringValue>(4096);

  protected final QuercusContext _quercus;

  private QuercusPage _page;

  private HashMap<String,Value> _scriptGlobalMap
    = new HashMap<String,Value>(16);

  // Function map
  public AbstractFunction []_fun;

  // anonymous functions created by create_function()
  public HashMap<StringValue, AbstractFunction> _anonymousFunMap;

  // Class map
  public ClassDef []_classDef;
  public QuercusClass []_qClass;

  public HashMap<String,QuercusClass> _classAliasMap;

  // Constant map
  public Value []_const;

  // Globals
  private Map<StringValue, EnvVar> _globalMap
    = new HashMap<StringValue, EnvVar>();

  private EnvVar []_globalList;

  // Statics
  private Map<StringValue, Var> _staticMap
    = new HashMap<StringValue, Var>();

  // Current env
  private Map<StringValue, EnvVar> _map = _globalMap;

  private HashMap<String, Value> _iniMap;

  // specialMap is used for implicit resources like the mysql link
  private HashMap<String, Object> _specialMap
    = new HashMap<String, Object>();

  private HashSet<String> _initializedClassSet
    = new HashSet<String>();

  // include_path ini
  private int _iniCount = 1;

  private ArrayList<EnvCleanup> _cleanupList;
  private ArrayList<ObjectValue> _objCleanupList;
  private ArrayList<Shutdown> _shutdownList;

  private String _defaultIncludePath;
  private String _includePath;
  private int _includePathIniCount;
  private ArrayList<String> _includePathList;
  private HashMap<Path,ArrayList<Path>> _includePathMap;

  // XXX: may require a LinkedHashMap for ordering?
  private HashMap<Path,QuercusPage> _includeMap
    = new HashMap<Path,QuercusPage>();

  private Value _this = NullThisValue.NULL;

  private Closure _closure;

  private final boolean _isUnicodeSemantics;
  private boolean _isAllowUrlInclude;
  private boolean _isAllowUrlFopen;

  private HashMap<StringValue,Path> _lookupCache
    = new HashMap<StringValue,Path>();

  private HashMap<ConnectionEntry,ConnectionEntry> _connMap
    = new HashMap<ConnectionEntry,ConnectionEntry>();

  private AbstractFunction _autoload;
  private HashSet<String> _autoloadClasses
    = new HashSet<String>();

  private ArrayList<Callable> _autoloadList;
  private InternalAutoloadCallback _internalAutoload;
  private Location _location;

  private long _startTime;
  private long _timeLimit = 600000L;
  private long _endTime;

  private Expr [] _callStack;
  private Value [] _callThisStack;
  private Value [][] _callArgStack;
  private int _callStackTop;

  private QuercusClass _callingClass;

  private Value [] _functionArgs;

  private LinkedList<FieldGetEntry> _fieldGetList
    = new LinkedList<FieldGetEntry>();

  private LinkedList<FieldGetEntry> _fieldSetList
    = new LinkedList<FieldGetEntry>();

  private LinkedList<FieldGetEntry> _issetList
    = new LinkedList<FieldGetEntry>();

  private LinkedList<FieldGetEntry> _unsetList
    = new LinkedList<FieldGetEntry>();

  public enum OVERLOADING_TYPES {INVALID_FIRST, FIELDGET, FIELDSET, ISSET, UNSET, INVALID_LAST};

  private Path _selfPath;
  private Path _selfDirectory;
  private Path _pwd;
  private Path _uploadPath;
  private Path _tmpPath;
  private ArrayList<Path> _removePaths;

  private final boolean _isStrict;

  private QuercusHttpServletRequest _request;
  private QuercusHttpServletResponse _response;

  private ArrayValue _inputGet;
  private ArrayValue _inputCookie;
  private ArrayValue _inputEnv;
  private ArrayValue _inputServer;

  private ArrayValue _inputPost = new ArrayValueImpl();
  private ArrayValue _files = new ArrayValueImpl();

  private StringValue _inputData;

  private SessionArrayValue _session;
  private QuercusHttpSession _javaSession;

  private ScriptContext _scriptContext;

  private WriteStream _originalOut;
  private OutputBuffer _outputBuffer;

  private WriteStream _out;

  private LocaleInfo _locale;

  private Callable [] _prevErrorHandlers = new Callback[B_LAST + 1];
  private Callable [] _errorHandlers = new Callback[B_LAST + 1];

  private Callable _prevExceptionHandler;
  private Callable _exceptionHandler;

  private SessionCallback _sessionCallback;

  private StreamContextResource _defaultStreamContext;
  private HashMap<StringValue,ProtocolWrapper> _wrappedStreamMap;
  private HashMap<StringValue,ProtocolWrapper> _unregisteredWrappedStreamMap;

  private int _objectId = 0;

  private Logger _logger;

  // hold special Quercus php import statements
  private ImportMap _importMap;

  private TimeZone _defaultTimeZone;
  private QDate _localDate;
  private QDate _gmtDate;

  private Object _gzStream;

  private Env _oldThreadEnv;

  private boolean _isTimeout;

  private long _firstMicroTime;
  private long _firstNanoTime;

  private RegexpState _freeRegexpState;

  private Object _duplex;

  private StringValue _variablesOrder;
  private int []_querySeparatorMap;

  public static final int []DEFAULT_QUERY_SEPARATOR_MAP;

  private CharBuffer _cb = new CharBuffer();

  private int _lastErrorType = -1;
  private String _lastErrorMessage = null;
  private Location _lastErrorLocation = null;

  public Env(QuercusContext quercus,
             QuercusPage page,
             WriteStream out,
             QuercusHttpServletRequest request,
             QuercusHttpServletResponse response)
  {
    _quercus = quercus;

    _isStrict = quercus.isStrict();
    _isUnicodeSemantics = quercus.isUnicodeSemantics();

    _isAllowUrlInclude = quercus.isAllowUrlInclude();
    _isAllowUrlFopen = quercus.isAllowUrlFopen();

    _variablesOrder
      = _quercus.getIniValue("variables_order").toStringValue(this);

    StringValue querySeparators
      = _quercus.getIniValue("arg_separator.input").toStringValue(this);

    int len = querySeparators.length();
    if (len == 0)
      _querySeparatorMap = DEFAULT_QUERY_SEPARATOR_MAP;
    else {
      _querySeparatorMap = new int[128];

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

        _querySeparatorMap[ch] = 1;
      }
    }

    _page = page;

    // XXX: grab initial from page
    // _defState = new DefinitionState(quercus);

    AbstractFunction []defFuns = getDefaultFunctionMap();
    _fun = _freeFunList.allocate();
    if (_fun == null || _fun.length < defFuns.length)
      _fun = new AbstractFunction[defFuns.length];
    System.arraycopy(defFuns, 0, _fun, 0, defFuns.length);

    ClassDef []defClasses = quercus.getClassDefMap();

    _classDef = _freeClassDefList.allocate();
    if (_classDef == null || _classDef.length < defClasses.length)
      _classDef = new ClassDef[defClasses.length];
    else {
      // list should have been zeroed on call to free
    }

    _qClass = _freeClassList.allocate();
    if (_qClass == null || _qClass.length < defClasses.length)
      _qClass = new QuercusClass[defClasses.length];
    else {
      // list should have been zeroed on call to free
    }

    Value []defConst = quercus.getConstantMap();

    _const = _freeConstList.allocate();
    if (_const == null || _const.length < defConst.length)
      _const = new Value[defConst.length];
    else {
      // list should have been zeroed on call to free
    }

    System.arraycopy(defConst, 0, _const, 0, defConst.length);

    _originalOut = out;
    _out = out;

    _request = request;
    _response = response;

    if (page != null) {
      pageInit(page);
    }

    // we need it for running scripts like the PDF generation
    if (request != null && page != null) {
      setPwd(page.getPwd(null));
    }
    else {
      setPwd(_quercus.getPwd());
    }

    if (_page != null) {
      setSelfPath(_page.getSelfPath(null));

      // php/0b32
      _includeMap.put(_selfPath, _page);
    }

    _internalAutoload
      = new InternalAutoloadCallback("com/caucho/quercus/php/");

    // Define the constant string PHP_VERSION

    String version = quercus.getPhpVersion();
    addConstant("PHP_VERSION", createString(version), true);

    // STDIN, STDOUT, STDERR
    // php://stdin, php://stdout, php://stderr
    if (response == null) {
      addConstant("STDOUT", wrapJava(new PhpStdout()), false);
      addConstant("STDERR", wrapJava(new PhpStderr()), false);
      addConstant("STDIN", wrapJava(new PhpStdin(this)), false);
    }
  }

  public Env(QuercusContext quercus)
  {
    this(quercus, null, null, null, null);
  }

  public static Env getCurrent()
  {
    return _threadEnv.get();
  }

  public static Env getInstance()
  {
    return getCurrent();
  }

  private void fillGet(ArrayValue array, boolean isMagicQuotes)
  {
    String queryString = getQueryString();

    if (queryString == null || queryString.length() == 0)
      return;

    StringUtility.parseStr(this,
                           queryString,
                           array,
                           true,
                           getHttpInputEncoding(),
                           isMagicQuotes,
                           true,
                           _querySeparatorMap);

    /*
    try {
      String encoding = getHttpInputEncoding();

      if (encoding == null)
        encoding = "iso-8859-1";

      _request.setCharacterEncoding(encoding);
    } catch (Exception e) {
      log.log(Level.FINE, e.toString(), e);
    }

    ArrayList<String> keys = new ArrayList<String>();
    keys.addAll(_request.getParameterMap().keySet());

    Collections.sort(keys);

    for (String key : keys) {
      String []value = _request.getParameterValues(key);

      Post.addFormValue(this,
                        array,
                        key,
                        value,
                        isMagicQuotes);
    }
    */
  }

  private void fillPost(ArrayValue array, ArrayValue postArray)
  {
    for (Map.Entry<Value, Value> entry : postArray.entrySet()) {
      Value key = entry.getKey();
      Value value = entry.getValue();

      Value existingValue = array.get(key);

      if (existingValue.isArray() && value.isArray())
       existingValue.toArrayValue(this).putAll(value.toArrayValue(this));
      else
        array.put(entry.getKey(), entry.getValue().copy());
    }
  }

  private void fillCookies(ArrayValue array,
                           QuercusCookie []cookies,
                           boolean isMagicQuotes)
  {
    for (int i = 0; cookies != null && i < cookies.length; i++) {
      QuercusCookie cookie = cookies[i];

      String decodedValue = decodeValue(cookie.getValue());

      Post.addFormValue(this,
                        array,
                        cookie.getName(),
                        new String[] { decodedValue },
                        isMagicQuotes,
                        true);
    }
  }

  protected void fillPost(ArrayValue postArray,
                          ArrayValue files,
                          QuercusHttpServletRequest request,
                          boolean isMagicQuotes)
  {
    if (request != null && request.getMethod().equals("POST")) {
      Post.fillPost(this,
                    postArray,
                    files,
                    request,
                    isMagicQuotes,
                    getIniBoolean("file_uploads"));
    } else if (request != null && ! request.getMethod().equals("GET")) {
      InputStream is = null;

      try {
        is = request.getInputStream();
      } catch (IOException e) {
        warning(e);
      }

      StringValue bb = createBinaryBuilder();
      bb.appendReadAll(is, Integer.MAX_VALUE);

      setInputData(bb);
    }
  }

  protected AbstractFunction []getDefaultFunctionMap()
  {
    return getQuercus().getFunctionMap();
  }

  /**
   * Initialize the page, loading any functions and classes
   */
  protected QuercusPage pageInit(QuercusPage page)
  {
    if (page.getCompiledPage() != null)
      page = page.getCompiledPage();

    page.init(this);

    page.importDefinitions(this);

    return page;
  }

  //
  // script accessible methods
  //

  /**
   * External calls to set a global value.
   */
  public Value setScriptGlobal(String name, Object object)
  {
    Value value;

    if (object instanceof Value)
      value = (Value) object;
    else if (object == null)
      value = NullValue.NULL;
    else
      value = wrapJava(object);

    _scriptGlobalMap.put(name, value);

    return value;
  }

  //
  // i18n
  //

  /**
   * Returns true if unicode.semantics is on.
   */
  public boolean isUnicodeSemantics()
  {
    return _isUnicodeSemantics;
  }

  /**
   * Returns the encoding used for scripts.
   */
  public String getScriptEncoding()
  {
    StringValue encoding = getIni("unicode.script_encoding");

    if (encoding.length() == 0) {
      encoding = getIni("unicode.fallback_encoding");

      if (encoding.length() == 0)
        return getQuercus().getScriptEncoding();
    }

    return encoding.toString();
  }

  /**
   * Returns the encoding used for runtime conversions, e.g. files
   * XXX: ISO-8859-1 when unicode.semantics is OFF
   */
  public String getRuntimeEncoding()
  {
    if (! _isUnicodeSemantics)
      return "iso-8859-1";

    StringValue encoding = getIni("unicode.runtime_encoding");

    if (encoding.length() == 0) {
      encoding = getIni("unicode.fallback_encoding");
    }

    if (encoding.length() > 0) {
      return encoding.toString();
    }
    else {
      return "utf-8";
    }
  }

  /**
   * Sets the encoding used for runtime conversions.
   */
  public Value setRuntimeEncoding(String encoding)
  {
    return setIni("unicode.runtime_encoding", encoding);
  }

  /**
   * Returns the encoding used for runtime conversions, e.g. files
   */
  public EncodingReader getRuntimeEncodingFactory()
    throws IOException
  {
    return Encoding.getReadFactory(getRuntimeEncoding());
  }

  /**
   * Returns the encoding used for input, i.e. post,
   * null if unicode.semantics is off.
   */
  public String getHttpInputEncoding()
  {
    if (! _isUnicodeSemantics)
      return null;

    StringValue encoding = getIni("unicode.http_input_encoding");

    if (encoding.length() == 0) {
      encoding = getIni("unicode.fallback_encoding");
    }

    if (encoding.length() > 0) {
      return encoding.toString();
    }
    else {
      return "utf-8";
    }
  }

  /**
   * Returns the encoding used for output, null if unicode.semantics is off.
   */
  public String getOutputEncoding()
  {
    return _quercus.getOutputEncoding();
  }

  /**
   * Creates a binary builder.
   */
  public StringValue createBinaryBuilder()
  {
    if (_isUnicodeSemantics)
      return new BinaryBuilderValue();
    else
      return new StringBuilderValue();
  }

  /**
   * Creates a binary builder for large things like files.
   */
  public StringValue createLargeBinaryBuilder()
  {
    if (_isUnicodeSemantics)
      return new BinaryBuilderValue();
    else
      return new LargeStringBuilderValue();
  }

  /**
   * Creates a binary builder.
   */
  public StringValue createBinaryBuilder(int length)
  {
    if (_isUnicodeSemantics)
      return new BinaryBuilderValue(length);
    else
      return new StringBuilderValue(length);
  }

  /**
   * Creates a binary builder.
   */
  public StringValue createBinaryBuilder(byte []buffer, int offset, int length)
  {
    if (_isUnicodeSemantics)
      return new BinaryBuilderValue(buffer, offset, length);
    else
      return new StringBuilderValue(buffer, offset, length);
  }

  /**
   * Creates a binary builder.
   */
  public StringValue createBinaryBuilder(byte []buffer)
  {
    if (buffer == null)
      return StringBuilderValue.EMPTY;

    if (_isUnicodeSemantics)
      return new BinaryBuilderValue(buffer, 0, buffer.length);
    else
      return new StringBuilderValue(buffer, 0, buffer.length);
  }

  /**
   * Creates a unicode builder.
   */
  public StringValue createUnicodeBuilder()
  {
    if (_isUnicodeSemantics)
      return new UnicodeBuilderValue();
    else
      return new StringBuilderValue();
  }

  public TimeZone getDefaultTimeZone()
  {
    if (_defaultTimeZone != null)
      return _defaultTimeZone;

    String timeZone = getIniString("date.timezone");

    if (timeZone != null)
      return TimeZone.getTimeZone(timeZone);
    else
      return TimeZone.getDefault();
  }

  public QDate getGmtDate()
  {
    if (_gmtDate == null) {
      _gmtDate = _freeGmtDateList.allocate();

      if (_gmtDate == null)
        _gmtDate = new QDate();
    }

    return _gmtDate;
  }

  public QDate getLocalDate()
  {
    if (_localDate == null) {
      _localDate = _freeLocalDateList.allocate();

      if (_localDate == null)
        _localDate = QDate.createLocal();
    }

    return _localDate;
  }

  public QDate getDate()
  {
    TimeZone zone = getDefaultTimeZone();

    if (zone.getID().equals("GMT"))
      return getGmtDate();
    else if (zone.equals(TimeZone.getDefault()))
      return getLocalDate();
    else
      return new QDate(zone);
  }

  public void setDefaultTimeZone(String id)
  {
    _defaultTimeZone = TimeZone.getTimeZone(id);
  }

  public void setDefaultTimeZone(TimeZone zone)
  {
    _defaultTimeZone = zone;
  }

  /**
   * Returns the ServletContext.
   */
  public QuercusServletContext getServletContext()
  {
    return _quercus.getServletContext();
  }

  /**
   * Sets the ScriptContext.
   */
  public void setScriptContext(ScriptContext context)
  {
    _scriptContext = context;
  }

  public ArrayValue getInputGetArray()
  {
    ArrayValue array = _inputGet;

    if (array == null) {
      array = new ArrayValueImpl();
      fillGet(array, getIniBoolean("magic_quotes_gpc"));

      _inputGet = array;
    }

    return array;
  }

  public ArrayValue getInputPostArray()
  {
    return _inputPost;
  }

  public ArrayValue getInputCookieArray()
  {
    ArrayValue array = _inputCookie;

    if (array == null) {
      array = new ArrayValueImpl();
      fillCookies(array, _request.getCookies(), getIniBoolean("magic_quotes_gpc"));

      _inputCookie = array;
    }

    return array;
  }

  public ArrayValue getInputEnvArray()
  {
    ArrayValue array = _inputEnv;

    if (array == null) {
      array = new ArrayValueImpl();
      _inputEnv = array;
    }

    return array;
  }

  public ArrayValue getInputServerArray()
  {
    ArrayValue array = _inputServer;

    if (array == null) {
      array = new ServerArrayValue(this);
      _inputServer = array;
    }

    return array;
  }

  /**
   * Returns the input (POST, PUT) data.
   */
  public StringValue getInputData()
  {
    return _inputData;
  }

  /**
   * Sets the post data.
   */
  public void setInputData(StringValue data)
  {
    _inputData = data;
  }

  /**
   * Returns true for strict mode.
   */
  public final boolean isStrict()
  {
    return _isStrict;
  }

  /*
   * Returns true if allowed to include urls.
   */
  public boolean isAllowUrlInclude()
  {
    return _isAllowUrlInclude;
  }

  /**
   * Returns true if allowed to fopen urls.
   */
  public boolean isAllowUrlFopen()
  {
    return _isAllowUrlFopen;
  }

  /**
   * Returns the connection status
   */
  public int getConnectionStatus()
  {
    // always returns a valid value for now
    return 0;
  }

  public void start()
  {
    _oldThreadEnv = _threadEnv.get();

    _startTime = _quercus.getCurrentTime();
    _timeLimit = getIniLong("max_execution_time") * 1000;

    if (_timeLimit > 0)
      _endTime = _startTime + _timeLimit;
    else
      _endTime = Long.MAX_VALUE / 2;

    _threadEnv.set(this);

    fillPost(_inputPost,
             _files,
             _request,
             getIniBoolean("magic_quotes_gpc"));

    // quercus/1b06
    String encoding = getOutputEncoding();

    String type = getIniString("default_mimetype");

    if ("".equals(type) || _response == null) {
    }
    else if (encoding != null)
      _response.setContentType(type + "; charset=" + encoding);
    else
      _response.setContentType(type);

    if (_out != null && encoding != null) {
      try {
        _out.setEncoding(encoding);
      } catch (Exception e) {
        log.log(Level.WARNING, e.toString(), e);
      }
    }

    HashSet<ModuleStartupListener> listeners
      = _quercus.getModuleStartupListeners();

    for (ModuleStartupListener listener : listeners) {
      listener.startup(this);
    }

    _quercus.startEnv(this);
  }

  /**
   * Returns the current time (may be cached).
   */
  public long getCurrentTime()
  {
    return getQuercus().getCurrentTime();
  }

  /**
   * Returns the current time (not cached).
   */
  public long getExactTime()
  {
    return getQuercus().getExactTime();
  }

  /**
   * add resource to the list of refrences that are
   * cleaned up when finished with this environment.
   */
  public void addCleanup(EnvCleanup envCleanup)
  {
    if (_cleanupList == null)
      _cleanupList = new ArrayList<EnvCleanup>();

    _cleanupList.add(envCleanup);
  }

  /**
   * add an object with a destructor to the list of references that are
   * cleaned up when finished with this environment.
   */
  public void addObjectCleanup(ObjectValue objCleanup)
  {
    if (_objCleanupList == null)
      _objCleanupList = new ArrayList<ObjectValue>();

    _objCleanupList.add(objCleanup);
  }

  /**
   * remove resource from the list of references that are
   * cleaned up when finished with this environment.
   *
   * @param resource
   */
  public void removeCleanup(EnvCleanup envCleanup)
  {
    if (_cleanupList == null)
      return;

    for (int i = _cleanupList.size() - 1; i >= 0; i--) {
      EnvCleanup res = _cleanupList.get(i);

      if (envCleanup.equals(res)) {
        _cleanupList.remove(i);
        break;
      }
    }
  }

  /**
   * Returns the owning PHP engine.
   */
  public QuercusContext getQuercus()
  {
    return _quercus;
  }

  /**
   * Returns the owning PHP engine.
   */
  public ModuleContext getModuleContext()
  {
    return _quercus.getModuleContext();
  }

  /**
   * Returns the configured database.
   */
  public DataSource getDatabase()
  {
    return _quercus.getDatabase();
  }

  protected final DataSource findDatabase(String driver, String url)
    throws Exception
  {
    return _quercus.findDatabase(driver, url);
  }

  /**
   * Returns a connection to the given database. If there is
   * already a connection to this specific database, then
   * return the connection from the pool. Otherwise, create
   * a new connection and add it to the pool.
   */
  public ConnectionEntry getConnection(String driver, String url,
                                       String userName, String password,
                                       boolean isReuse)
    throws Exception
  {
    // XXX: connections might not be reusable (see gallery2), because
    // of case with two reuses and one closes because of CREATE or
    // catalog
    isReuse = false;

    DataSource database = _quercus.getDatabase();

    if (database != null) {
      // php/4360 - if configured database, don't use PHP names
      userName = null;
      password = null;
    }
    else {
      database = findDatabase(driver, url);

      if (database == null)
        return null;
    }

    ConnectionEntry entry = new ConnectionEntry(this);
    entry.init(database, userName, password);

    ConnectionEntry oldEntry = null;

    if (isReuse) {
      oldEntry = _connMap.get(entry);
    }

    if (oldEntry != null && oldEntry.isReusable()) {
      return oldEntry;
    }

    entry.connect(isReuse);

    if (isReuse) {
      _connMap.put(entry, entry);
    }

    return entry;
  }

  /**
   * Returns the configured database.
   */
  public DataSource getDataSource(String driver, String url)
    throws Exception
  {
    DataSource database = _quercus.getDatabase();

    if (database != null)
      return database;
    else
      return findDatabase(driver, url);
  }

  /**
   * Sets the time limit.
   */
  public void setTimeLimit(long ms)
  {
    if (ms <= 0)
      ms = Long.MAX_VALUE / 2;

    _timeLimit = ms;
  }

  /**
   * Checks for the program timeout.
   */
  public void checkTimeout()
  {
    /*
    long now = CurrentTime.getCurrentTime();

    if (_endTime < now)
      throw new QuercusRuntimeException(L.l("script timed out"));
      */
    if (_isTimeout)
      throw new QuercusRuntimeException(L.l("script timed out"));
  }

  public void updateTimeout()
  {
    long now = _quercus.getCurrentTime();

    if (_endTime < now)
      _isTimeout = true;
  }

  public void resetTimeout()
  {
    _startTime = _quercus.getCurrentTime();
    _endTime = _startTime + _timeLimit;
    _isTimeout = false;
  }

  public long getStartTime()
  {
    return _startTime;
  }

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

  /**
   * Returns the writer.
   */
  public WriteStream getOriginalOut()
  {
    return _originalOut;
  }

  /**
   * Flushes the output buffer.
   */
  public final void flush()
  {
    try {
      getOut().flush();
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a string
   */
  public final void print(String v)
  {
    try {
      getOut().print(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a character buffer.
   */
  public final void print(char []buffer, int offset, int length)
  {
    try {
      getOut().print(buffer, offset, length);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a char
   */
  public final void print(char v)
  {
    try {
      getOut().print(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a long
   */
  public final void print(long v)
  {
    try {
      getOut().print(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a double
   */
  public final void print(double v)
  {
    try {
      long longV = (long) v;

      if (v == longV)
        getOut().print(longV);
      else
        getOut().print(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints an object
   */
  public final void print(Object v)
  {
    try {
      getOut().print(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a value
   */
  public final void print(Value v)
  {
    v.print(this);
  }

  /**
   * Prints a string
   */
  public final void println()
  {
    try {
      getOut().println();
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a string
   */
  public final void println(String v)
  {
    try {
      getOut().println(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a string
   */
  public final void println(Value v)
  {
    try {
      v.print(this);
      getOut().println();
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints and object.
   */
  public final void println(Object v)
  {
    try {
      getOut().println(v);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Prints a byte buffer.
   */
  public final void write(byte []buffer, int offset, int length)
  {
    try {
      getOut().write(buffer, offset, length);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Returns the current output buffer.
   */
  public OutputBuffer getOutputBuffer()
  {
    return _outputBuffer;
  }

  /**
   * Returns the writer.
   */
  public void pushOutputBuffer(Callable callback, int chunkSize, boolean erase)
  {
    if (_outputBuffer == null) {
      _outputBuffer =
        new OutputBuffer(_outputBuffer, this, callback, chunkSize, erase);
    }
    else
      _outputBuffer =
        new OutputBuffer(_outputBuffer, this, callback, chunkSize, erase);

    _out = _outputBuffer.getOut();
  }

  /**
   * Pops the output buffer
   */
  public boolean popOutputBuffer()
  {
    OutputBuffer outputBuffer = _outputBuffer;

    if (outputBuffer == null)
      return false;

    outputBuffer.close();

    _outputBuffer = outputBuffer.getNext();

    if (_outputBuffer != null)
      _out = _outputBuffer.getOut();
    else {
      _out = _originalOut;
    }

    return true;
  }

  /**
   * Returns the current directory.
   */
  public Path getPwd()
  {
    return _pwd;
  }

  public String getShellPwd()
  {
    if (_pwd instanceof MemoryPath)
      return System.getProperty("user.dir");
    else
      return _pwd.getNativePath();
  }

  /**
   * Returns the current directory.
   */
  public Path getWorkDir()
  {
    return _quercus.getWorkDir();
  }

  /**
   * Sets the current directory.
   */
  public void setPwd(Path path)
  {
    _pwd = path;

    _lookupCache.clear();
  }

  /**
   * Returns the initial directory.
   */
  public Path getSelfPath()
  {
    return _selfPath;
  }

  /**
   * Returns the initial directory.
   */
  public Path getSelfDirectory()
  {
    return _selfDirectory;
  }

  /**
   * Sets the initial directory.
   */
  public void setSelfPath(Path path)
  {
    _selfPath = path;
    _selfDirectory = _selfPath.getParent();
  }

  /**
   * Returns the upload directory.
   */
  public Path getUploadDirectory()
  {
    if (_uploadPath == null) {
      String realPath = getIniString("upload_tmp_dir");

      if (realPath != null) {
        _uploadPath = _quercus.getPwd().lookup(realPath);
      }
      else if (getRequest() != null) {
        _uploadPath = _quercus.getWebInfDir().lookup("upload");
      }
      else {
        realPath = WorkDir.getTmpWorkDir().lookup("upload").getNativePath();

        _uploadPath = _quercus.getPwd().lookup(realPath);
      }

      try {
        if (! _uploadPath.isDirectory())
          _uploadPath.mkdirs();
      }
      catch (IOException e) {
        log.log(Level.FINE, e.toString(), e);
      }

      _uploadPath = _uploadPath.createRoot();
    }

    return _uploadPath;
  }

  /**
   * Returns the real path.
   */
  public String getRealPath(String path)
  {
    String realPath;

    if (getRequest() != null)
      realPath = getRequest().getRealPath(path);
    else
      realPath = getSelfDirectory().lookup(path).getNativePath();

    return realPath;
  }

  /**
   * Returns the temp directory (used by tmpfile()).
   */
  public Path getTempDirectory()
  {
    String realPath;

    if (_tmpPath == null) {
      if (getRequest() != null) {
        realPath = getRequest().getRealPath("/WEB-INF/tmp");
      }
      else {
        realPath = "file:/tmp";
      }

      _tmpPath = getPwd().lookup(realPath);

      try {
        if (! _tmpPath.isDirectory()) {
          _tmpPath.mkdirs();
        }
      }
      catch (IOException e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    return _tmpPath;
  }

  /**
   * Adds an auto-remove path.
   */
  public void addRemovePath(Path path)
  {
    if (_removePaths == null) {
      _removePaths = new ArrayList<Path>();
    }

    _removePaths.add(path);
  }

  /**
   * Returns the request.
   */
  public QuercusHttpServletRequest getRequest()
  {
    return _request;
  }

  /**
   * Returns the most recently modified time of all of the {@link Path}'s that
   * have been used for this Env, or 0 if that cannot be determined.
   */
  /*
  public long getLastModified()
  {
    long lastModified = 0;

    if (_page != null) {
      Path pagePath = _page.getSelfPath(this);

      if (pagePath != null)
        lastModified = pagePath.getLastModified();
    }

    for (Path includePath : _includeSet) {
      long includeLastModified = includePath.getLastModified();

      if (lastModified < includeLastModified)
        lastModified = includeLastModified;
    }

    return lastModified;
  }
  */

  /**
   * Returns the response.
   */
  public QuercusHttpServletResponse getResponse()
  {
    return _response;
  }

  /**
   * Sets the session callback.
   */
  public void setSessionCallback(SessionCallback callback)
  {
    _sessionCallback = callback;
  }

  /**
   * Gets the session callback.
   */
  public SessionCallback getSessionCallback()
  {
    return _sessionCallback;
  }

  /**
   * Returns the session.
   */
  public SessionArrayValue getSession()
  {
    return _session;
  }

  /**
   * Returns the Java Http session.
   */
  public QuercusHttpSession getJavaSession()
  {
    return _javaSession;
  }

  /**
   * Sets the session.
   */
  public void setSession(SessionArrayValue session)
  {
    _session = session;

    StringValue sessionStr;

    if (isUnicodeSemantics()) {
      sessionStr = S_SESSION_U;
    }
    else {
      sessionStr = S_SESSION;
    }

    if (session != null) {
      Value var = getGlobalVar(sessionStr);

      if (! (var instanceof SessionVar)) {
        var = new SessionVar();
        setGlobalValue(sessionStr, var);
      }

      var.set(session);

      setGlobalValue("HTTP_SESSION_VARS", session);

      session.addUse();
    }
    else {
      // php/1k0v
      Value v = getGlobalVar(sessionStr);

      if (v != null)
        v.set(UnsetValue.UNSET);

      v = getGlobalVar("HTTP_SESSION_VARS");

      if (v != null)
        v.set(UnsetValue.UNSET);
    }
  }

  /**
   * Returns a new session id.
   */
  public String generateSessionId()
  {
    String sessionId =
      _quercus.getQuercusSessionManager().createSessionId(this);

    if (_javaSession != null)
      sessionId = _javaSession.getId().substring(0, 3) + sessionId.substring(3);

    return sessionId;
  }

  /**
   * Create the session.
   */
  public SessionArrayValue createSession(String sessionId, boolean create)
  {
    long now = _quercus.getCurrentTime();

    SessionCallback callback = getSessionCallback();

    _javaSession = _request.getSession(true);

    if (create
        && _javaSession.getId().length() >= 3
        && sessionId.length() >= 3) {
      sessionId = _javaSession.getId().substring(0, 3) + sessionId.substring(3);
    }

    SessionArrayValue session = _quercus.loadSession(this, sessionId);

    if (callback != null) {
      StringValue value = callback.read(this, sessionId);

      if (value != null && value.length() != 0) {
        Value unserialize = VariableModule.unserialize(this, value);

        if (unserialize instanceof ArrayValue) {
          ArrayValue arrayValue = (ArrayValue) unserialize;

          session.reset(now);
          session.putAll(arrayValue);
        }
      }
    }

    setSession(session);

    return session;
  }

  /**
   * Destroy the session.
   */
  public void destroySession(String sessionId)
  {
    SessionCallback callback = getSessionCallback();

    if (callback != null) {
      callback.destroy(this, sessionId);
    }
    else {
      _quercus.destroySession(sessionId);
    }

    setSession(null);
  }

  /**
   * Returns the logger used for syslog.
   */
  public Logger getLogger()
  {
    if (_logger == null)
      _logger = Logger.getLogger("quercus.quercus");

    return _logger;
  }

  /**
   * Returns the configuration value of an init var.
   */
  public Value getConfigVar(String name)
  {
    return getIniDefinition(name).getValue(_quercus);
  }

  /**
   * Returns a map of the ini values that have been explicitly set.
   */
  public HashMap<String, Value> getIniMap(boolean create)
  {
    if (_iniMap == null && create)
      _iniMap = new HashMap<String,Value>();

    return _iniMap;
  }

  /**
   * Sets an ini value.
   */
  public StringValue setIni(String name, Value value)
  {
    _iniCount++;

    StringValue oldValue = getIni(name);

    getIniDefinition(name).set(this, value);

    return oldValue;
  }

  /**
   * Sets an ini value.
   */
  public StringValue setIni(String name, String value)
  {
    _iniCount++;

    StringValue oldValue = getIni(name);

    getIniDefinition(name).set(this, value);

    return oldValue;
  }

  /**
   * Returns an ini value.
   */
  public StringValue getIni(String name)
  {
    return getIniDefinition(name).getAsStringValue(this);
  }

  private IniDefinition getIniDefinition(String name)
  {
    return _quercus.getIniDefinitions().get(name);
  }

  /**
   * Returns an ini value.
   */
  public boolean getIniBoolean(String name)
  {
    return getIniDefinition(name).getAsBoolean(this);
  }

  /**
   * Returns an ini value as a long.
   */
  public long getIniLong(String name)
  {
    return getIniDefinition(name).getAsLong(this);
  }

  /**
   * Returns an ini value as a string, null for missing or empty string
   */
  public String getIniString(String name)
  {
    return getIniDefinition(name).getAsString(this);
  }

  /**
   * Returns an ini value.
   */
  public long getIniBytes(String name, long deflt)
  {
    return getIniDefinition(name).getAsLongBytes(this, deflt);
  }

  /**
   * Returns the ByteToChar converter.
   */
  public ByteToChar getByteToChar()
  {
    return ByteToChar.create();
  }

  /**
   * Returns the 'this' value.
   */
  public Value getThis()
  {
    return _this;
  }

  /**
   * Sets the 'this' value, returning the old value.
   */
  public Value setThis(Value value)
  {
    Value oldThis = _this;

    _this = value.toValue();

    return oldThis;
  }

  /**
   * Returns the current closure scope.
   */
  public Closure getClosure()
  {
    return _closure;
  }

  /**
   * Sets the current closure scope and returns the old one.
   */
  public Closure setClosure(Closure closure)
  {
    Closure old = _closure;

    _closure = closure;

    return old;
  }

  /**
   * Gets a value.
   */
  public Value getValue(StringValue name)
  {
    return getValue(name, true, false);
  }

  /**
   * Gets a value.
   */
  public Value getValue(StringValue name,
                        boolean isAutoCreate,
                        boolean isOutputNotice)
  {
    EnvVar var = getEnvVar(name, isAutoCreate, isOutputNotice);

    if (var != null)
      return var.get();
    else
      return NullValue.NULL;
  }

  /**
   * Gets a special value, a special value is used to store and retrieve module
   * specific values in the env using a unique name.
   */
  public Object getSpecialValue(String name)
  {
    return _specialMap.get(name);
  }

  /**
   * Sets a special value, a special value is used to store and retrieve module
   * specific values in the env using a unique name.
   */
  public Object setSpecialValue(String name, Object value)
  {
    _specialMap.put(name, value);

    return value;
  }

  public Value getGlobalValue(String name)
  {
    return getGlobalValue(createString(name));
  }

  /**
   * Gets a global
   */
  public Value getGlobalValue(StringValue name)
  {
    EnvVar var = getGlobalEnvVar(name);

    // XXX: don't allocate?

    return var.get();

    /*
    if (var != null)
      return var.toValue();
    else
      return NullValue.NULL;
    */
  }

  /**
   * Gets a variable
   *
   * @param name the variable name
   * @param var the current value of the variable
   */
  public final Var getVar(StringValue name, Value value)
  {
    if (value != null)
      return (Var) value;

    return getRef(name);
  }

  /**
   * Gets a variable
   *
   * @param name the variable name
   * @param value the current value of the variable
   */
  public final Var getGlobalVar(StringValue name, Value value)
  {
    if (value != null)
      return (Var) value;

    return getGlobalRef(name);
  }

  /**
   * Gets a value.
   */
  public Var getRef(StringValue name)
  {
    EnvVar envVar = getEnvVar(name);

    // XXX: can return null?

    return envVar.getVar();

    /*
    Var var = _map.get(name);

    // required for $$ref where $ref is the name of a superglobal
    if (var == null) {
      // php/0809
      if (Quercus.isSuperGlobal(name))
        return getGlobalRef(name);
    }

    return var;
    */
  }

  /**
   * Gets a value.
   */
  public Var getRef(StringValue name, boolean isAutoCreate)
  {
    EnvVar envVar = getEnvVar(name, isAutoCreate, true);

    if (envVar != null)
      return envVar.getVar();
    else
      return null;
  }

  /**
   * Returns the raw global lookup.
   */
  public EnvVar getGlobalRaw(String name)
  {
    return _globalMap.get(name);
  }

  /**
   * Gets a global value.
   */
  public Var getGlobalRef(StringValue name)
  {
    EnvVar envVar = getGlobalEnvVar(name);

    // XXX: not create?

    return envVar.getVar();
  }

  public final EnvVar getEnvVar(StringValue name)
  {
    return getEnvVar(name, true, true);
  }

  public final EnvVar getLazyEnvVar(StringValue name)
  {
    EnvVar var = getEnvVar(name, false, false);

    if (var == null) {
      var = new LazyEnvVar(name);
    }

    return var;
  }

  /**
   * Gets a variable
   *
   * @param name the variable name
   * @param var the current value of the variable
   */
  public final EnvVar getEnvVar(StringValue name,
                                boolean isAutoCreate,
                                boolean isOutputNotice)
  {
    EnvVar envVar = _map.get(name);

    if (envVar != null)
      return envVar;

    if (_map == _globalMap) {
      return getGlobalEnvVar(name, isAutoCreate, isOutputNotice);
    }

    envVar = getSuperGlobalRef(name, true, false);

    // php/0809
    if (envVar != null) {
      _globalMap.put(name, envVar);
    }
    else {
      // php/0205, php/0206
      // XXX: optimize: don't create a string when the error is just going to be ignored
      if (isOutputNotice) {
        notice(L.l("${0} is an undefined variable", name));
      }

      if (! isAutoCreate) {
        return null;
      }

      envVar = new EnvVarImpl(new Var());
    }

    _map.put(name, envVar);

    return envVar;
  }

  /**
   * Gets a variable
   *
   * @param name the variable name
   */
  public final EnvVar getGlobalEnvVar(StringValue name)
  {
    return getGlobalEnvVar(name, true, false);
  }

  /**
   * Gets a variable
   *
   * @param name the variable name
   * @param isAutoCreate
   */
  public final EnvVar getGlobalEnvVar(StringValue name,
                                      boolean isAutoCreate,
                                      boolean isOutputNotice)
  {
    EnvVar envVar = _globalMap.get(name);

    if (envVar != null) {
      return envVar;
    }

    envVar = getSuperGlobalRef(name, true);

    if (envVar == null) {
      // variables set by the caller, e.g. the servlet

      Value value = _scriptGlobalMap.get(name);

      if (value != null) {
        envVar = new EnvVarImpl(new Var());
        envVar.setRef(value);
      }
    }

    if (envVar == null)
      envVar = getGlobalScriptContextRef(name);

    if (envVar == null) {
      if (isOutputNotice) {
        notice(L.l("${0} is an undefined variable", name));
      }

      if (! isAutoCreate) {
        return null;
      }

      Var var = new Var();
      // var.setGlobal();

      envVar = new EnvVarImpl(var);
    }

    _globalMap.put(name, envVar);

    return envVar;
  }

  /**
   * Pushes a new environment.
   */
  public Map<StringValue,EnvVar> pushEnv(Map<StringValue,EnvVar> map)
  {
    Map<StringValue,EnvVar> oldEnv = _map;

    _map = map;

    return oldEnv;
  }

  /**
   * Restores the old environment.
   */
  public void popEnv(Map<StringValue,EnvVar> oldEnv)
  {
    _map = oldEnv;
  }

  /**
   * Returns the current environment.
   */
  public Map<StringValue,EnvVar> getEnv()
  {
    return _map;
  }

  /**
   * Returns the current environment.
   */
  public Map<StringValue,EnvVar> getGlobalEnv()
  {
    return _globalMap;
  }

  public boolean isGlobalEnv()
  {
    return _map == _globalMap;
  }

  /**
   * Gets a static variable name.
   */
  public final StringValue createStaticName()
  {
    return _quercus.createStaticName();
  }

  /**
   * Gets a static variable
   *
   * @param name the variable name
   */
  public final Var getStaticVar(StringValue name)
  {
    Var var = _staticMap.get(name);

    if (var == null) {
      var = new Var();
      _staticMap.put(name, var);
    }

    return var;
  }

  /**
   * Gets a static variable
   *
   * @param name the variable name
   */
  public final Value getStaticValue(StringValue name)
  {
    Var var = _staticMap.get(name);

    if (var != null) {
      return var.toValue();
    }
    else
      return NullValue.NULL;
  }

  /**
   * Gets a static variable
   *
   * @param name the variable name
   */
  public final Var setStaticRef(StringValue name, Value value)
  {
    if (value.isVar()) {
      Var var = (Var) value;

      _staticMap.put(name, var);

      return var;
    }

    Var var = _staticMap.get(name);

    if (var == null) {
      var = new Var();
      _staticMap.put(name, var);
    }

    var.set(value);

    return var;
  }

  /**
   * Gets a static variable
   *
   * @param name the variable name
   */
  /*
  public final Var getStaticClassVar(Value qThis,
                                     String className,
                                     String name)
  {
    // php/3248
    // php/324a
    // php/324b
    QuercusClass callingClass = getCallingClass(qThis);

    if (callingClass.isA(className))
      className = callingClass.getName();

    StringValue varName = createString(className).append("::").append(name);

    return getStaticVar(varName);
  }
  */

  /**
   * Unsets variable
   *
   * @param name the variable name
   */
  public final Var unsetVar(StringValue name)
  {
    EnvVar envVar = _map.get(name);

    if (envVar != null)
      envVar.setVar(new Var());

    return null;
  }

  /**
   * Gets a variable
   *
   * @param name the variable name
   * @param value the current value of the variable
   */
  public final Var setVar(String name, Value value)
  {
    throw new UnsupportedOperationException();

    /*
    Var var;

    if (value instanceof Var) {
      var = (Var) value;

      if (_map == _globalMap)
        var.setGlobal();
    }
    else
      var = new Var(value.toValue());

    _map.put(name, var);

    return var;
    */
  }

  /**
   * Unsets variable
   *
   * @param name the variable name
   */
  public final Var unsetLocalVar(StringValue name)
  {
    EnvVar envVar = _map.get(name);

    if (envVar != null)
      envVar.setVar(new Var());

    return null;
  }

  /**
   * Unsets variable
   *
   * @param name the variable name
   */
  public final Var unsetGlobalVar(StringValue name)
  {
    EnvVar envVar = _globalMap.get(name);

    if (envVar != null)
      envVar.setVar(new Var());

    return null;
  }

  /**
   * Gets a local
   *
   * @param var the current value of the variable
   */
  public static final Value getLocalVar(Value var)
  {
    if (var == null)
      var = new Var();

    return var;
  }

  /**
   * Gets a local value
   *
   * @param var the current value of the variable
   */
  public static final Value getLocalValue(Value var)
  {
    if (var != null)
      return var;
    else
      return NullValue.NULL;
  }

  /**
   * Gets a local
   *
   * @param var the current value of the variable
   */
  public static final Value setLocalVar(Value var, Value value)
  {
    value = value.toValue();

    if (var instanceof Var) {
      var.set(value);
    }

    return value;
  }

  private EnvVar getSuperGlobalRef(StringValue name, boolean isGlobal)
  {
    return getSuperGlobalRef(name, false, isGlobal);
  }

  /**
   * Returns a superglobal.
   */
  private EnvVar getSuperGlobalRef(StringValue name,
                                   boolean isCheckGlobal,
                                   boolean isGlobal)
  {
    int specialVarId;

    if (isUnicodeSemantics()) {
      specialVarId = SPECIAL_VARS_U.get(name);
    }
    else {
      specialVarId = SPECIAL_VARS.get(name);

    }

    if (isCheckGlobal) {
      if (specialVarId != IntMap.NULL) {
        EnvVar envVar = _globalMap.get(name);

        if (envVar != null)
          return envVar;
      }
    }

    switch (specialVarId) {
      case _ENV: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        ArrayValue array = new ArrayValueImpl(getInputEnvArray());
        envVar.set(array);

        return envVar;
      }

      case ARGV: {
        if (! _quercus.isRegisterArgv()) {
          return null;
        }

        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        ArrayValue array = createArgv();
        envVar.set(array);

        _globalMap.put(name, envVar);

        return envVar;
      }

      case ARGC: {
        if (! _quercus.isRegisterArgv()) {
          return null;
        }

        Var array = getGlobalEnvVar(isUnicodeSemantics() ? S_ARGV_U : S_ARGV).getVar();

        int size = array.getSize();

        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        LongValue value = LongValue.create(size);
        envVar.set(value);

        _globalMap.put(name, envVar);

        return envVar;
      }

      case HTTP_POST_VARS: {
        if (! QuercusContext.INI_REGISTER_LONG_ARRAYS.getAsBoolean(this))
          return null;
        else
          return getGlobalEnvVar(isUnicodeSemantics() ? S_POST_U : S_POST);
      }

      case _POST: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        ArrayValue post = new ArrayValueImpl();

        envVar.set(post);

        if (_variablesOrder.indexOf('P') >= 0
            && _inputPost.getSize() > 0) {
          for (Map.Entry<Value, Value> entry : _inputPost.entrySet()) {
            post.put(entry.getKey(), entry.getValue());
          }
        }

        return envVar;
      }

      case HTTP_POST_FILES: {
        if (! QuercusContext.INI_REGISTER_LONG_ARRAYS.getAsBoolean(this))
          return null;
        else
          return getGlobalEnvVar(isUnicodeSemantics() ? S_FILES_U : S_FILES);
      }

      case _FILES: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        ArrayValue files = new ArrayValueImpl();

        if (_files != null) {
          for (Map.Entry<Value, Value> entry : _files.entrySet()) {
            files.put(entry.getKey(), entry.getValue());
          }
        }

        envVar.set(files);

        return envVar;
      }

      case HTTP_GET_VARS: {
        if (! QuercusContext.INI_REGISTER_LONG_ARRAYS.getAsBoolean(this))
          return null;
        else if (! isGlobal)
          return null;
        else
          return getGlobalEnvVar(isUnicodeSemantics() ? S_GET_U : S_GET);
      }

      case _GET: {
        if (isCheckGlobal) {
          EnvVar e = _globalMap.get(name);

          if (e != null)
            return e;
        }

        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        ArrayValue array;

        if (_variablesOrder.indexOf('G') >= 0) {
          array = new ArrayValueImpl(getInputGetArray());
        }
        else {
          array = new ArrayValueImpl();
        }

        envVar.set(array);

        return envVar;
      }

      case _REQUEST: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        ArrayValue array = new ArrayValueImpl();

        envVar.set(array);

        _globalMap.put(name, envVar);

        if (_request == null)
          return envVar;

        int orderLen = _variablesOrder.length();

        for (int i = 0; i < orderLen; i++) {
          switch (_variablesOrder.charAt(i)) {
            case 'G':
              array.putAll(getInputGetArray());
              break;
            case 'P':
              if (_inputPost.getSize() > 0)
                fillPost(array, _inputPost);
              break;
            case 'C':
              array.putAll(getInputCookieArray());
              break;
          }
        }

        return envVar;
      }

      case HTTP_RAW_POST_DATA: {
        if (! QuercusContext.INI_ALWAYS_POPULATE_RAW_POST_DATA.getAsBoolean(this)) {
          String contentType = getContentType();

          if (contentType == null || ! contentType.startsWith("unknown/type"))
            return null;
        }

        if (_inputData == null)
          return null;

        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        var.set(_inputData);

        return envVar;
      }

      case HTTP_SERVER_VARS: {
        if (! QuercusContext.INI_REGISTER_LONG_ARRAYS.getAsBoolean(this))
          return null;
        else
          return getGlobalEnvVar(isUnicodeSemantics() ? S_SERVER_U : S_SERVER);
      }

      case _SERVER: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        Value serverEnv;

        if (_variablesOrder.indexOf('S') >= 0) {
          serverEnv = getInputServerArray().copy();

          if (_quercus.isRegisterArgv()) {
            ArrayValue argv = createArgv();

            serverEnv.put(createString("argc"),
                          LongValue.create(argv.getSize()));

            serverEnv.put(createString("argv"), argv);
          }
        }
        else {
          serverEnv = new ArrayValueImpl();
        }

        var.set(serverEnv);

        return envVar;
      }

      case _GLOBAL: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        var.set(new GlobalArrayValue(this));

        return envVar;
      }

      case HTTP_COOKIE_VARS:
        if (! QuercusContext.INI_REGISTER_LONG_ARRAYS.getAsBoolean(this))
          return null;
        else
          return getGlobalEnvVar(isUnicodeSemantics() ? S_COOKIE_U : S_COOKIE);

      case _COOKIE: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        if (_variablesOrder.indexOf('C') >= 0)
          var.set(getCookies());
        else
          var.set(new ArrayValueImpl());

        return envVar;
      }

      case _SESSION: {
        EnvVar envVar = _globalMap.get("_SESSION");

        if (envVar == null) {
          Var var = new SessionVar();
          envVar = new EnvVarImpl(var);

          _globalMap.put(name, envVar);
        }

        return envVar;
      }

      case PHP_SELF: {
        Var var = new Var();
        EnvVar envVar = new EnvVarImpl(var);

        _globalMap.put(name, envVar);

        var.set(getGlobalVar("_SERVER").get(isUnicodeSemantics() ? PHP_SELF_STRING_U : PHP_SELF_STRING));

        return envVar;
      }

      default:
        return null;
    }
  }

  protected ArrayValue createArgv() {
    ArrayValue array = new ArrayValueImpl();

    String query = getQueryString();

    if (query == null) {
      return array;
    }

    int i = 0;
    int j = 0;
    while ((j = query.indexOf('+', i)) >= 0) {
      String sub = query.substring(i, j);

      array.put(sub);

      i = j + 1;
    }

    if (i < query.length())
      array.put(query.substring(i));

    return array;
  }

  protected String getQueryString()
  {
    if (_request != null)
      return _request.getQueryString();
    else
      return null;
  }

  protected String getContentType()
  {
    if (_request != null)
      return _request.getContentType();
    else
      return null;
  }

  protected ArrayValue getCookies()
  {
    ArrayValue array = new ArrayValueImpl();

    if (_request == null) {
      return array;
    }

    boolean isMagicQuotes = getIniBoolean("magic_quotes_gpc");

    QuercusCookie []cookies = _request.getCookies();
    if (cookies != null) {
      for (int i = 0; i < cookies.length; i++) {
        QuercusCookie cookie = cookies[i];

        String value = decodeValue(cookie.getValue());

        StringValue valueAsValue = createString(value);

        if (isMagicQuotes) // php/0876
          valueAsValue = StringModule.addslashes(valueAsValue);

        array.append(createString(cookie.getName()), valueAsValue);
      }
    }

    return array;
  }

  public void setArgv(String []args)
  {
    if (_quercus.isRegisterArgv()) {
      ArrayValue argv = new ArrayValueImpl();

      for (String arg : args) {
        argv.put(arg);
      }

      Value serverEnv = getGlobalValue("_SERVER");

      serverEnv.put(createString("argc"),
                    LongValue.create(args.length));

      serverEnv.put(createString("argv"), argv);
    }
  }

  /**
   * Gets a value.
   */
  protected EnvVar getGlobalSpecialRef(StringValue name)
  {
    if (QuercusContext.isSuperGlobal(name))
      return _globalMap.get(name);
    else
      return null;
  }

  protected EnvVar getGlobalScriptContextRef(StringValue name)
  {
    if (_scriptContext == null)
      return null;

    EnvVar envVar = _globalMap.get(name);

    if (envVar != null)
      return envVar;

    Object value = _scriptContext.getAttribute(name.toString());

    if (value == null) {
      Bindings bindings
        = _scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);

      if (bindings != null)
        value = bindings.get(name.toString());
    }

    if (value == null) {
      Bindings bindings
      = _scriptContext.getBindings(ScriptContext.GLOBAL_SCOPE);

      if (bindings != null)
        value = bindings.get(name.toString());
    }

    if (value != null) {
      envVar = new EnvVarImpl(new Var());

      _globalMap.put(name, envVar);

      envVar.set(wrapJava(value));
    }

    return envVar;
  }

  protected static String decodeValue(String s)
  {
    int len = s.length();
    StringBuilder sb = new StringBuilder();

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

      if (ch == '%' && i + 2 < len) {
        int d1 = s.charAt(i + 1);
        int d2 = s.charAt(i + 2);

        int v = 0;

        if ('0' <= d1 && d1 <= '9')
          v = 16 * (d1 - '0');
        else if ('a' <= d1 && d1 <= 'f')
          v = 16 * (d1 - 'a' + 10);
        else if ('A' <= d1 && d1 <= 'F')
          v = 16 * (d1 - 'A' + 10);
        else {
          sb.append('%');
          continue;
        }

        if ('0' <= d2 && d2 <= '9')
          v += (d2 - '0');
        else if ('a' <= d2 && d2 <= 'f')
          v += (d2 - 'a' + 10);
        else if ('A' <= d2 && d2 <= 'F')
          v += (d2 - 'A' + 10);
        else {
          sb.append('%');
          continue;
        }

        i += 2;
        sb.append((char) v);
      }
      else if (ch == '+')
        sb.append(' ');
      else
        sb.append(ch);
    }

    return sb.toString();
  }

  public Var getVar(String name)
  {
    return getVar(createString(name));
  }

  /**
   * Gets a value.
   */
  public Var getVar(StringValue name,
                    boolean isAutoCreate,
                    boolean isOutputNotice)
  {
    EnvVar envVar = getEnvVar(name, isAutoCreate, isOutputNotice);

    if (envVar != null) {
      return envVar.getVar();
    }
    else {
      return null;
    }
  }

  /**
   * Gets a value.
   */
  public Var getVar(StringValue name)
  {
    EnvVar envVar = getEnvVar(name, true, false);

    return envVar.getVar();
  }

  /**
   * Gets a value.
   */
  public Var getGlobalVar(String name)
  {
    EnvVar envVar = getGlobalEnvVar(createString(name));

    return envVar.getVar();
  }

  /**
   * Gets a value.
   */
  public Var getGlobalVar(StringValue name)
  {
    EnvVar envVar = getGlobalEnvVar(name);

    return envVar.getVar();
  }

  public void setValue(String name, Value value)
  {
    // XXX: update to Quercus
    setValue(createString(name), value);
  }
  /**
   * Sets a value. value must not be a Var.
   */
  public Value setValue(StringValue name, Value value)
  {
    EnvVar envVar = getEnvVar(name, true, false);

    envVar.set(value);

    return value;
  }

  /**
   * Sets a variable.
   */
  public Var setVar(StringValue name, Var var)
  {
    EnvVar envVar = getEnvVar(name, true, false);

    envVar.setVar(var);

    return var;
  }

  /**
   * Sets a value.
   */
  public Var setRef(StringValue name, Value value)
  {
    EnvVar envVar = getEnvVar(name, true, false);

    return envVar.setRef(value);
  }

  /**
   * Sets a value.
   */
  /*
  public static Var toRef(Value value)
  {
    // php/3243

    if (value instanceof Var)
      return (Var) value;
    else
      return new Var(value);
  }
  */

  /**
   * Convert to a reference argument.  It's a PHP error to convert anything
   * but a var or null to a ref arg.
   */
  /*
  public Value toRefArgument(Value value)
  {
    // php/33lg

    if (value instanceof Var)
      return (Var) value;
    else if (value instanceof NullValue)
      return NullValue.NULL;
    else {
      warning(L.l("'{0}' is an invalid reference, "
      + "because only variables may be passed by reference.",
                  value));

      return NullValue.NULL;
    }
  }
  */

  /**
   * External calls to set a global value.
   */
  public Value setGlobalValue(String name, Value value)
  {
    return setGlobalValue(createString(name), value);
  }

  /**
   * External calls to set a global value.
   */
  public Value setGlobalValue(StringValue name, Value value)
  {
    EnvVar envVar = getGlobalEnvVar(name);

    envVar.setRef(value);

    return value;
  }

  /**
   * Sets the calling function expression.
   */
  public void pushCall(Expr call, Value obj, Value []args)
  {
    if (_callStack == null) {
      _callStack = new Expr[256];
      _callThisStack = new Value[256];
      _callArgStack = new Value[256][];
    }

    if (_callStack.length <= _callStackTop) {
      Expr []newStack = new Expr[2 * _callStack.length];
      System.arraycopy(_callStack, 0, newStack, 0, _callStack.length);
      _callStack = newStack;

      Value []newThisStack = new Value[2 * _callThisStack.length];
      System.arraycopy(_callThisStack,
                       0, newThisStack,
                       0, _callThisStack.length);

      _callThisStack = newThisStack;

      Value [][]newArgStack = new Value[2 * _callArgStack.length][];
      System.arraycopy(_callArgStack,
                       0, newArgStack,
                       0, _callArgStack.length);

      _callArgStack = newArgStack;
    }

    _callStack[_callStackTop] = call;
    _callThisStack[_callStackTop] = obj;
    _callArgStack[_callStackTop] = args;

    _callStackTop++;
  }

  /**
   * Pops the top call.
   */
  public Expr popCall()
  {
    if (_callStack == null)
      throw new IllegalStateException();

    return _callStack[--_callStackTop];
  }

  /**
   * Returns the stack depth.
   */
  public int getCallDepth()
  {
    return _callStackTop;
  }

  /**
   * Peeks at the the top call.
   */
  public Expr peekCall(int depth)
  {
    if (_callStackTop - depth > 0)
      return _callStack[_callStackTop - depth - 1];
    else
      return null;
  }

  /**
   * Peeks at the "this" top call.
   */
  public Value peekCallThis(int depth)
  {
    if (_callStackTop - depth > 0)
      return _callThisStack[_callStackTop - depth - 1];
    else
      return null;
  }

  /**
   * Peeks at the the top call.
   */
  public Value []peekArgs(int depth)
  {
    if (_callStackTop - depth > 0)
      return _callArgStack[_callStackTop - depth - 1];
    else
      return null;
  }

  //
  // allocations
  //

  /**
   * Allocate the free regexp
   */
  public RegexpState allocateRegexpState()
  {
    RegexpState state = _freeRegexpState;
    _freeRegexpState = null;

    return state;
  }

  /**
   * Free the free regexp
   */
  public void freeRegexpState(RegexpState state)
  {
    _freeRegexpState = state;
  }

  //
  // profiling
  //

  public void pushProfile(int id)
  {
  }

  public void popProfile(long nanos)
  {
  }

  /*
   * Returns true if <code>name</code> doesn't already exist on the
   * field __get() stack.
   */
  public boolean pushFieldGet(Env.OVERLOADING_TYPES type, String className, StringValue fieldName)
  {
    FieldGetEntry entry = new FieldGetEntry(className, fieldName);

    LinkedList<FieldGetEntry> list = null;
    switch (type) {
    case FIELDGET:
      list = _fieldGetList;
      break;
    case FIELDSET:
      list = _fieldSetList;
      break;
    case ISSET:
      list = _issetList;
      break;
    case UNSET:
      list = _unsetList;
      break;
    case INVALID_FIRST:
    case INVALID_LAST:
      // defensive programming according to &quot;Code Complete 2nd Edition, MS Press&quot;
      throw new IllegalStateException("IllegalState: pushFieldGet with FIRST/LAST Element");
    }

    if (list == null) {
      return false;
    }

    if (list.contains(entry)) {
      return false;
    }
    else {
      list.push(entry);

      return true;
    }
  }

  public void popFieldGet(Env.OVERLOADING_TYPES type)
  {
    switch (type) {
    case FIELDGET:
      _fieldGetList.pop();
      break;
    case FIELDSET:
      _fieldSetList.pop();
      break;
    case ISSET:
      _issetList.pop();
      break;
    case UNSET:
      _unsetList.pop();
      break;
    case INVALID_FIRST:
    case INVALID_LAST:
      throw new IllegalStateException("IllegalState: popFieldGet with FIRST/LAST Element");
    }
  }

  public QuercusClass getCallingClass()
  {
    return _callingClass;
  }

  public Value getCallingClassName()
  {
    QuercusClass qClass = _callingClass;

    if (qClass != null)
      return createString(qClass.getName());
    else {
      warning(L.l("get_called_class() must be called from a class-context."));

      return NullValue.NULL;
    }
  }

  /*
   * Returns the calling class.
   */
  public QuercusClass getCallingClass(Value qThis)
  {
    QuercusClass cls = qThis.getQuercusClass();

    if (cls == null)
      cls = _callingClass;

    return cls;
  }

  /*
   * Sets the calling class.
   */
  public QuercusClass setCallingClass(QuercusClass cls)
  {
    QuercusClass oldCallingClass = _callingClass;

    _callingClass = cls;

    return oldCallingClass;
  }

  public String getStackTraceAsString()
  {
    return getStackTraceAsString(getLocation());
  }

  public String getStackTraceAsString(Location loc)
  {
    ArrayValue value = ErrorModule.debug_backtrace(this, 0, 0);

    return getStackTraceAsString(value, loc);
  }

  public String getStackTraceAsString(Throwable e, Location loc)
  {
    ArrayValue value = ErrorModule.debug_backtrace_exception(this, e, 0);

    return getStackTraceAsString(value, loc);
  }

  public String getStackTraceAsString(ArrayValue value, Location location)
  {
    StringBuilder sb = new StringBuilder();

    for (Value item : value.values()) {
      String function = item.get(createString("function")).toJavaString();
      String file = item.get(createString("file")).toJavaString();
      int line = item.get(createString("line")).toInt();

      if (function == null || "".equals(function))
        continue;

      sb.append("\n  at ");
      sb.append(function);

      if (file != null && ! "".equals(file)) {
        sb.append(" (" + file + ":" + line + ")");
      }
    }

    if (sb.length() == 0 && location != null) {
      return "\n  " + String.valueOf(location);
    }

    return sb.toString();
  }

  public ArrayList<String> getStackTrace()
  {
    ArrayList<String> trace = new ArrayList<String>();

    for (int i = _callStackTop - 1; i >= 0; i--) {
      String entry;
      Location location = _callStack[i].getLocation();
      String loc;

      if (location != null && location.getFileName() != null) {
        loc = (" (at " + location.getFileName()
               + ":" + location.getLineNumber() + ")");
      }
      else
        loc = "";

      if (_callThisStack[i] != null
          && ! "".equals(_callThisStack[i].toString())) {
        entry = _callThisStack[i] + "." + _callStack[i].toString() + loc;
      }
      else
        entry = _callStack[i].toString() + loc;

      trace.add(entry);
    }

    return trace;
  }

  /**
   * Pushes a new environment.
   */
  public final Value []setFunctionArgs(Value []args)
  {
    Value []oldArgs = _functionArgs;

    Value []newArgs = new Value[args.length];

    for (int i = 0; args != null && i < args.length; i++) {
      // php/3715, 3768
      newArgs[i] = args[i].toValue().copySaveFunArg();
    }

    _functionArgs = newArgs;

    return oldArgs;
  }

  /**
   * Pushes a new environment.
   */
  public final Value []setFunctionArgsNoCopy(Value []args)
  {
    Value []oldArgs = _functionArgs;

    for (int i = 0; args != null && i < args.length; i++)
      args[i] = args[i].toValue();

    _functionArgs = args;

    return oldArgs;
  }

  /**
   * Pushes a new environment.
   */
  public final void restoreFunctionArgs(Value []args)
  {
    _functionArgs = args;
  }

  /**
   * Returns the function args.
   */
  public final Value []getFunctionArgs()
  {
    return _functionArgs;
  }

  /**
   * Removes a specialValue
   */
  public Object removeSpecialValue(String name)
  {
    return _specialMap.remove(name);
  }

  /**
   * Returns a constant.
   */
  public Value getConstant(String name)
  {
    return getConstant(name, true);
  }

  /**
   * Returns a constant.
   */
  public Value getConstant(String name, boolean isAutoCreateString)
  {
    Value value = getConstantImpl(name);

    if (value != null)
      return value;

    int ns = name.lastIndexOf('\\');

    if (ns >= 0) {
      name = name.substring(ns + 1);
      value = getConstantImpl(name);

      if (value != null)
        return value;
    }

    /* XXX:
       notice(L.l("Converting undefined constant '{0}' to string.",
       name));
    */

    if (isAutoCreateString)
      return createString(name);
    else
      return null;
  }

  /**
   * Returns true if the constant is defined.
   */
  public boolean isDefined(String name)
  {
    return getConstantImpl(name) != null;
  }

  /**
   * Returns a constant.
   */
  private Value getConstantImpl(String name)
  {
    int id = _quercus.getConstantId(name);

    if (id < _const.length) {
      Value value = _const[id];

      if (value != null)
        return value;
    }

    id = _quercus.getConstantLowerId(name);

    if (id > 0 && id < _const.length) {
      Value value = _const[id];

      if (value != null)
        return value;
    }

    /*
    id = _quercus.getConstantLower(id);

    if (id > 0 && id < _const.length) {
      Value value = _const[id];

      if (value != null)
        return value;
    }
    */

    return null;

    /*
    value = _quercus.getConstant(name);
    if (value != null)
      return value;

    if (_lowerConstMap != null) {
      value = _lowerConstMap.get(name.toLowerCase(Locale.ENGLISH));

      if (value != null)
        return value;
    }

    return null;
    */
  }

  /**
   * Returns a constant.
   */
  public Value getConstant(int id)
  {
    if (_const.length <= id)
      return _quercus.getConstantName(id);

    Value value = _const[id];

    if (value != null)
      return value;

    int lowerId = _quercus.getConstantLower(id);

    value = _const[lowerId];
    if (value != null) {
      _const[id] = value;

      return value;
    }

    Value nameValue = _quercus.getConstantName(id);

    String name = nameValue.toString();

    int ns = name.lastIndexOf('\\');
    if (ns >= 0) {
      name = name.substring(ns + 1);

      value = getConstantImpl(name);

      if (value != null) {
        _const[id] = value;

        return value;
      }
    }

    return nameValue;

    /*
    value = _quercus.getConstant(name);
    if (value != null)
      return value;

    if (_lowerConstMap != null) {
      value = _lowerConstMap.get(name.toLowerCase(Locale.ENGLISH));

      if (value != null)
        return value;
    }

    return null;
    */
  }

  /**
   * Removes a constant.
   */
  public Value removeConstant(String name)
  {
    int id = _quercus.getConstantId(name);

    Value value = _const[id];

    _const[id] = null;

    return value;
  }

  /**
   * Sets a constant.
   */
  public Value addConstant(String name,
                           Value value,
                           boolean isCaseInsensitive)
  {
    int id;

    if (isCaseInsensitive)
      id = _quercus.addLowerConstantId(createString(name));
    else
      id = _quercus.getConstantId(name);

    return addConstant(id, value, isCaseInsensitive);
  }

  /**
   * Sets a constant.
   */
  public Value addConstant(StringValue name,
                           Value value,
                           boolean isCaseInsensitive)
  {
    int id;

    if (isCaseInsensitive)
      id = _quercus.addLowerConstantId(name);
    else
      id = _quercus.getConstantId(name);

    return addConstant(id, value, isCaseInsensitive);
  }

  /**
   * Sets a constant.
   */
  public Value addConstant(int id,
                           Value value,
                           boolean isCaseInsensitive)
  {
    if (_const.length <= id) {
      Value []newConst = new Value[id + 256];
      System.arraycopy(_const, 0, newConst, 0, _const.length);

      _const = newConst;
    }

    if (_const[id] != null) {
      return notice(L.l("cannot redefine constant {0}",
                        _quercus.getConstantName(id)));
    }

    _const[id] = value;

    if (isCaseInsensitive) {
      int lowerId = _quercus.getConstantLower(id);

      if (_const.length <= lowerId) {
        Value []newConst = new Value[lowerId + 256];
        System.arraycopy(_const, 0, newConst, 0, _const.length);

        _const = newConst;
      }

      _const[lowerId] = value;
    }

    return value;
  }

  /**
   * Returns an array of the defined functions.
   */
  public ArrayValue getDefinedConstants()
  {
    ArrayValue result = new ArrayValueImpl();

    for (int i = 0; i < _const.length; i++) {
      if (_const[i] != null) {
        result.append(_quercus.getConstantName(i), _const[i]);
      }
    }

    return result;
  }

  /**
   * Returns true if an extension is loaded.
   */
  public boolean isExtensionLoaded(String name)
  {
    return getQuercus().isExtensionLoaded(name);
  }

  /**
   * Returns true if an extension is loaded.
   */
  public HashSet<String> getLoadedExtensions()
  {
    return getQuercus().getLoadedExtensions();
  }

  /**
   * Returns true if an extension is loaded.
   */
  public Value getExtensionFuncs(String name)
  {
    return getQuercus().getExtensionFuncs(name);
  }

  /**
   * Returns the default stream resource.
   */
  public StreamContextResource getDefaultStreamContext()
  {
    if (_defaultStreamContext == null)
      _defaultStreamContext = new StreamContextResource();

    return _defaultStreamContext;
  }

  public HashMap<StringValue,ProtocolWrapper> getStreamWrappers()
  {
    if (_wrappedStreamMap == null) {
      _wrappedStreamMap = new HashMap<StringValue,ProtocolWrapper>();

      ProtocolWrapper zlibProtocolWrapper = new ZlibProtocolWrapper();
      _wrappedStreamMap.put(createString("compress.zlib"), zlibProtocolWrapper);
      _wrappedStreamMap.put(createString("zlib"), zlibProtocolWrapper);
      _wrappedStreamMap.put(createString("php"), new PhpProtocolWrapper());
    }

    return _wrappedStreamMap;
  }

  public void addStreamWrapper(StringValue name, ProtocolWrapper wrapper)
  {
    HashMap<StringValue,ProtocolWrapper> streamMap = getStreamWrappers();

    streamMap.put(name, wrapper);
  }

  public ProtocolWrapper getStreamWrapper(StringValue name)
  {
    HashMap<StringValue,ProtocolWrapper> wrapperMap = getStreamWrappers();

    return wrapperMap.get(name);
  }

  public boolean unregisterStreamWrapper(StringValue name)
  {
    HashMap<StringValue,ProtocolWrapper> wrapperMap = getStreamWrappers();

    ProtocolWrapper wrapper = wrapperMap.remove(name);

    if (wrapper == null) {
      return false;
    }

    if (_unregisteredWrappedStreamMap == null) {
      _unregisteredWrappedStreamMap = new HashMap<StringValue,ProtocolWrapper>();
    }

    _unregisteredWrappedStreamMap.put(name, wrapper);
    return true;
  }

  public boolean restoreStreamWrapper(StringValue name)
  {
    if (_unregisteredWrappedStreamMap == null) {
      return false;
    }

    ProtocolWrapper wrapper = _unregisteredWrappedStreamMap.remove(name);

    if (wrapper == null) {
      return false;
    }

    HashMap<StringValue,ProtocolWrapper> wrapperMap = getStreamWrappers();

    wrapperMap.put(name, wrapper);

    return true;
  }

  //
  // function handling
  //

  public ArrayValue getDefinedFunctions()
  {
    ArrayValueImpl funs = new ArrayValueImpl();
    ArrayValueImpl system = new ArrayValueImpl();
    ArrayValueImpl user = new ArrayValueImpl();

    AbstractFunction []systemFuns = _quercus.getFunctionMap();
    AbstractFunction []envFuns = _fun;

    for (int i = 0; i < envFuns.length; i++) {
      if (i < systemFuns.length
          && systemFuns[i] != null
          && ! (systemFuns[i] instanceof UndefinedFunction)) {
        system.append(createString(systemFuns[i].getName()));
      }
      else if (envFuns[i] != null
               && ! (envFuns[i] instanceof UndefinedFunction)) {
        user.append(createString(envFuns[i].getName()));
      }
    }

    funs.append(createString("internal"), system);
    funs.append(createString("user"), user);

    return funs;
  }

  /**
   * Returns the function with a given name.
   *
   * Compiled mode normally uses the _fun array directly, so this call
   * is rare.
   */
  public int findFunctionId(StringValue name)
  {
    return _quercus.findFunctionId(name);
  }

  /**
   * Returns the function with a given name.
   *
   * Compiled mode normally uses the _fun array directly, so this call
   * is rare.
   */
  public AbstractFunction findFunction(StringValue name)
  {
    int id = _quercus.findFunctionId(name);

    if (id >= 0) {
      if (id < _fun.length && ! (_fun[id] instanceof UndefinedFunction)) {
        return _fun[id];
      }
      else {
        return null;
      }
    }

    /*
    AbstractFunction fun = _quercus.findFunctionImpl(name);

    if (fun != null)
      return fun;

    if (isStrict())
      return null;

    name = name.toLowerCase(Locale.ENGLISH);

    id = _quercus.findFunctionId(name);

    if (id >= 0) {
      if (id < _fun.length && ! (_fun[id] instanceof UndefinedFunction))
        return _fun[id];
      else
        return null;
    }

    fun = _quercus.findLowerFunctionImpl(name);

    if (fun != null)
      return fun;
    */

    if (_anonymousFunMap != null)
      return _anonymousFunMap.get(name);
    else
      return null;
  }

  public AbstractFunction getFunction(int id)
  {
    return _fun[id];
  }

  public AbstractFunction getFunction(StringValue name)
  {
    AbstractFunction fun = findFunction(name);

    if (fun != null) {
      return fun;
    }
    else {
      throw createErrorException(L.l("'{0}' is an unknown function.", name));
    }
  }

  public void updateFunction(int id, AbstractFunction fun)
  {
    if (_fun.length <= id) {
      AbstractFunction []oldFun = _fun;

      _fun = new AbstractFunction[id + 256];
      System.arraycopy(oldFun, 0, _fun, 0, oldFun.length);
    }

    if (_fun[id] == null) {
      _fun[id] = fun;
    }
  }

  /*
  public int getFunctionId(String name)
  {
    int id = _quercus.getFunctionId(name);

    if (_fun.length <= id) {
      AbstractFunction []oldFun = _fun;

      _fun = new AbstractFunction[id + 256];
      System.arraycopy(oldFun, 0, _fun, 0, oldFun.length);
    }

    AbstractFunction []defFuns = _quercus.getFunctionMap();

    if (_fun[id] == null)
      _fun[id] = defFuns[id];

    return id;
  }

  public int getFunctionIdCount()
  {
    return _quercus.getFunctionIdCount();
  }
  */

  /**
   * Finds the java reflection method for the function with the given name.
   *
   * @param name the method name
   * @return the found method or null if no method found.
   */
  public AbstractFunction getFunction(Value name)
  {
    name = name.toValue();

    if (name instanceof CallbackFunction) {
      return ((CallbackFunction) name).getFunction(this);
    }

    return getFunction(name.toStringValue());
  }

  /*
  public DefinitionState getDefinitionState()
  {
    return _defState;
  }
  */

  public Value addFunction(String name, AbstractFunction fun)
  {
    return addFunction(createString(name), fun);
  }

  public Value addFunction(StringValue name, AbstractFunction fun)
  {
    AbstractFunction staticFun
      = _quercus.findLowerFunctionImpl(name.toLowerCase(Locale.ENGLISH));

    if (staticFun != null)
      throw new QuercusException(L.l("can't redefine function {0}", name));

    int id = _quercus.getFunctionId(name);

    // XXX: anonymous/generated functions(?), e.g. like foo2431

    if (_fun.length <= id) {
      AbstractFunction []funMap = new AbstractFunction[id + 256];
      System.arraycopy(_fun, 0, funMap, 0, _fun.length);
      _fun = funMap;
    }

    if (_fun[id] != null && ! (_fun[id] instanceof UndefinedFunction))
      throw new QuercusException(L.l("can't redefine function {0}", name));

    _fun[id] = fun;

    return BooleanValue.TRUE;
  }

  public AbstractFunction createAnonymousFunction(String args, String code)
    throws IOException
  {
    if (_anonymousFunMap == null) {
      _anonymousFunMap = new HashMap<StringValue, AbstractFunction>();
    }

    StringValue sb = createStringBuilder();

    // PHP naming style for anonymous functions
    sb.append("\u0000lambda_");
    sb.append(_anonymousFunMap.size() + 1);

    StringValue name = sb;

    if (args == null) {
      args = "";
    }

    if (code == null) {
      code = "";
    }

    AbstractFunction fun
      = getQuercus().parseFunction(name.toString(), args, code);

    _anonymousFunMap.put(name, fun);
    return fun;
  }

  /**
   * Adds a function from a compiled include
   *
   * @param name the function name, must be an intern() string
   * @param lowerName the function name, must be an intern() string
   */
  public Value addFunctionFromPage(String name, String lowerName,
                                   AbstractFunction fun)
  {
    // XXX: skip the old function check since the include for compiled
    // pages is already verified.  Might have a switch here?
    /*
    AbstractFunction oldFun = _lowerFunMap.get(lowerName);

    if (oldFun == null)
      oldFun = _quercus.findLowerFunctionImpl(lowerName);

    if (oldFun != null) {
      throw new QuercusException(L.l("can't redefine function {0}", name));
    }

    _funMap.put(name, fun);

    if (! isStrict())
      _lowerFunMap.put(lowerName, fun);
    */

    return BooleanValue.TRUE;
  }

  //
  // evaluation
  //

  /**
   * Compiles and evalutes the given code
   *
   * @param code the code to evalute
   * @return the result
   */
  public Value evalCode(StringValue code)
    throws IOException
  {
    if (log.isLoggable(Level.FINER)) {
      log.finer(code.toString());
    }

    QuercusContext quercus = getQuercus();

    QuercusProgram program = quercus.parseEvalExpr(code);

    Value value = program.execute(this);

    if (value == null)
      return NullValue.NULL;
    else
      return value;
  }

  /**
   * Evaluates the top-level code and prepend and append code.
   */
  public void execute()
    throws IOException
  {
    StringValue prepend
      = _quercus.getIniValue("auto_prepend_file").toStringValue(this);

    if (prepend.length() > 0) {
      Path prependPath = lookup(prepend);

      if (prependPath == null)
        error(L.l("auto_prepend_file '{0}' not found.", prepend));
      else {
        QuercusPage prependPage = _quercus.parse(prependPath);
        prependPage.executeTop(this);
      }
    }

    executeTop();

    StringValue append
      = _quercus.getIniValue("auto_append_file").toStringValue(this);

    if (append.length() > 0) {
      Path appendPath = lookup(append);

      if (appendPath == null)
        error(L.l("auto_append_file '{0}' not found.", append));
      else {
        QuercusPage appendPage = getQuercus().parse(appendPath);
        appendPage.executeTop(this);
      }
    }
  }

  /**
   * Evaluates the top-level code
   *
   * @return the result
   */
  public Value executeTop()
  {
    try {
      return executePageTop(_page);
    } catch (QuercusLanguageException e) {
      log.log(Level.FINER, e.toString(), e);

      if (getExceptionHandler() != null) {
        try {
          getExceptionHandler().call(this, e.toException(this));
        }
        catch (QuercusLanguageException e2) {
          uncaughtExceptionError(e2);
        }
      }
      else {
        uncaughtExceptionError(e);
      }

      return NullValue.NULL;
    }
  }

  /*
   * Throws an error for this uncaught exception.
   */
  private void uncaughtExceptionError(QuercusLanguageException e)
  {
    Location location = e.getLocation(this);
    String type = e.getValue().getClassName();
    String message = e.getMessage(this);

    error(L.l("Uncaught exception of type '{0}' with message '{1}'", type, message),
          location);
  }

  /**
   * Executes the given page
   */
  protected Value executePage(QuercusPage page)
  {
    if (log.isLoggable(Level.FINEST))
      log.finest(this + " executePage " + page);

    if (page.getCompiledPage() != null)
      return page.getCompiledPage().execute(this);
    else
      return page.execute(this);
  }

  /**
   * Executes the given page
   */
  protected Value executePageTop(QuercusPage page)
  {
    if (page.getCompiledPage() != null)
      return page.getCompiledPage().execute(this);
    else
      return page.execute(this);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @return the function value
   */
  public Value call(StringValue name)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.call(this);
  }

  //
  // function calls (obsolete?)
  //

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @return the function value
   */
  public Value call(StringValue name, Value a0)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.call(this, a0);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @return the function value
   */
  public Value call(StringValue name, Value a0, Value a1)
  {
    return getFunction(name).call(this, a0, a1);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @param a2 the third argument
   * @return the function value
   */
  public Value call(StringValue name, Value a0, Value a1, Value a2)
  {
    return getFunction(name).call(this, a0, a1, a2);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @param a2 the third argument
   * @param a3 the fourth argument
   * @return the function value
   */
  public Value call(StringValue name, Value a0, Value a1, Value a2, Value a3)
  {
    return getFunction(name).call(this, a0, a1, a2, a3);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @param a2 the third argument
   * @param a3 the fourth argument
   * @param a4 the fifth argument
   * @return the function value
   */
  public Value call(StringValue name, Value a0, Value a1,
                    Value a2, Value a3, Value a4)
  {
    return getFunction(name).call(this, a0, a1, a2, a3, a4);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param args the arguments
   * @return the function value
   */
  public Value call(StringValue name, Value []args)
  {
    return getFunction(name).call(this, args);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @return the function value
   */
  public Value callRef(StringValue name)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this);
  }

  /**
   * EvalRefuates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @return the function value
   */
  public Value callRef(StringValue name, Value a0)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this, a0);
  }

  /**
   * EvalRefuates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @return the function value
   */
  public Value callRef(StringValue name, Value a0, Value a1)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this, a0, a1);
  }

  /**
   * EvalRefuates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @param a2 the third argument
   * @return the function value
   */
  public Value callRef(StringValue name, Value a0, Value a1, Value a2)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this, a0, a1, a2);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @param a2 the third argument
   * @param a3 the fourth argument
   * @return the function value
   */
  public Value callRef(StringValue name, Value a0, Value a1, Value a2, Value a3)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this, a0, a1, a2, a3);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param a0 the first argument
   * @param a1 the second argument
   * @param a2 the third argument
   * @param a3 the fourth argument
   * @param a4 the fifth argument
   * @return the function value
   */
  public Value callRef(StringValue name, Value a0, Value a1,
                       Value a2, Value a3, Value a4)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this, a0, a1, a2, a3, a4);
  }

  /**
   * Evaluates the named function.
   *
   * @param name the function name
   * @param args the arguments
   * @return the function value
   */
  public Value callRef(StringValue name, Value []args)
  {
    AbstractFunction fun = findFunction(name);

    if (fun == null)
      return error(L.l("'{0}' is an unknown function.", name));

    return fun.callRef(this, args);
  }

  /**
   * Adds a class, e.g. from an include.
   */
  public void addClassDef(String name, ClassDef cl)
  {
    int id = _quercus.getClassId(name);

    if (_classDef.length <= id) {
      ClassDef []def = new ClassDef[id + 256];
      System.arraycopy(_classDef, 0, def, 0, _classDef.length);
      _classDef = def;
    }

    if (_classDef[id] == null)
      _classDef[id] = cl;
  }

  public ClassDef findClassDef(String name)
  {
    int id = _quercus.getClassId(name);

    if (id < _classDef.length)
      return _classDef[id];
    else
      return null;
  }

  /**
   * Saves the current state
   */
  public SaveState saveState()
  {
    if (_globalMap != _map)
      throw new QuercusException(
        L.l("Env.saveState() only allowed at top level"));

    return new SaveState(this,
                         _fun,
                         _classDef,
                         _qClass,
                         _const,
                         _staticMap,
                         _globalMap,
                         _includeMap,
                         _importMap);
  }

  EnvVar []getGlobalList()
  {
    return _globalList;
  }

  /**
   * Returns true for any special variables, i.e. which should not be
   * saved
   */
  boolean isSpecialVar(StringValue name)
  {
    if (QuercusContext.isSuperGlobal(name))
      return true;
    else if (_scriptGlobalMap.get(name) != null)
      return true;

    return false;
  }

  /**
   * Restores to a given state
   */
  public void restoreState(SaveState saveState)
  {
    AbstractFunction []fun = saveState.getFunctionList();
    if (_fun.length < fun.length)
      _fun = new AbstractFunction[fun.length];

    System.arraycopy(fun, 0, _fun, 0, fun.length);

    ClassDef []classDef = saveState.getClassDefList();
    if (_classDef.length < classDef.length)
      _classDef = new ClassDef[classDef.length];

    System.arraycopy(classDef, 0, _classDef, 0, classDef.length);

    QuercusClass []qClass = saveState.getQuercusClassList();
    if (_qClass.length < qClass.length)
      _qClass = new QuercusClass[qClass.length];

    System.arraycopy(qClass, 0, _qClass, 0, qClass.length);

    Value []constList = saveState.getConstantList();
    if (_const.length < constList.length)
      _const = new Value[constList.length];

    System.arraycopy(constList, 0, _const, 0, constList.length);

    IntMap staticNameMap = saveState.getStaticNameMap();
    Value []staticList = saveState.getStaticList();

    _staticMap = new LazyStaticMap(staticNameMap, staticList);

    IntMap globalNameMap = saveState.getGlobalNameMap();
    Value []globalList = saveState.getGlobalList();

    Map<StringValue,EnvVar> oldGlobal = _globalMap;

    _globalMap = new LazySymbolMap(globalNameMap, globalList);
    _map = _globalMap;

    // php/4045 - set the vars for any active EnvVar entries
    for (Map.Entry<StringValue,EnvVar> oldEntry : oldGlobal.entrySet()) {
      EnvVar oldEnvVar = oldEntry.getValue();

      EnvVar newEnvVar = _globalMap.get(oldEntry.getKey());

      if (newEnvVar != null)
        oldEnvVar.setVar(newEnvVar.getVar());
    }

    // php/404j - include_once
    Map<Path,QuercusPage> includeMap = saveState.getIncludeMap();
    _includeMap = new HashMap<Path,QuercusPage>(includeMap);

    // php/404l
    // XXX: import and namespaces

    ImportMap importMap =  saveState.getImportMap();

    if (importMap != null)
      _importMap = importMap.copy();
  }

  /**
   * Creates a stdClass object.
   */
  public ObjectValue createObject()
  {
    try {
      return (ObjectValue) _quercus.getStdClass().createObject(this);
    }
    catch (Exception e) {
      throw new QuercusModuleException(e);
    }
  }

  /**
   * Creates a stdClass object.
   */
  public ObjectValue createIncompleteObject(String name)
  {
    try {
      ObjectValue obj
        = (ObjectValue) _quercus.getStdClass().createObject(this);

      obj.setIncompleteObjectName(name);

      return obj;

    }
    catch (Exception e) {
      throw new QuercusModuleException(e);
    }
  }

  /*
   * Creates an empty string.
   */
  public StringValue getEmptyString()
  {
    if (_isUnicodeSemantics)
      return UnicodeBuilderValue.EMPTY;
    else
      return ConstStringValue.EMPTY;
  }

  /**
   * Creates an empty string builder.
   */
  public StringValue createStringBuilder()
  {
    if (_isUnicodeSemantics)
      return new UnicodeBuilderValue();
    else
      return new StringBuilderValue();
  }

  /**
   * Creates a PHP string from a byte buffer.
   */
  public StringValue createString(byte []buffer, int offset, int length)
  {
    if (_isUnicodeSemantics)
      return new UnicodeValueImpl(new String(buffer, offset, length));
    else
      return new ConstStringValue(buffer, offset, length);
  }

  /**
   * Creates a PHP string from a byte buffer.
   */
  public StringValue createString(char []buffer, int length)
  {
    if (_isUnicodeSemantics)
      return new UnicodeBuilderValue(buffer, length);
    else
      return new ConstStringValue(buffer, length);
  }

  /**
   * Creates a PHP string from a char buffer.
   */
  public StringValue createString(char []buffer, int offset, int length)
  {
    if (_isUnicodeSemantics)
      return new UnicodeBuilderValue(buffer, offset, length);
    else
      return new ConstStringValue(buffer, offset, length);
  }

  /**
   * Creates a PHP string from a long.
   */
  public StringValue createString(long value) {
    String s = String.valueOf(value);

    if (_isUnicodeSemantics)
      return new UnicodeValueImpl(s);
    else
      return new ConstStringValue(s);
  }

  /**
   * Creates a PHP string from a java String.
   */
  public StringValue createString(String s)
  {
    if (s == null || s.length() == 0) {
      return (_isUnicodeSemantics
              ? UnicodeBuilderValue.EMPTY
              : ConstStringValue.EMPTY);
    }
    else if (s.length() == 1) {
      if (_isUnicodeSemantics)
        return UnicodeBuilderValue.create(s.charAt(0));
      else
        return ConstStringValue.create(s.charAt(0));
    }
    else if (_isUnicodeSemantics) {
      return new UnicodeBuilderValue(s);
    }
    else if (s.length() < 256) {
      StringValue stringValue = _internStringMap.get(s);

      if (stringValue == null) {
        stringValue = new ConstStringValue(s);
        _internStringMap.put(s, stringValue);
      }

      return stringValue;

      // return new ConstStringValue(s);
    }
    else {
      return new ConstStringValue(s);
    }
  }

  /**
   * Creates a string from a byte.
   */
  public StringValue createString(char ch)
  {
    if (_isUnicodeSemantics)
      return UnicodeValueImpl.create(ch);
    else
      return ConstStringValue.create(ch);
  }

  /**
   * Creates a PHP string from a buffer.
   */
  public StringValue createBinaryString(TempBuffer head)
  {
    StringValue string;

    if (_isUnicodeSemantics)
      string = new BinaryBuilderValue();
    else
      string = new StringBuilderValue();

    for (; head != null; head = head.getNext()) {
      string.append(head.getBuffer(), 0, head.getLength());
    }

    return string;
  }

  public Value createException(String exceptionClass, String message)
  {
    QuercusClass cls = getClass(exceptionClass);

    StringValue messageV = createString(message);
    Value []args = { messageV };

    Value value = cls.callNew(this, args);

    Location location = getLocation();

    value.putField(this, "file", createString(location.getFileName()));
    value.putField(this, "line", LongValue.create(location.getLineNumber()));
    value.putField(this, "trace", ErrorModule.debug_backtrace(this, 0, 0));

    return value;
  }

  public Value createException(String exceptionClass, String ...args)
  {
    QuercusClass cls = getClass(exceptionClass);

    Value[] argsV = new Value[args.length];

    for (int i = 0; i < args.length; i++) {
      argsV[i] = createString(args[i]);
    }

    Value value = cls.callNew(this, argsV);

    Location location = getLocation();

    value.putField(this, "file", createString(location.getFileName()));
    value.putField(this, "line", LongValue.create(location.getLineNumber()));
    value.putField(this, "trace", ErrorModule.debug_backtrace(this, 0, 0));

    return value;
  }

  /**
   * Creates a PHP Exception.
   */
  public Value createException(Throwable e)
  {
    QuercusClass cls = findClass("Exception");

    StringValue message = createString(e.getMessage());
    Value []args = { message };

    Value value = cls.callNew(this, args);

    StackTraceElement elt = e.getStackTrace()[0];

    value.putField(this, "file", createString(elt.getFileName()));
    value.putField(this, "line", LongValue.create(elt.getLineNumber()));
    value.putField(this, "trace", ErrorModule.debug_backtrace(this, 0, 0));

    if ((e instanceof QuercusException) && e.getCause() != null)
      e = e.getCause();

    value.putField(this, "__javaException", wrapJava(e));

    return value;
  }

  /**
   * Generate an object id.
   */
  public int generateObjectId()
  {
    return ++_objectId;
  }

  /**
   * Returns an introspected Java class definition.
   */
  public JavaClassDef getJavaClassDefinition(String className)
  {
    JavaClassDef def = getJavaClassDefinition(className, true);

    if (def != null)
      return def;
    else
      throw createErrorException(L.l("'{0}' class definition not found",
                                     className));
  }

  /**
   * Returns an introspected Java class definition.
   */
  public JavaClassDef getJavaClassDefinition(Class<?> type)
  {
    JavaClassDef def = _quercus.getJavaClassDefinition(type, type.getName());

    return def;
  }

  private JavaClassDef getJavaClassDefinition(String className,
                                              boolean useImport)
  {
    JavaClassDef def = null;

    try {
      def = _quercus.getJavaClassDefinition(className);

      if (def == null && useImport) {
        useImport = false;
        def = importJavaClass(className);
      }
    }
    catch (Throwable e) {
      if (log.isLoggable(Level.FINEST)) {
        log.log(Level.FINEST, e.toString(), e);
      }

      if (useImport) {
        def = importJavaClass(className);

        if (def != null)
          return def;
      }
    }

    return def;
  }

  /**
   * Imports a Java class.
   *
   * @param className name of class to import
   * @return class definition of imported class, null if class not found
   */
  public JavaClassDef importJavaClass(String className)
  {
    if (_importMap == null)
      return null;

    String fullName = _importMap.getQualified(className);

    if (fullName != null) {
      return getJavaClassDefinition(fullName, false);
    }
    else {
      ArrayList<String> wildcardList
        = _importMap.getWildcardList();

      for (String entry : wildcardList) {
        fullName = entry + '.' + className;

        JavaClassDef def;

        try {
          def = getJavaClassDefinition(fullName, false);

          if (def != null) {
            _importMap.putQualified(className, fullName);
            return def;
          }
        } catch (Exception e) {
          log.log(Level.ALL, e.toString(), e);
        }
      }
    }

    return null;
  }

  /**
   * Adds a Quercus class import.
   *
   * @param javaName fully qualified class import string
   */
  public void putQualifiedImport(String javaName)
  {
    if (_importMap == null)
      _importMap = new ImportMap();

    String phpName = _importMap.putQualified(javaName);
  }

  /**
   * Adds a Quercus class import.
   *
   * @param name wildcard class import string
   * minus '*' at the end (i.e. java.util.)
   */
  public void addWildcardImport(String name)
  {
    if (_importMap == null)
      _importMap = new ImportMap();

    _importMap.addWildcardImport(name);
  }

  /**
   * Returns a PHP value for a Java object
   *
   * @param isNullAsFalse what to return if <i>obj</i> is null, if true return
   * {@link BooleanValue.FALSE} otherwise return {@link NullValue.NULL)
   */
  public Value wrapJava(Object obj, boolean isNullAsFalse)
  {
    if (obj == null) {
      if (isNullAsFalse)
        return BooleanValue.FALSE;
      else
        return NullValue.NULL;
    }

    return wrapJava(obj);
  }

  /**
   * Returns a PHP value for a Java object
   *
   * @param isNullAsFalse what to return if <i>obj</i> is null, if true return
   * {@link BooleanValue.FALSE} otherwise return {@link NullValue.NULL)
   */
  public Value wrapJava(Object obj, JavaClassDef def, boolean isNullAsFalse)
  {
    if (obj == null) {
      if (isNullAsFalse)
        return BooleanValue.FALSE;
      else
        return NullValue.NULL;
    }

    return wrapJava(obj, def);
  }

  /**
   * Returns a PHP value for a Java object
   */
  public Value wrapJava(Object obj)
  {
    if (obj == null)
      return NullValue.NULL;

    if (obj instanceof Value)
      return (Value) obj;

    JavaClassDef def = getJavaClassDefinition(obj.getClass());

    return def.wrap(this, obj);
  }

  /**
   * Returns a PHP value for a Java object
   *
   * @param isNullAsFalse what to return if <i>obj</i> is null, if true return
   * {@link BooleanValue.FALSE} otherwise return {@link NullValue.NULL)
   */
  public Value wrapJava(Object obj, JavaClassDef def)
  {
    if (obj == null) {
      return NullValue.NULL;
    }

    if (obj instanceof Value) {
      return (Value) obj;
    }

    // XXX: why is this logic here?  The def should be correct on the call
    // logic is for JavaMarshal, where can avoid the lookup call
    if (def.getType() != obj.getClass()) {
      // php/0ceg

      // XXX: what if types are incompatible, does it matter?
      // if it doesn't matter, simplify this to one if with no else
      def = getJavaClassDefinition(obj.getClass());
    }

    return def.wrap(this, obj);
  }

  /**
   * Adds a class alias.
   */
  public void addClassAlias(String alias, QuercusClass cls)
  {
    if (_classAliasMap == null) {
      _classAliasMap = new HashMap<String,QuercusClass>();
    }

    _classAliasMap.put(alias, cls);
  }

  /**
   * Finds the class with the given alias name.
   */
  public QuercusClass findClassByAlias(String alias)
  {
    if (_classAliasMap == null) {
      return null;
    }

    return _classAliasMap.get(alias);
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @return the found class or null if no class found.
   */
  public QuercusClass findClass(String name)
  {
    return findClass(name, -1, true, true, true);
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @param useAutoload use autoload to locate the class if necessary
   * @return the found class or null if no class found.
   */
  public QuercusClass findClass(String name,
                                boolean useAutoload,
                                boolean useImport,
                                boolean useAliasMap)
  {
    return findClass(name, -1, useAutoload, useImport, useAliasMap);
  }

  /**
   * Finds the class with the given name id.
   *
   * @param name the class name
   * @return the found class or null if no class found.
   */
  public QuercusClass findClass(int id)
  {
    return findClass(null, id, true, true, true);
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @param useAutoload use autoload to locate the class if necessary
   * @return the found class or null if no class found.
   */
  public QuercusClass findClass(int id,
                                boolean useAutoload,
                                boolean useImport,
                                boolean useAliasMap)
  {
    return findClass(null, id, useAutoload, useImport, useAliasMap);
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @param useAutoload use autoload to locate the class if necessary
   * @return the found class or null if no class found.
   */
  public QuercusClass findClass(String name,
                                int id,
                                boolean useAutoload,
                                boolean useImport,
                                boolean useAliasMap)
  {
    if (id < 0) {
      id = _quercus.getClassId(name);
    }

    if (id < _qClass.length && _qClass[id] != null) {
      return _qClass[id];
    }

    QuercusClass cl = createClassFromCache(id, useAutoload, useImport, useAliasMap);

    if (cl != null) {
      _qClass[id] = cl;

      // php/09b7
      cl.init(this);

      return cl;
    }
    else {
      if (name == null) {
        name = _quercus.getClassName(id);
      }

      QuercusClass qcl = null;

      if (useAliasMap) {
        qcl = findClassByAlias(name);
      }

      if (qcl == null) {
        qcl = findClassExt(name, id, useAutoload, useImport, useAliasMap);
      }

      if (qcl != null) {
        _qClass[id] = qcl;
      }
      else {
        return null;
      }

      return qcl;
    }
  }

  private QuercusClass findClassExt(String name,
                                    int id,
                                    boolean useAutoload,
                                    boolean useImport,
                                    boolean useAliasMap)
  {
    if (id < 0) {
      id = _quercus.getClassId(name);
    }

    if (useAutoload) {
      if (! _autoloadClasses.contains(name)) {
        try {
          _autoloadClasses.add(name);

          StringValue nameString = createString(name);

          int size = _autoloadList != null ? _autoloadList.size() : 0;

          for (int i = 0; i < size; i++) {
            Callable cb = _autoloadList.get(i);

            cb.call(this, nameString);

            // php/0977
            QuercusClass cls = findClass(name, id, false, useImport, useAliasMap);

            if (cls != null)
              return cls;
          }

          if (size == 0) {
            if (_autoload == null)
              _autoload = findFunction(createString("__autoload"));

            if (_autoload != null) {
              _autoload.call(this, nameString);

              // php/0976
              QuercusClass cls = findClass(name, id, false, useImport, useAliasMap);

              if (cls != null)
                return cls;
            }
          }
        } finally {
          _autoloadClasses.remove(name);
        }
      }
    }

    if (useImport) {
      if (importPhpClass(name)) {
        return findClass(name, id, false, false, useAliasMap);
      }
      else {
        try {
          JavaClassDef javaClassDef = getJavaClassDefinition(name, true);

          if (javaClassDef != null) {
            QuercusClass cls = createQuercusClass(id, javaClassDef, null);

            _qClass[id] = cls;

            cls.init(this);

            return cls;
          }
        }
        catch (Exception e) {
          log.log(Level.FINER, e.toString(), e);
        }
      }
    }

    return _internalAutoload.loadClass(this, name);
  }

  /**
   * Returns the class with the given id
   */
  public QuercusClass getClass(int classId)
  {
    if (_qClass.length <= classId) {
      QuercusClass []oldClassList = _qClass;

      _qClass = new QuercusClass[classId + 256];

      System.arraycopy(oldClassList, 0, _qClass, 0, oldClassList.length);
    }

    QuercusClass qClass = _qClass[classId];

    if (qClass != null)
      return qClass;

    if (_classDef.length <= classId) {
      ClassDef []oldClassDefList = _classDef;

      _classDef = new ClassDef[classId + 256];

      System.arraycopy(oldClassDefList, 0,
                       _classDef, 0,
                       oldClassDefList.length);
    }

    ClassDef def = _classDef[classId];

    if (def == null) {
      QuercusClass cl = findClass(null, classId, true, true, true);

      if (cl != null)
        return cl;
      else {
        error(L.l("'{0}' is an unknown class.",
                  _quercus.getClassName(classId)));


        throw new QuercusException(L.l("'{0}' is an unknown class.",
                                       _quercus.getClassName(classId)));
      }
    }

    int parentId = -1;

    if (def.getParentName() != null)
      parentId = _quercus.getClassId(def.getParentName());

    addClass(def, classId, parentId);

    return _qClass[classId];
  }

  /**
   * Adds the class with the given name
   *
   * @param def the class definition
   * @param classId the identifier for the class name
   * @param parentId the identifier for the parent class name
   */
  public void addClass(ClassDef def, int classId, int parentId)
  {
    def = def.loadClassDef();

    // php/0cn2 - make sure interfaces have a QuercusClass
    /* XXX: temp, needs to be argument
    for (String iface : def.getInterfaces()) {
      QuercusClass cl = findClass(iface);
    }
    */

    QuercusClass parentClass = null;

    if (parentId >= 0)
      parentClass = getClass(parentId);

    QuercusClass qClass = _quercus.getCachedClass(classId);

    if (qClass == null
        || qClass.isModified()
        || qClass.getClassDef() != def
        || qClass.getParent() != parentClass) {
      qClass = createQuercusClass(classId, def, parentClass);

      _quercus.setCachedClass(classId, qClass);
    }

    if (_qClass.length <= classId) {
      QuercusClass []oldClassList = _qClass;

      _qClass = new QuercusClass[classId + 256];

      System.arraycopy(oldClassList, 0, _qClass, 0, oldClassList.length);
    }

    _qClass[classId] = qClass;
    qClass.init(this);
  }

  public void addClass(String name, ClassDef def)
  {
    int id = _quercus.getClassId(name);

    int parentId = -1;

    if (def.getParentName() != null)
      parentId = _quercus.getClassId(def.getParentName());

    addClass(def, id, parentId);
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @param useAutoload use autoload to locate the class if necessary
   * @param useImport import the class if necessary
   *
   * @return the found class or null if no class found.
   */
  private QuercusClass createClassFromCache(int id,
                                            boolean useAutoload,
                                            boolean useImport,
                                            boolean useAliasMap)
  {
    if (id < _classDef.length && _classDef[id] != null) {
      ClassDef classDef = _classDef[id];

      String parentName = classDef.getParentName();

      QuercusClass parent = null;

      if (parentName != null) {
        parent = findClass(parentName, -1, useAutoload, useImport, useAliasMap);
      }

      if (parentName == null || parent != null) {
        return createQuercusClass(id, classDef, parent);
      }
      else {
        return null; // php/
      }
    }

    ClassDef staticClass = _quercus.getClassDef(id);

    if (staticClass != null) {
      return createQuercusClass(id, staticClass, null); // XXX: cache
    }
    else {
      return null;
    }
  }

  /**
   * Registers an SPL autoload function.
   */
  public void addAutoloadFunction(Callable fun, boolean isPrepend)
  {
    if (fun == null)
      throw new NullPointerException();

    if (_autoloadList == null)
      _autoloadList = new ArrayList<Callable>();

    if (isPrepend) {
      _autoloadList.add(0, fun);
    }
    else {
      _autoloadList.add(fun);
    }
  }

  /**
   * Unregisters an SPL autoload function.
   */
  public void removeAutoloadFunction(Callable fun)
  {
    if (_autoloadList != null) {
      _autoloadList.remove(fun);

      //restore original __autoload functionality
      if (_autoloadList.size() == 0)
        _autoloadList = null;
    }
  }

  /**
   * Returns the registered SPL autoload functions.
   */
  public ArrayList<Callable> getAutoloadFunctions()
  {
    return _autoloadList;
  }

  /**
   * Imports a PHP class.
   *
   * @param name of the PHP class
   *
   * @return true if matching php file was found and included.
   */
  public boolean importPhpClass(String name)
  {
    if (_importMap == null)
      return false;

    String fullName = _importMap.getQualifiedPhp(name);

    URL url = null;
    ClassLoader loader = Thread.currentThread().getContextClassLoader();

    if (fullName != null) {
      url = loader.getResource(fullName);
    }
    else {
      for (String entry : _importMap.getWildcardPhpList()) {

        url = loader.getResource(entry + '/' + name + ".php");

        if (url != null)
          break;
      }
    }

    if (url != null) {
      includeOnce(createString(url.toString()));
      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Returns the declared classes.
   *
   * @return an array of the declared classes()
   */
  public Value getDeclaredClasses()
  {
    ArrayList<String> list = new ArrayList<String>();

    for (int i = 0; i < _classDef.length; i++) {
      if (_classDef[i] != null)
        list.add(_classDef[i].getName());
    }

    HashMap<String, ClassDef> classMap = getModuleContext().getClassMap();

    for (Map.Entry<String, ClassDef> entry : classMap.entrySet()) {
      list.add(entry.getKey());
    }

    Collections.sort(list);

    ArrayValue array = new ArrayValueImpl();

    Iterator<String> iter = list.iterator();
    while (iter.hasNext()) {
      array.put(iter.next());
    }

    return array;
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @return the found class or null if no class found.
   */
  public QuercusClass findAbstractClass(String name)
  {
    QuercusClass cl = findClass(name, -1, true, true, true);

    if (cl != null) {
      return cl;
    }

    throw createErrorException(L.l("'{0}' is an unknown class name.", name));
    /*
    // return _quercus.findJavaClassWrapper(name);

    return null;
    */
  }

  /**
   * Finds the class with the given name.
   *
   * @param name the class name
   * @return the found class
   * @throws QuercusRuntimeException if the class is not found
   */
  public QuercusClass getClass(String name)
  {
    QuercusClass cl = findClass(name);

    if (cl != null)
      return cl;
    else
      throw createErrorException(L.l("'{0}' is an unknown class.", name));
  }

  public void clearClassCache()
  {
    // _classCache.clear();
  }

  QuercusClass createJavaQuercusClass(JavaClassDef def)
  {
    int id = getQuercus().getClassId(def.getName());

    if (_qClass.length <= id) {
      QuercusClass []oldClassList = _qClass;

      _qClass = new QuercusClass[id + 256];

      System.arraycopy(oldClassList, 0, _qClass, 0, oldClassList.length);
    }

    if (_qClass[id] == null)
      _qClass[id] = def.getQuercusClass();

    return _qClass[id];
  }

  QuercusClass createQuercusClass(int id,
                                  ClassDef def,
                                  QuercusClass parent)
  {
    QuercusClass qClass = _quercus.getCachedClass(id);

    // php/0ac0
    if (_qClass.length <= id) {
      QuercusClass []oldClassList = _qClass;

      _qClass = new QuercusClass[id + 256];

      System.arraycopy(oldClassList, 0, _qClass, 0, oldClassList.length);
    }

    if (qClass == null
        || qClass.isModified()
        || qClass.getClassDef() != def
        || qClass.getParent() != parent) {
      qClass = new QuercusClass(getModuleContext(), def, parent);
      _qClass[id] = qClass;

      qClass.validate(this);
      _quercus.setCachedClass(id, qClass);
    }

    _qClass[id] = qClass;

    return qClass;
  }

  /**
   * Returns true if class has already been initialized.
   */
  public boolean isInitializedClass(String name)
  {
    return _initializedClassSet.contains(name);
  }

  /**
   * Mark this class as being initialized.
   */
  public void addInitializedClass(String name)
  {
    _initializedClassSet.add(name);
  }

  /**
   * Finds the class and method.
   *
   * @param className the class name
   * @param methodName the method name
   * @return the found method or null if no method found.
   */
  public AbstractFunction findFunction(String className, String methodName)
  {
    QuercusClass cl = findClass(className);

    if (cl == null)
      throw new QuercusRuntimeException(L.l("'{0}' is an unknown class",
                                            className));

    return cl.findFunction(methodName);
  }

  /**
   * Returns the appropriate callback.
   */
  /*
  public Callback createCallback(Value value)
  {
    if (value == null || value.isNull())
      return null;

    value = value.toValue();

    if (value instanceof Callback) {
      return (Callback) value;
    }
    else if (value instanceof StringValue) {
      // php/1h0o
      if (value.isEmpty())
        return null;

      String s = value.toString();

      int p = s.indexOf("::");

      if (p < 0)
        return new CallbackFunction(this, s);
      else {
        String className = s.substring(0, p);
        String methodName = s.substring(p + 2);

        QuercusClass cl = findClass(className);

        if (cl == null)
          throw new IllegalStateException(L.l("can't find class {0}",
                                              className));

        return new CallbackFunction(cl.getFunction(createString(methodName)));
      }
    }
    else if (value.isArray()) {
      Value obj = value.get(LongValue.ZERO);
      Value nameV = value.get(LongValue.ONE);

      if (! nameV.isString())
        throw new IllegalStateException(
          L.l("'{0}' ({1}) is an unknown callback name",
          nameV, nameV.getClass().getSimpleName()));

      String name = nameV.toString();

      if (obj.isObject()) {
        AbstractFunction fun;

        int p = name.indexOf("::");

        // php/09lf
        if (p > 0) {
          String clsName = name.substring(0, p);
          name = name.substring(p + 2);

          QuercusClass cls = findClass(clsName);

          if (cls == null) {
            warning(L.l("Callback: '{0}' is not a valid callback class for {1}",
                        clsName, name));

            return null;
          }

          if (cls == null)
            throw new IllegalStateException(L.l("can't find class '{0}'",
                                                obj.toString()));

          // fun = cls.getFunction(name);
        }

        return new CallbackObjectMethod(this, obj, createString(name));
      }
      else {
        QuercusClass cl = findClass(obj.toString());

        if (cl == null) {
          warning(L.l("Callback: '{0}' is not a valid callback string for {1}",
                      obj.toString(), obj));

          return null;
        }

        return new CallbackObjectMethod(this, cl, createString(name));
      }
    }
    else
      return null;
  }
  */

  /**
   * Evaluates an included file.
   */
  public Value requireOnce(StringValue include)
  {
    return include(getSelfDirectory(), include, true, true);
  }

  /**
   * Evaluates an included file.
   */
  public Value require(StringValue include)
  {
    return include(getSelfDirectory(), include, true, false);
  }

  /**
   * Evaluates an included file.
   */
  public Value include(StringValue include)
  {
    return include(getSelfDirectory(), include, false, false);
  }

  /**
   * Evaluates an included file.
   */
  public Value includeOnce(StringValue include)
  {
    return include(getSelfDirectory(), include, false, true);
  }

  /**
   * Evaluates an included file.
   */
  public Value includeOnce(Path scriptPwd, StringValue include,
                           boolean isRequire)
  {
    return include(scriptPwd, include, isRequire, true);
  }

  /**
   * Evaluates an included file.
   */
  public Value include(Path scriptPwd, StringValue include,
                       boolean isRequire, boolean isOnce)
  {
    try {
      Path pwd = getPwd();

      Path path = lookupInclude(include, pwd, scriptPwd);

      if (path != null) {
      }
      else if (isRequire) {
        error(L.l("'{0}' is not a valid include path", include));
        return BooleanValue.FALSE;
      }
      else {
        warning(L.l("'{0}' is not a valid include path", include));
        return BooleanValue.FALSE;
      }

      // php/0b2d
      if (! _isAllowUrlInclude && isUrl(path)) {
        String msg = (L.l("not allowed to include url {0}", path.getURL()));

        log.warning(dbgId() + msg);
        error(msg);

        return BooleanValue.FALSE;
      }

      QuercusPage page = _includeMap.get(path);

      if (page != null && isOnce)
        return BooleanValue.TRUE;
      else if (page == null || page.isModified()) {
        page = _quercus.parse(path);

        pageInit(page);

        _includeMap.put(path, page);
      }

      return executePage(page);
    } catch (IOException e) {
      throw new QuercusModuleException(e);
    }
  }

  void executePage(Path path)
  {
    if (log.isLoggable(Level.FINEST)) {
      log.finest(this + " execute " + path);
    }

    try {
      QuercusPage page = _quercus.parse(path);

      pageInit(page);

      executePage(page);
    } catch (IOException e) {
      throw new QuercusException(e);
    }
  }

  /**
   * Returns true if this path is likely to be a URL.
   */
  private boolean isUrl(Path path)
  {
    String scheme = path.getScheme();

    if ("".equals(scheme)
        || "file".equals(scheme)
        || "memory".equals(scheme)) {
      return false;
    }

    if (path instanceof JarPath) {
      // php/0h1a
      return isUrl(((JarPath) path).getContainer());
    }

    // XXX: too restrictive for filters
    return ! "php".equals(scheme)
           || "php://input".equals(path.toString())
           || path.toString().startsWith("php://filter");
  }

  /**
   * Looks up based on the pwd.
   */
  public Path lookupPwd(Value relPathV)
  {
    if (! relPathV.isset())
      return null;

    StringValue relPath = relPathV.toStringValue();

    if (relPath.length() == 0)
      return null;

    Path path = _lookupCache.get(relPath);

    if (path == null) {
      path = getPwd().lookup(normalizePath(relPath));

      _lookupCache.put(relPath, path);
    }

    return path;
  }

  /**
   * Looks up the path.
   */
  public Path lookup(StringValue relPath)
  {
    return lookupInclude(getSelfDirectory(), normalizePath(relPath));
  }

  /**
   * Looks up the path.
   */
  public Path lookupInclude(StringValue relPath)
  {
    return lookupInclude(relPath, getPwd(), getSelfDirectory());
  }

  private Path lookupInclude(StringValue include, Path pwd, Path scriptPwd)
  {
    String includePath = getDefaultIncludePath();

    Path path = _quercus.getIncludeCache(include, includePath, pwd, scriptPwd);

    if (path == null) {
      path = lookupIncludeImpl(include, pwd, scriptPwd);

      /*
      if (path == null)
        path = NullPath.NULL;
      */

      if (path != null)
        _quercus.putIncludeCache(include, includePath, pwd, scriptPwd, path);
    }

    if (path == NullPath.NULL)
      path = null;

    _includePath = includePath;
    _includePathIniCount = _iniCount;

    return path;
  }

  private String getDefaultIncludePath()
  {
    String includePath = _includePath;

    if (_includePathIniCount != _iniCount) {
      includePath = QuercusContext.INI_INCLUDE_PATH.getAsString(this);
      _includePath = null;
      _includePathList = null;
    }

    if (includePath == null)
      includePath = ".";

    return includePath;
  }

  private Path lookupIncludeImpl(StringValue includeValue,
                                 Path pwd,
                                 Path scriptPwd)
  {
    String include = normalizePath(includeValue);

    // php/0b0g

    Path path = lookupInclude(pwd, include);

    if (path == null) {
      // php/0b0l
      path = lookupInclude(scriptPwd, include);
    }

    if (path == null) {
      // php/0b21
      path = scriptPwd.lookup(include);

      if (! includeExists(path))
        path = null;
    }

    return path;
  }

  /**
   * Looks up the path.
   */
  private Path lookupInclude(Path pwd, String relPath)
  {
    ArrayList<Path> pathList = getIncludePath(pwd);

    for (int i = 0; i < pathList.size(); i++) {
      Path path = pathList.get(i).lookup(relPath);

      if (path.canRead() && ! path.isDirectory()) {
        return path;
      }
    }

    return null;
  }

  private boolean includeExists(Path path)
  {
    if (path.canRead() && ! path.isDirectory())
      return true;
    else if (! getQuercus().isRequireSource())
      return getQuercus().includeExists(path);
    else
      return false;
  }
  /**
   * Returns the include path.
   */
  private ArrayList<Path> getIncludePath(Path pwd)
  {
    String includePath = getDefaultIncludePath();

    if (_includePathList == null) {
      _includePathList = new ArrayList<String>();
      _includePathMap = new HashMap<Path,ArrayList<Path>>();

      int head = 0;
      int tail;

      String pathSeparator = FileModule.PATH_SEPARATOR;
      int length = pathSeparator.length();

      while ((tail = includePath.indexOf(pathSeparator, head)) >= 0) {
        String subpath = includePath.substring(head, tail);

        _includePathList.add(normalizePath(subpath));

        head = tail + length;
      }

      String subpath = includePath.substring(head);

      _includePathList.add(normalizePath(subpath));

      _includePath = includePath;
      _includePathIniCount = _iniCount;
    }

    ArrayList<Path> pathList = _includePathMap.get(pwd);

    if (pathList == null) {
      pathList = new ArrayList<Path>();

      if (pwd != null) {
        for (int i = 0; i < _includePathList.size(); i++) {
          pathList.add(pwd.lookup(_includePathList.get(i)));
        }

        _includePathMap.put(pwd, pathList);
      }
    }

    return pathList;
  }

  /**
   * Sets the include path.
   */
  public String setIncludePath(String path)
  {
    String prevIncludePath = QuercusContext.INI_INCLUDE_PATH.getAsString(this);

    if (_defaultIncludePath == null)
      _defaultIncludePath = prevIncludePath;

    QuercusContext.INI_INCLUDE_PATH.set(this, path);

    // reset include path cache count
    _includePathIniCount = -1;

    return prevIncludePath;
  }

  public String normalizePath(CharSequence path)
  {
    if (Path.isWindows()) {
      _cb.setLength(0);

      int len = path.length();

      if (len >= 3) {
        char ch;
        char ch3;

        if (path.charAt(1) == ':'
            && ('a' <= (ch = path.charAt(0)) && ch <= 'z'
                || 'A' <= ch && ch <= 'Z')
            && (ch3 = path.charAt(2)) != '/' && ch3 != '\\') {
          _cb.append(ch);
          _cb.append(':');
          _cb.append('\\');
          _cb.append(ch3);

          for (int i = 3; i < len; i++) {
            _cb.append(path.charAt(i));
          }

          return _cb.toString();
        }
      }
    }

    return path.toString();
  }

  /**
   * Restores the default include path.
   */
  public void restoreIncludePath()
  {
    QuercusContext.INI_INCLUDE_PATH.set(this, _defaultIncludePath);
  }

  /**
   * Returns all the included files.
   */
  public ArrayValue getIncludedFiles()
  {
    ArrayValue array = new ArrayValueImpl();

    ArrayList<String> list = new ArrayList<String>();

    for (Path path : _includeMap.keySet()) {
      list.add(path.getNativePath());
    }

    Collections.sort(list);

    for (String pathName : list) {
      array.put(createString(pathName));
    }

    return array;
  }

  /**
   * Handles error suppression.
   */
  public Value suppress(int errorMask, Value value)
  {
    setErrorMask(errorMask);

    return value;
  }

  /**
   * Handles exit/die
   */
  public Value exit(Value msg)
  {
    if (msg.isNull() || msg instanceof LongValue)
      return exit();

    try {
      getOut().print(msg.toString());
    } catch (IOException e) {
      log.log(Level.WARNING, e.toString(), e);
    }

    throw new QuercusExitException(msg.toString()
                                   + "\n" + getStackTraceAsString());
  }

  /**
   * Handles exit/die
   */
  public Value exit()
  {
    throw new QuercusExitException(getStackTraceAsString());
  }

  /**
   * Handles exit/die
   */
  public Value die(String msg)
  {
    try {
      getOut().print(msg);
    } catch (IOException e) {
      log.log(Level.WARNING, e.toString(), e);
    }

    throw new QuercusDieException(msg + "\n" + getStackTraceAsString());
  }

  /**
   * Handles exit/die
   */
  public Value die()
  {
    throw new QuercusDieException(getStackTraceAsString());
  }

  /**
   * Handles exit/die
   */
  public Value cast(Class<?> cl, Value value)
  {
    value = value.toValue();

    if (value.isNull())
      return null;
    else if (cl.isAssignableFrom(value.getClass()))
      return value;
    else {
      // php/3cr2
      warning(L.l("{0} ({1}) is not assignable to {2}",
                value, value.getClass().getName(), cl.getName()));

      return null;
    }
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value, Value a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value, double a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static long first(long value, Value a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static double first(double value, Value a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static long first(long value, double a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static long first(long value, long a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static double first(double value, double a1)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value, Value a1, Value a2)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value, Value a1, Value a2, Value a3)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value, Value a1, Value a2, Value a3,
                            Value a4)
  {
    return value;
  }

  /**
   * Returns the first value
   */
  public static Value first(Value value, Value a1, Value a2, Value a3,
                            Value a4, Value a5)
  {
    return value;
  }

  /**
   * Check for expected type.
   */
  public Value expectString(Value value)
  {
    if (! value.isString()
        && ! value.isLong()
        && ! value.isDouble()
        && ! value.isBoolean()
        && ! value.isNull()) {
      //warning(L.l("argument must be a string, but {0} given",
      //            value.getType()));

      return new UnexpectedValue(value);
    }

    return value;
  }

  /**
   * Check for expected type.
   */
  public Value expectNumeric(Value value)
  {
    if (! value.isNull()
        && ! value.isLongConvertible()) {
      //warning(L.l("argument must be numeric, but {0} given",
      //            value.getType()));

      return new UnexpectedValue(value);
    }

    return value;
  }

  /**
   * Check for expected type.
   */
  public Value expectBoolean(Value value)
  {
    if (! value.isBoolean()
        && ! value.isNull()
        && ! value.isString()
        && ! value.isNumeric()) {
      //warning(L.l("argument must be boolean, but {0} given",
      //            value.getType()));
      return new UnexpectedValue(value);
    }

    return value;
  }

  /**
   * Check for type hinting
   */
  public void checkTypeHint(Value value,
                            String type,
                            String argName,
                            String functionName)
  {
    if (value.isNull()) {
      error(L.l(
        "'{0}' is an unexpected value for "
        + "arg '{1}' in function '{2}', expected '{3}'",
        value,
        argName,
        functionName,
        type));
    }
  }

  /**
   * Error when using $this not inside an object context.
   */
  public Value thisError(Location location)
  {
    return error(L.l("Cannot use '$this' when not in object context."),
                 location);
  }

  /**
   * Error when using $this not inside an object context.
   */
  public Value thisError()
  {
    return thisError(getLocation());
  }

  /**
   * A fatal runtime error.
   */
  public Value error(String msg)
  {
    return error(B_ERROR, msg, getLocation());
  }

  /**
   * A fatal runtime error.
   */
  public Value error(String msg, Location location)
  {
    return error(B_ERROR, msg, location);
  }

  /**
   * A warning with an exception.
   */
  public Value error(String msg, Throwable e)
  {
    log.log(Level.WARNING, e.toString(), e);

    return error(msg);
  }

  /**
   * A warning with an exception.
   */
  public Value error(Throwable e)
  {
    log.log(Level.WARNING, e.toString(), e);

    return error(e.toString());
  }

  /**
   * A fatal runtime error.
   */
  public QuercusRuntimeException createErrorException(String msg)
    throws QuercusRuntimeException
  {
    return createErrorException(null, msg);
  }

  /**
   * A fatal runtime error.
   */
  public QuercusRuntimeException createErrorException(Location location,
                                                      String msg)
    throws QuercusRuntimeException
  {
    if (location == null || location.isUnknown())
      location = getLocation();

    String prefix = location.getMessagePrefix();

    String fullMsg = msg + getFunctionLocation();

    error(B_ERROR, fullMsg, location);

    //String exMsg = prefix + fullMsg;

    return new QuercusRuntimeException(fullMsg);
  }

  /**
   * A fatal runtime error.
   */
  public QuercusRuntimeException createErrorException(Throwable e)
    throws QuercusRuntimeException
  {
    Location location = getLocation();

    String prefix = location.getMessagePrefix();

    String fullMsg = e.toString() + getFunctionLocation();

    error(B_ERROR, fullMsg, location);

    String exMsg = prefix + fullMsg + getStackTraceAsString(e, null);

    return new QuercusRuntimeException(exMsg, e);
  }

  /**
   * A runtime warning.
   */
  public Value warning(String msg)
  {
    int mask = 1 << B_WARNING;

    if ((getErrorMask() & mask) != 0) {
      if (log.isLoggable(Level.FINER)) {
        QuercusException e = new QuercusException(getExceptionLocation(msg));

        log.log(Level.FINER, e.toString(), e);
      }
    }

    return error(B_WARNING, msg, getLocation());
  }

  private String getExceptionLocation(String msg)
  {
    return getExceptionLocation(msg, getLocation());
  }

  private String getExceptionLocation(String msg, Location loc)
  {
    if (loc != null && ! loc.isUnknown()) {
      return (loc.getFileName() + ":" + loc.getLineNumber() + ": " + msg
          + getFunctionLocation()
          + getStackTraceAsString(null));
    }
    else {
      return (msg
              + getFunctionLocation()
              + getStackTraceAsString());
    }
  }

  /**
   * A runtime warning.
   */
  public Value warning(String msg, Location location)
  {
    int mask = 1 << B_WARNING;

    if ((getErrorMask() & mask) != 0) {
      if (log.isLoggable(Level.FINER)) {
        QuercusException e = new QuercusException(msg);

        log.log(Level.FINER, e.toString(), e);
      }
    }

    return error(B_WARNING, msg, location);
  }

  /**
   * A warning with an exception.
   */
  public Value warning(String msg, Throwable e)
  {
    log.log(Level.FINE, e.toString(), e);

    return warning(msg);
  }

  /**
   * A warning with an exception.
   */
  public Value warning(String msg, Location location, Throwable e)
  {
    log.log(Level.FINE, e.toString(), e);

    return warning(msg, location);
  }

  /**
   * A warning with an exception.
   */
  public Value warning(Throwable e)
  {
    return warning(e.toString(), e);
  }

  /**
   * A warning with an exception.
   */
  public Value warning(Throwable e, Location location)
  {
    return warning(e.toString(), location, e);
  }

  /**
   * A runtime strict warning.
   */
  public Value strict(String msg)
  {
    if (log.isLoggable(Level.FINER)) {
      QuercusException e = new QuercusException(msg);

      log.log(Level.FINER, e.toString(), e);
    }

    return error(B_STRICT, msg, getLocation());
  }

  /**
   * A warning about an invalid argument passed to a function.
   */
  public Value invalidArgument(String name, Object value)
  {
    return warning(L.l("invalid value `{0}' for `{1}'", value, name));
  }

  /**
   * A warning about an deprecated argument passed to a function.
   */
  public Value deprecatedArgument(String name)
  {
    return strict(L.l("argument `{1}' is deprecated", name));
  }

  /**
   * A notice.
   */
  public Value notice(String msg)
  {
    return error(B_NOTICE, msg, getLocation());
  }

  /**
   * A warning with an exception.
   */
  public Value notice(Throwable e)
  {
    log.log(Level.FINE, e.toString(), e);

    return notice(e.toString());
  }

  /**
   * A notice with an exception.
   */
  public Value notice(String msg, Throwable e)
  {
    log.log(Level.FINE, e.toString(), e);

    return notice(msg);
  }

  /**
   * A stub notice.
   */
  public Value stub(String msg)
  {
    if (log.isLoggable(Level.FINE)) {
      log.fine(getLocation().getMessagePrefix() + msg);
    }

    return NullValue.NULL;
  }

  public static Value nullAsFalse(Value value)
  {
    return value == null || value.isNull() ? BooleanValue.FALSE : value;
  }

  /**
   * A parse error
   */
  public Value parse(String msg)
    throws Exception
  {
    return error(B_PARSE, msg);
  }

  /**
   * A parse error
   */
  public Value compileError(String msg)
  {
    return error(B_COMPILE_ERROR, msg);
  }

  /**
   * A parse warning
   */
  public Value compileWarning(String msg)
  {
    return error(B_COMPILE_WARNING, msg);
  }

  /**
   * Returns the error mask.
   */
  public int getErrorMask()
  {
    return getIni("error_reporting").toInt();
  }

  /**
   * Sets the error mask.
   */
  public int setErrorMask(int mask)
  {
    int oldMask = getErrorMask();

    setIni("error_reporting", LongValue.create(mask));

    return oldMask;
  }

  /**
   * Sets an error handler
   */
  public void setErrorHandler(int mask, Callable fun)
  {
    for (int i = 0; i < _errorHandlers.length; i++)
      _prevErrorHandlers[i] = _errorHandlers[i];

    if ((mask & E_ERROR) != 0)
      _errorHandlers[B_ERROR] = fun;

    if ((mask & E_WARNING) != 0)
      _errorHandlers[B_WARNING] = fun;

    if ((mask & E_PARSE) != 0)
      _errorHandlers[B_PARSE] = fun;

    if ((mask & E_NOTICE) != 0)
      _errorHandlers[B_NOTICE] = fun;

    if ((mask & E_USER_ERROR) != 0)
      _errorHandlers[B_USER_ERROR] = fun;

    if ((mask & E_USER_WARNING) != 0)
      _errorHandlers[B_USER_WARNING] = fun;

    if ((mask & E_USER_NOTICE) != 0)
      _errorHandlers[B_USER_NOTICE] = fun;

    if ((mask & E_STRICT) != 0)
      _errorHandlers[B_STRICT] = fun;

    if ((mask & E_RECOVERABLE_ERROR) != 0)
      _errorHandlers[B_RECOVERABLE_ERROR] = fun;
  }

  /**
   * Sets an error handler
   */
  public void restoreErrorHandler()
  {
    for (int i = 0; i < _errorHandlers.length; i++)
      _errorHandlers[i] = _prevErrorHandlers[i];
  }

  /**
   * Gets the exception handler
   */
  public Callable getExceptionHandler()
  {
    return _exceptionHandler;
  }

  /**
   * Sets an exception handler
   */
  public Value setExceptionHandler(Callable fun)
  {
    _prevExceptionHandler = _exceptionHandler;

    _exceptionHandler = fun;

    if (_prevExceptionHandler != null) {
      //
      return ((Value) _prevExceptionHandler).toStringValue();
    }
    else
      return NullValue.NULL;
  }

  /**
   * Restore an exception handler
   */
  public void restoreExceptionHandler()
  {
    _exceptionHandler = _prevExceptionHandler;
  }

  /**
   * Writes an error.
   */
  public Value error(int code, String msg)
  {
    return error(code, msg, getLocation());
  }

  /**
   * Writes an error.
   */
  public Value error(int code, String msg, Location location)
  {
    //System.err.println("Env.error0: " + code + " . " + msg + " . " + location);
    //Thread.dumpStack();

    if (location == null || location.isUnknown()) {
      location = getLocation();
    }

    int mask = 1 << code;

    _lastErrorType = mask;
    _lastErrorMessage = msg;
    _lastErrorLocation = location;

    int errorMask = getErrorMask();

    if (log.isLoggable(Level.FINEST)) {
      QuercusException e = new QuercusException(location.getMessagePrefix() + msg);

      log.log(Level.FINEST, e.toString(), e);
    }

    if ((errorMask & mask) != 0) {
      if (log.isLoggable(Level.FINE)) {
        log.fine(this + " " + location.getMessagePrefix() + getExceptionLocation(msg));
      }
    }

    if (code >= 0 && code < _errorHandlers.length
        && _errorHandlers[code] != null) {
      Callable handler = _errorHandlers[code];

      try {
        _errorHandlers[code] = null;

        Value fileNameV = NullValue.NULL;

        String fileName = location.getFileName();

        if (fileName != null)
          fileNameV = createString(fileName);

        Value lineV = NullValue.NULL;
        int line = location.getLineNumber();
        if (line > 0)
          lineV = LongValue.create(line);

        Value context = NullValue.NULL;

        handler.call(this, LongValue.create(mask), createString(msg),
                     fileNameV, lineV, context);

        return NullValue.NULL;
      }
      catch (RuntimeException e) {
        throw e;
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
      finally {
        _errorHandlers[code] = handler;
      }
    }

    if ((errorMask & mask) != 0) {
      try {
        String fullMsg;

        if (log.isLoggable(Level.FINE)) {
          fullMsg = location.getMessagePrefix()
              + getCodeName(mask)
              + getExceptionLocation(msg);
        }
        else {
          fullMsg = location.getMessagePrefix()
              + getCodeName(mask)
              + msg
              + getFunctionLocation();
        }

        if (getIniBoolean("track_errors")) {
          setGlobalValue("php_errormsg", createString(fullMsg));
        }

        if ("stderr".equals(getIniString("display_errors"))) {
          // initial newline to match PHP
          System.err.println("\n" + fullMsg);
        }
        else if (getIniBoolean("display_errors")) {
          // initial newline to match PHP
          getOut().println("\n" + fullMsg);
        }

        if (getIniBoolean("log_errors"))
          log.info(fullMsg);
      }
      catch (IOException e) {
        log.log(Level.FINE, e.toString(), e);
      }
    }

    if ((mask & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)) != 0) {
      QuercusErrorException exn = new QuercusErrorException(msg);

      exn.fillInStackTrace();

      if (log.isLoggable(Level.FINER)) {
        log.log(Level.FINER, exn.toString(), exn);
      }

      throw exn;
    }

    return NullValue.NULL;
  }

  public Value getLastError()
  {
    if (_lastErrorType < 0) {
      return NullValue.NULL;
    }

    ArrayValueImpl array = new ArrayValueImpl();

    array.put(createString("type"), LongValue.create(_lastErrorType));
    array.put(createString("message"), createString(_lastErrorMessage));

    String file = "";
    int line = -1;
    if (_lastErrorLocation != null) {
      file = _lastErrorLocation.getFileName();
      line = _lastErrorLocation.getLineNumber();
    }

    array.put(createString("file"), createString(file));
    array.put(createString("line"), LongValue.create(line));

    return array;
  }

  /**
   * Returns the error code name.
   */
  private String getCodeName(int code)
  {
    switch (code) {
    case E_ERROR:
      return "Fatal Error: ";
    case E_WARNING:
      return "Warning: ";
    case E_PARSE:
      return "Parse Error: ";
    case E_NOTICE:
      return "Notice: ";
    case E_CORE_ERROR:
      return "Fatal Error: ";
    case E_CORE_WARNING:
      return "Warning: ";
    case E_COMPILE_ERROR:
      return "Fatal Error: ";
    case E_COMPILE_WARNING:
      return "Warning : ";
    case E_USER_ERROR:
      return "Fatal Error: ";
    case E_USER_WARNING:
      return "Warning: ";
    case E_USER_NOTICE:
      return "Notice: ";
    case E_STRICT:
      return "Notice: ";
    case E_RECOVERABLE_ERROR:
      return "Error: ";

    default:
      return String.valueOf("ErrorCode(" + code + ")");
    }
  }

  /**
   * Returns the source of an error line.
   */
  public static String []getSourceLine(Path path, int sourceLine, int length)
  {
    if (path == null)
      return null;
    else if (path instanceof NullPath) {
      // for QuercusScriptEngine.eval() where only a Reader is passed in
      // XXX: not too pretty
      return null;
    }

    ReadStream is = null;

    try {
      is = path.openRead();

      int line = 1;
      String lineString;

      for (; line < sourceLine; line++) {
        lineString = is.readLine();

        if (lineString == null)
          return null;
      }

      String []result = new String[length];

      int i = line - sourceLine;
      for (; i < length && (lineString = is.readLine()) != null; i++) {
        result[i] = lineString;
      }

      return result;
    }
    catch (IOException e) {
      log.log(Level.FINE, e.toString(), e);
    }
    finally {
      if (is != null)
        is.close();
    }

    return null;
  }

  public final Location setLocation(Location newLocation)
  {
    Location location = _location;
    _location = newLocation;

    return location;
  }

  protected final Location getLocationImpl()
  {
    return _location;
  }

  /**
   * Returns the current execution location.
   *
   * Use with care, for compiled code this can be a relatively expensive
   * operation.
   */
  public Location getLocation()
  {
    Location location = _location;

    if (location != null)
      return location;

    Expr call = peekCall(0);

    if (call != null)
      return call.getLocation();

    return Location.UNKNOWN;
  }

  public int getSourceLine(String className, int javaLine)
  {
    return javaLine;
  }

  /**
   * Returns the current function.
   */
  public String getFunctionLocation()
  {
    // XXX: need to work with compiled code, too
    Expr call = peekCall(0);

    if (call != null)
      return call.getFunctionLocation();
    else
      return "";
  }

  /**
   * Converts a boolean to the boolean value
   */
  public static Value toValue(boolean value)
  {
    return value ? BooleanValue.TRUE : BooleanValue.FALSE;
  }

  /**
   * Converts a boolean to the boolean value
   */
  public static Value toValue(long value)
  {
    return LongValue.create(value);
  }

  /**
   * Converts to a variable
   */
  public static Var toVar(Value value)
  {
    if (value instanceof Var)
      return (Var) value;
    else if (value == null)
      return new Var();
    else
      return new Var(value);
  }

  /**
   * Sets a vield variable
   */
  public static Value setFieldVar(Value oldValue, Value value)
  {
    if (value instanceof Var)
      return value;
    else if (oldValue instanceof Var)
      return new Var(value);
    else
      return value;
  }

  /**
   * Sets a reference
   */
  public static Value setRef(Value oldValue, Value value)
  {
    // php/3243
    if (value instanceof Var)
      return value;
    /*
    else if (oldValue instanceof Var) {
      oldValue.set(value);

      return oldValue;
    }
    */
    else
      return new Var(value);
  }

  /**
   * Sets a reference
   */
  public static Var setEnvRef(Var oldVar, Value value)
  {
    // 3ab7
    // XXX: need better test case, since that one isn't allowed by php

    if (value instanceof Var)
      return (Var) value;
    else {
      oldVar.set(value);

      return oldVar;
    }
  }

  /**
   * Returns the last value.
   */
  public static Value comma(Value a0, Value a1)
  {
    return a1;
  }

  /**
   * Returns the last value.
   */
  public static Value comma(Value a0, Value a1, Value a2)
  {
    return a2;
  }

  /**
   * Returns the last value.
   */
  public static Value comma(Value a0, Value a1, Value a2, Value a3)
  {
    return a3;
  }

  /**
   * Returns the last value.
   */
  public static Value comma(Value a0, Value a1, Value a2, Value a3, Value a4)
  {
    return a4;
  }

  // long comma

  /**
   * Returns the last value.
   */
  public static long comma(Value a0, long a1)
  {
    return a1;
  }

  /**
   * Returns the last value.
   */
  public static long comma(long a0, long a1)
  {
    return a1;
  }

  /**
   * Returns the last value.
   */
  public static Value comma(long a0, Value a1)
  {
    return a1;
  }

  //
  // comma
  //

  /**
   * Returns the last value.
   */
  public static double comma(Value a0, double a1)
  {
    return a1;
  }

  /**
   * Returns the last value.
   */
  public static double comma(double a0, double a1)
  {
    return a1;
  }

  /**
   * Returns the last value.
   */
  public static Value comma(double a0, Value a1)
  {
    return a1;
  }

  /**
   * Calls a parent::name method.
   */
  /*
  public Value callParentMethod(Value qThis,
                                String parentName,
                                String funName,
                                Value []args)
  {
    AbstractFunction fun
      = getClass(parentName).getFunction(funName);

    Value retValue = fun.callMethod(this, qThis, args);

    if (fun.isConstructor())
      qThis.setJavaObject(retValue);

    return retValue;
  }
  */

  /**
   * Calls a parent::name method.
   */
  /*
  public Value callParentMethod(Value qThis,
                                String parentName,
                                int hash,
                                char []name,
                                int len,
                                Value []args)
  {

    AbstractFunction fun
      = getClass(parentName).getFunction(hash, name, len);

    Value retValue = fun.callMethod(this, qThis, args);

    if (fun.isConstructor())
      qThis.setJavaObject(retValue);

    return retValue;
  }
  */

  public String toString()
  {
    return "Env[]";
  }

  /**
   * Returns ifNull if condition.isNull(), otherwise returns ifNotNull.
   */
  public Value ifNull(Value condition, Value ifNull, Value ifNotNull)
  {
    return condition.isNull() ? ifNull : ifNotNull;
  }

  /**
   * Returns the locale info.
   */
  public LocaleInfo getLocaleInfo()
  {
    if (_locale == null)
      _locale = new LocaleInfo();

    return _locale;
  }

  public long getMicroTime()
  {
    long nanoTime = _quercus.getExactTimeNanoseconds();

    if (_firstMicroTime <= 0) {
      _firstNanoTime = nanoTime;

      _firstMicroTime = _quercus.getExactTime() * 1000
                        + (_firstNanoTime % 1000000L) / 1000;

      return _firstMicroTime;
    }
    else {
      long microDiff = (nanoTime - _firstNanoTime) / 1000;

      return _firstMicroTime + microDiff;
    }
  }

  /**
   * Registers a shutdown function.
   */
  public void addShutdown(Callable callback, Value []args)
  {
    if (_shutdownList == null)
      _shutdownList = new ArrayList<Shutdown>();

    _shutdownList.add(new Shutdown(callback, args));
  }

  // XXX: hack until can clean up
  public void setGzStream(Object obj)
  {
    _gzStream = obj;
  }

  // XXX: hack until can clean up
  public Object getGzStream()
  {
    return _gzStream;
  }

  public void startDuplex(Object duplex)
  {
    throw new UnsupportedOperationException(getClass().getName());
    /*
    if (_duplex != null)
      return;

    _duplex = duplex;
    */
  }

  public void closeDuplex()
  {
    _duplex = null;

    close();
  }

  public Object getDuplex()
  {
    return _duplex;
  }

  /**
   * Called when the Env is no longer needed.
   */
  public void close()
  {
    _quercus.completeEnv(this);

    /*
    if (_duplex != null) {
      log.fine(this + " skipping close for duplex mode");
      return;
    }
    */

    try {
      // php/1l0t
      // output buffers callbacks may throw an exception
      while (_outputBuffer != null) {
        popOutputBuffer();
      }
    }
    //catch (Exception e) {
      //throw new RuntimeException(e);
    //}
    finally {
      cleanup();
    }
  }

  private void cleanup()
  {
    // cleanup is in reverse order of creation

    if (_shutdownList != null) {
      for (int i = 0; i < _shutdownList.size(); i++) {
        try {
          _shutdownList.get(i).call(this);
        }
        catch (Throwable e) {
          log.log(Level.FINE, e.toString(), e);
        }
      }
    }

    try {
      sessionWriteClose();
    } catch (Throwable e) {
      log.log(Level.FINE, e.toString(), e);
    }

    int count = 0;

    if (_objCleanupList != null) {
      int size;

      // may result in an infinite loop but that would be user error
      while ((size = _objCleanupList.size()) > 0) {
        count++;

        if (count % 100 == 0){
          updateTimeout();

          if (_isTimeout) {
            log.log(Level.WARNING, "script has timed out while calling __destruct()");

            break;
          }
        }

        ObjectValue obj = _objCleanupList.remove(size - 1);

        try {
          if (obj != null)
            obj.cleanup(this);
        }
        catch (Throwable e) {
          log.log(Level.FINER, e.toString(), e);
        }
      }
    }

    if (_cleanupList != null) {
      ArrayList<EnvCleanup> cleanupList
        = new ArrayList<EnvCleanup>(_cleanupList);

      // cleanup is in reverse order of creation
      for (int i = cleanupList.size() - 1; i >= 0; i--) {
        EnvCleanup envCleanup = cleanupList.get(i);
        try {
          if (envCleanup != null)
            envCleanup.cleanup();
        }
        catch (Throwable e) {
          log.log(Level.FINER, e.toString(), e);
        }
      }
    }

    _threadEnv.set(_oldThreadEnv);

    for (int i = 0; _removePaths != null && i < _removePaths.size(); i++) {
      Path path = _removePaths.get(i);

      try {
        path.remove();
      }
      catch (IOException e) {
        log.log(Level.FINER, e.toString(), e);
      }
    }

    AbstractFunction []fun = _fun;
    _fun = null;
    if (fun != null) {
      boolean isUsed = false;

      /**
       * Fix for Bug #4077
       * Page may be null when if the Env was created throught
       * the (QuercusContext)-Constructor and therefor every
       * parameter except the context is null.
       */
      if (_page != null && _page.setRuntimeFunction(fun)) {
        isUsed = true;
      }

      for (QuercusPage page : _includeMap.values()) {
        if (page.setRuntimeFunction(fun)) {
          isUsed = true;
        }
      }

      if (! isUsed) {
        for (int i = fun.length - 1; i >= 0; i--) {
          fun[i] = null;
        }

        _freeFunList.free(fun);
      }
    }

    ClassDef []classDef = _classDef;
    _classDef = null;
    if (classDef != null) {
      // php/0b3b
      for (int i = 0; i < classDef.length; i++) {
        classDef[i] = null;
      }

      _freeClassDefList.free(classDef);
    }

    QuercusClass []qClass = _qClass;
    _qClass = null;
    if (qClass != null) {
      for (int i = 0; i < qClass.length; i++) {
        qClass[i] = null;
      }

      _freeClassList.free(qClass);
    }

    Value []consts = _const;
    _const = null;
    if (consts != null) {
      for (int i = 0; i < consts.length; i++) {
        consts[i] = null;
      }

      _freeConstList.free(consts);
    }

    if (_gmtDate != null)
      _freeGmtDateList.free(_gmtDate);

    if (_localDate != null)
      _freeLocalDateList.free(_localDate);
  }

  public void sessionWriteClose()
  {
    SessionArrayValue session = _session;

    _session = null;

    if (session != null) {
      SessionCallback callback = getSessionCallback();

      if (callback != null) {
        String value;

        // php/1k6e
        if (session.getSize() > 0)
          value = VariableModule.serialize(this, session.getArray());
        else
          value = "";

        callback.write(this, session.getId(), value);

        callback.close(this);
      }
      else {
        _quercus.saveSession(this, session);

        Value sessionCopy = session.copy(this);

        setGlobalValue("_SESSION", sessionCopy);
        setGlobalValue("HTTP_SESSION_VARS", sessionCopy);
      }
    }
  }

  public String dbgId()
  {
    return getClass().getSimpleName() + "[" + _selfPath + "] ";
  }

  static class FieldGetEntry {
    private final String _className;
    private final StringValue _fieldName;

    FieldGetEntry(String className, StringValue fieldName)
    {
      _className = className;
      _fieldName = fieldName;
    }

    public boolean equals(Object o)
    {
      if (! (o instanceof FieldGetEntry))
        return false;

      FieldGetEntry entry = (FieldGetEntry) o;

      return entry._className.equals(_className)
             && entry._fieldName.equals(_fieldName);
    }
  }

  static class ClassKey {
    private final WeakReference<ClassDef> _defRef;
    private final WeakReference<QuercusClass> _parentRef;

    private final int _hash;

    ClassKey(ClassDef def, QuercusClass parent)
    {
      _defRef = new WeakReference<ClassDef>(def);

      if (parent != null)
        _parentRef = new WeakReference<QuercusClass>(parent);
      else
        _parentRef = null;

      // hash needs to be precalculated so losing a weak references won't
      // change the result

      int hash = 37;

      if (def != null)
        hash = 65521 * hash + def.hashCode();

      if (parent != null)
        hash = 65521 * hash + parent.hashCode();

      _hash = hash;
    }

    public int hashCode()
    {
      return _hash;
    }

    public boolean equals(Object o)
    {
      ClassKey key = (ClassKey) o;

      ClassDef aDef = _defRef.get();
      ClassDef bDef = key._defRef.get();

      if (aDef != bDef)
        return false;

      if (_parentRef == key._parentRef)
        return true;

      else if (_parentRef == null || key._parentRef == null)
        return false;

      QuercusClass aParent = _parentRef.get();
      QuercusClass bParent = key._parentRef.get();

      return (aParent != null && aParent.equals(bParent));
    }

    @Override
    public String toString()
    {
      return (getClass().getSimpleName()
              + "[" + _defRef.get() + ","
              + (_parentRef != null ? _parentRef.get() : null) + "]");
    }
  }

  static {
    SPECIAL_VARS.put(new ConstStringValue("GLOBALS"), _GLOBAL);
    SPECIAL_VARS.put(new ConstStringValue("_SERVER"), _SERVER);
    SPECIAL_VARS.put(new ConstStringValue("_GET"), _GET);
    SPECIAL_VARS.put(new ConstStringValue("_POST"), _POST);
    SPECIAL_VARS.put(new ConstStringValue("_FILES"), _FILES);
    SPECIAL_VARS.put(new ConstStringValue("_REQUEST"), _REQUEST);
    SPECIAL_VARS.put(new ConstStringValue("_COOKIE"), _COOKIE);
    SPECIAL_VARS.put(new ConstStringValue("_SESSION"), _SESSION);
    SPECIAL_VARS.put(new ConstStringValue("_ENV"), _ENV);

    SPECIAL_VARS.put(new ConstStringValue("argc"), ARGC);
    SPECIAL_VARS.put(new ConstStringValue("argv"), ARGV);

    SPECIAL_VARS.put(new ConstStringValue("HTTP_GET_VARS"), HTTP_GET_VARS);
    SPECIAL_VARS.put(new ConstStringValue("HTTP_POST_VARS"), HTTP_POST_VARS);
    SPECIAL_VARS.put(new ConstStringValue("HTTP_POST_FILES"), HTTP_POST_FILES);
    SPECIAL_VARS.put(new ConstStringValue("HTTP_COOKIE_VARS"), HTTP_COOKIE_VARS);
    SPECIAL_VARS.put(new ConstStringValue("HTTP_SERVER_VARS"), HTTP_SERVER_VARS);
    SPECIAL_VARS.put(new ConstStringValue("PHP_SELF"), PHP_SELF);
    SPECIAL_VARS.put(new ConstStringValue("HTTP_RAW_POST_DATA"), HTTP_RAW_POST_DATA);

    SPECIAL_VARS_U.put(new UnicodeBuilderValue("GLOBALS"), _GLOBAL);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_SERVER"), _SERVER);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_GET"), _GET);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_POST"), _POST);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_FILES"), _FILES);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_REQUEST"), _REQUEST);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_COOKIE"), _COOKIE);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_SESSION"), _SESSION);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("_ENV"), _ENV);

    SPECIAL_VARS_U.put(new UnicodeBuilderValue("argc"), ARGC);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("argv"), ARGV);

    SPECIAL_VARS_U.put(new UnicodeBuilderValue("HTTP_GET_VARS"), HTTP_GET_VARS);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("HTTP_POST_VARS"), HTTP_POST_VARS);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("HTTP_POST_FILES"), HTTP_POST_FILES);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("HTTP_COOKIE_VARS"), HTTP_COOKIE_VARS);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("HTTP_SERVER_VARS"), HTTP_SERVER_VARS);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("PHP_SELF"), PHP_SELF);
    SPECIAL_VARS_U.put(new UnicodeBuilderValue("HTTP_RAW_POST_DATA"), HTTP_RAW_POST_DATA);

    DEFAULT_QUERY_SEPARATOR_MAP = new int[128];
    DEFAULT_QUERY_SEPARATOR_MAP['&'] = 1;
  }
}
