/*
 * Copyright (c) 1998-2012 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;

import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.LockSupport;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.cache.Cache;
import javax.sql.DataSource;

import com.caucho.config.ConfigException;
import com.caucho.java.JavaCompilerUtil;
import com.caucho.quercus.annotation.ClassImplementation;
import com.caucho.quercus.env.AbstractJavaMethod;
import com.caucho.quercus.env.ArrayValue;
import com.caucho.quercus.env.ArrayValueImpl;
import com.caucho.quercus.env.BooleanValue;
import com.caucho.quercus.env.ConstStringValue;
import com.caucho.quercus.env.DoubleValue;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.LongValue;
import com.caucho.quercus.env.NullValue;
import com.caucho.quercus.env.QuercusClass;
import com.caucho.quercus.env.SessionArrayValue;
import com.caucho.quercus.env.StringBuilderValue;
import com.caucho.quercus.env.StringValue;
import com.caucho.quercus.env.UnicodeBuilderValue;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.expr.ExprFactory;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.lib.db.JavaSqlDriverWrapper;
import com.caucho.quercus.lib.db.JdbcDriverContext;
import com.caucho.quercus.lib.file.FileModule;
import com.caucho.quercus.lib.regexp.RegexpModule;
import com.caucho.quercus.lib.session.QuercusSessionManager;
import com.caucho.quercus.module.IniDefinition;
import com.caucho.quercus.module.IniDefinitions;
import com.caucho.quercus.module.ModuleContext;
import com.caucho.quercus.module.ModuleInfo;
import com.caucho.quercus.module.ModuleStartupListener;
import com.caucho.quercus.module.QuercusModule;
import com.caucho.quercus.page.InterpretedPage;
import com.caucho.quercus.page.PageManager;
import com.caucho.quercus.page.QuercusPage;
import com.caucho.quercus.parser.QuercusParser;
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.servlet.api.QuercusHttpServletRequest;
import com.caucho.quercus.servlet.api.QuercusHttpServletResponse;
import com.caucho.quercus.servlet.api.QuercusServletContext;
import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import com.caucho.util.LruCache;
import com.caucho.util.TimedCache;
import com.caucho.vfs.FilePath;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;

/**
 * Facade for the PHP language.
 */
public class QuercusContext
{
  private static L10N L = new L10N(QuercusContext.class);
  private static final Logger log
    = Logger.getLogger(QuercusContext.class.getName());

  private static HashSet<String> _superGlobals
    = new HashSet<String>();

  private static IniDefinitions _ini = new IniDefinitions();

  private final PageManager _pageManager;
  private final ClassLoader _loader;
  private final QuercusSessionManager _sessionManager;

  private ModuleContext _moduleContext;

  private static LruCache<String, UnicodeBuilderValue> _unicodeMap
    = new LruCache<String, UnicodeBuilderValue>(8 * 1024);

  private static LruCache<String, ConstStringValue> _stringMap
    = new LruCache<String, ConstStringValue>(8 * 1024);

  private HashMap<String, ModuleInfo> _modules
    = new HashMap<String, ModuleInfo>();

  private ArrayList<ModuleInfo> _moduleInitList
    = new ArrayList<ModuleInfo>();

  private HashSet<ModuleStartupListener> _moduleStartupListeners
    = new HashSet<ModuleStartupListener>();

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

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

  private HashMap<StringValue, AbstractFunction> _funMap
    = new HashMap<StringValue, AbstractFunction>();

  private HashMap<StringValue, AbstractFunction> _lowerFunMap
    = new HashMap<StringValue, AbstractFunction>();

  private ConcurrentHashMap<String, JavaClassDef> _javaClassWrappers
    = new ConcurrentHashMap<String, JavaClassDef>();

  private LruCache<String, String> _classNotFoundCache
    = new LruCache<String, String>(64);

  private HashMap<String, JavaClassDef> _lowerJavaClassWrappers
    = new HashMap<String, JavaClassDef>();

  private HashMap<String, Class<?>> _javaInitClassMap
    = new HashMap<String, Class<?>>();

  private final IniDefinitions _iniDefinitions = new IniDefinitions();

  private Path _iniFile;
  private HashMap<String, Value> _iniMap;

  private HashMap<Value, Value> _serverEnvMap
    = new HashMap<Value, Value>();

  private IntMap _classNameMap = new IntMap(8192);
  private String []_classNames = new String[256];
  private ClassDef []_classDefMap = new ClassDef[256];
  private QuercusClass []_classCacheMap = new QuercusClass[256];

  private IntMap _constantNameMap = new IntMap(8192);
  private int []_constantLowerMap = new int[256];
  private Value []_constantNameList = new Value[256];
  private Value []_constantMap = new Value[256];

  // protected to allow locking from pro
  protected IntMap _functionNameMap = new IntMap(8192);

  private AbstractFunction []_functionMap = new AbstractFunction[256];

  private LruCache<StringValue, QuercusProgram> _evalCache
    = new LruCache<StringValue, QuercusProgram>(4096);

  private int _includeCacheMax = 8192;
  private long _includeCacheTimeout = 10000L;

  private TimedCache<IncludeKey, Path> _includeCache;

  //private LruCache<DefinitionKey,SoftReference<DefinitionState>> _defCache
  //    = new LruCache<DefinitionKey,SoftReference<DefinitionState>>(4096);

  private long _defCacheHitCount;
  private long _defCacheMissCount;

  // XXX: needs to be a timed LRU
  //private LruCache<String, SessionArrayValue> _sessionMap
  //  = new LruCache<String, SessionArrayValue>(4096);

  private ConcurrentHashMap<String, Object> _specialMap
    = new ConcurrentHashMap<String, Object>();

  private String _scriptEncoding;

  private String _phpVersion = "5.4.0";
  private String _mySqlVersion;

  private boolean _isStrict;
  private boolean _isLooseParse;
  private boolean _isRequireSource;

  private boolean _isConnectionPool = true;

  private DataSource _database;

  private ConcurrentHashMap<String,DataSource> _databaseMap
    = new ConcurrentHashMap<String,DataSource>();

  private ConcurrentHashMap<Env,Env> _activeEnvSet
    = new ConcurrentHashMap<Env,Env>();

  private long _staticId;

  private Path _pwd;
  private Path _workDir;
  private Path _webInfDir;

  private QuercusServletContext _servletContext;

  private QuercusTimer _quercusTimer;

  private EnvTimeoutThread _envTimeoutThread;
  protected long _envTimeout = 60000L;

  // how long to sleep the env timeout thread,
  // for fast, complete tomcat undeploys
  protected static final long ENV_TIMEOUT_UPDATE_INTERVAL = 1000L;

  private long _dependencyCheckInterval = 2000L;

  private boolean _isClosed;

  private JdbcDriverContext _jdbcDriverContext;

  private Boolean _isUnicodeSemantics;

  /**
   * Constructor.
   */
  public QuercusContext()
  {
    _loader = Thread.currentThread().getContextClassLoader();
    _pageManager = createPageManager();
    _sessionManager = createSessionManager();

    _moduleContext = getLocalContext();
  }

  /**
   * Returns the current time.
   */
  public long getCurrentTime()
  {
    return _quercusTimer.getCurrentTime();
  }

  /**
   * Returns the current time in nanoseconds.
   */
  public long getExactTimeNanoseconds()
  {
    return _quercusTimer.getExactTimeNanoseconds();
  }

  /**
   * Returns the exact current time in milliseconds.
   */
  public long getExactTime()
  {
    return _quercusTimer.getExactTime();
  }

  /**
   * Returns the working directory.
   */
  public Path getPwd()
  {
    if (_pwd == null) {
      _pwd = Vfs.getPwd();
    }

    return _pwd;
  }

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

  public Path getWebInfDir()
  {
    if (_webInfDir == null) {
      _webInfDir = getPwd().lookup("WEB-INF");
    }

    return _webInfDir;
  }

  public void setWebInfDir(Path path)
  {
    _webInfDir = path;
  }

  public Path getWorkDir()
  {
    if (_workDir == null)
      _workDir = getWebInfDir().lookup("work");

    return _workDir;
  }

  public void setWorkDir(Path workDir)
  {
    _workDir = workDir;
  }

  public String getCookieName()
  {
    return "JSESSIONID";
  }

  public final long getDependencyCheckInterval()
  {
    return _dependencyCheckInterval;
  }

  public final void setDependencyCheckInterval(long ms)
  {
    _dependencyCheckInterval = ms;
  }

  public int getIncludeCacheMax()
  {
    return _includeCacheMax;
  }

  public void setIncludeCacheMax(int cacheMax)
  {
    _includeCacheMax = cacheMax;
  }

  public void setIncludeCacheTimeout(long timeout)
  {
    _includeCacheTimeout = timeout;
  }

  public long getIncludeCacheTimeout()
  {
    return _includeCacheTimeout;
  }

  public String getName()
  {
    if (isPro()) {
      return "Quercus Pro";
    }
    else {
      return "Quercus";
    }
  }

  public String getVersion()
  {
    return "Open Source " + QuercusVersion.getVersionNumber();
  }

  public String getVersionDate()
  {
    return QuercusVersion.getVersionDate();
  }

  /**
   * Returns the SAPI (Server API) name.
   */
  public String getSapiName()
  {
    // default to cgi for mediawiki-1.19.1
    return "cgi";
  }

  public boolean isRegisterArgv() {
    return getIniBoolean("register_argc_argv");
  }

  public boolean isProfile()
  {
    return false;
  }

  public int getProfileIndex(String name)
  {
    return -1;
  }

  public void setProfileProbability(double probability)
  {
  }

  protected PageManager createPageManager()
  {
    return new PageManager(this);
  }

  protected QuercusSessionManager createSessionManager()
  {
    return new QuercusSessionManager(this);
  }

  /**
   * Returns the context for this class loader.
   */
  public final ModuleContext getLocalContext()
  {
    return getLocalContext(_loader);
  }

  public ModuleContext getLocalContext(ClassLoader loader)
  {
    synchronized (this) {
      if (_moduleContext == null) {
        _moduleContext = createModuleContext(null, loader);
        _moduleContext.init();
      }
    }

    return _moduleContext;
  }

  protected ModuleContext createModuleContext(ModuleContext parent,
                                              ClassLoader loader)
  {
    return new ModuleContext(parent, loader);
  }

  /**
   * Returns the module context.
   */
  public ModuleContext getModuleContext()
  {
    return _moduleContext;
  }

  public QuercusSessionManager getQuercusSessionManager()
  {
    return _sessionManager;
  }

  /**
   * true if the pages should be compiled.
   */
  public boolean isCompile()
  {
    return _pageManager.isCompile();
  }

  /**
   * Returns true if this is the Professional version.
   */
  public boolean isPro()
  {
    return false;
  }

  /**
   * Returns true if Quercus is running under Resin.
   */
  public boolean isResin()
  {
    return false;
  }

  public void setUnicodeSemantics(boolean isUnicode)
  {
    _isUnicodeSemantics = Boolean.valueOf(isUnicode);
  }

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

  /**
   * Returns true if URLs may be arguments of include().
   */
  public boolean isAllowUrlInclude()
  {
    return getIniBoolean("allow_url_include");
  }

  /**
   * Returns true if URLs may be arguments of fopen().
   */
  public boolean isAllowUrlFopen()
  {
    return getIniBoolean("allow_url_fopen");
  }

  /**
   * Set true if pages should be compiled.
   */
  public void setCompile(boolean isCompile)
  {
    _pageManager.setCompile(isCompile);
  }

  /**
   * Set true if pages should be compiled.
   */
  public void setLazyCompile(boolean isCompile)
  {
    _pageManager.setLazyCompile(isCompile);
  }

  /**
   * true if interpreted pages should be used if pages fail to compile.
   */
  public void setCompileFailover(boolean isCompileFailover)
  {
    _pageManager.setCompileFailover(isCompileFailover);
  }

  /**
   * Returns the expected encoding of php scripts.
   */
  public String getScriptEncoding()
  {
    if (_scriptEncoding != null)
      return _scriptEncoding;
    else if (isUnicodeSemantics())
      return "utf-8";
    else
      return "utf-8";
  }

  /**
   * Sets the expected encoding of php scripts.
   */
  public void setScriptEncoding(String encoding)
  {
    _scriptEncoding = encoding;
  }

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

    String encoding = QuercusContext.INI_UNICODE_OUTPUT_ENCODING.getAsString(this);

    if (encoding == null)
      encoding = QuercusContext.INI_UNICODE_FALLBACK_ENCODING.getAsString(this);

    if (encoding == null)
      encoding = "utf-8";

    return encoding;
  }


  /**
   * Returns the mysql version to report to to PHP applications.
   * It is user set-able to allow cloaking of the underlying mysql
   * JDBC driver version for application compatibility.
   */
  public String getMysqlVersion()
  {
    return _mySqlVersion;
  }

  /**
   * Sets the mysql version to report to applications.  This cloaks
   * the underlying JDBC driver version, so that when an application
   * asks for the mysql version, this version string is returned instead.
   */
  public void setMysqlVersion(String version)
  {
    _mySqlVersion = version;
  }

  public String getPhpVersion()
  {
    return _phpVersion;
  }

  public void setPhpVersion(String version)
  {
    _phpVersion = version;
  }

  /**
   * Sets the ServletContext.
   */
  public void setServletContext(QuercusServletContext servletContext)
  {
    _servletContext = servletContext;
  }

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

  /**
   * Sets the default data source.
   */
  public void setDatabase(DataSource database)
  {
    _database = database;
  }

  /**
   * Gets the default data source.
   */
  public DataSource getDatabase()
  {
    return _database;
  }

  public JdbcDriverContext getJdbcDriverContext()
  {
    if (_jdbcDriverContext == null) {
      _jdbcDriverContext = new JdbcDriverContext();

      Value driverValue = getIniValue("quercus.jdbc_drivers");

      if (driverValue.isArray()) {
        ArrayValue array = driverValue.toArray();

        for (Map.Entry<Value,Value> entry : array.entrySet()) {

          Value key = entry.getKey();
          Value value = entry.getValue();

          _jdbcDriverContext.setProtocol(key.toString(), value.toString());
        }
      }
    }

    return _jdbcDriverContext;
  }

  /**
   * Gets the default data source.
   */
  public DataSource findDatabase(String driver, String url)
  {
    if (_database != null)
      return _database;
    else {
      try {
        String key = driver + ";" + url;

        DataSource database = _databaseMap.get(key);

        if (database != null)
          return database;

        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        Class<?> cls = loader.loadClass(driver);

        Object ds = cls.newInstance();

        if (ds instanceof DataSource)
          database = (DataSource) ds;
        else
          database = new JavaSqlDriverWrapper((java.sql.Driver) ds, url);

        _databaseMap.put(key, database);

        return database;
      } catch (ClassNotFoundException e) {
        throw new QuercusModuleException(e);
      } catch (InstantiationException e) {
        throw new QuercusModuleException(e);
      } catch (IllegalAccessException e) {
        throw new QuercusModuleException(e);
      }
    }
  }

  /**
   * Marks the connection for removal from the connection pool.
   */
  public void markForPoolRemoval(Connection conn)
  {
  }

  /**
   * Unwrap connection if necessary.
   */
  public Connection getConnection(Connection conn)
  {
    return conn;
  }

  /**
   * Unwrap statement if necessary.
   */
  public java.sql.Statement getStatement(java.sql.Statement stmt)
  {
    return stmt;
  }

  /**
   * Sets the strict mode.
   */
  public void setStrict(boolean isStrict)
  {
    _isStrict = isStrict;
  }

  /**
   * Gets the strict mode.
   */
  public boolean isStrict()
  {
    return _isStrict;
  }

  /**
   * Sets the loose mode.
   */
  public void setLooseParse(boolean isLoose)
  {
    _isLooseParse = isLoose;
  }

  /**
   * Gets the loose mode.
   */
  public boolean isLooseParse()
  {
    return _isLooseParse;
  }

  /**
   * Gets the max size of the page cache.
   */
  public int getPageCacheSize()
  {
    return _pageManager.getPageCacheSize();
  }

  /**
   * Sets the capacity of the page cache.
   */
  public void setPageCacheSize(int size)
  {
    _pageManager.setPageCacheSize(size);
  }

  /**
   * Gets the max size of the regexp cache.
   */
  public int getRegexpCacheSize()
  {
    return RegexpModule.getRegexpCacheSize();
  }

  /**
   * Sets the capacity of the regexp cache.
   */
  public void setRegexpCacheSize(int size)
  {
    RegexpModule.setRegexpCacheSize(size);
  }

  /**
   * Set to true if compiled pages need to be backed by php source files.
   */
  public void setRequireSource(boolean isRequireSource)
  {
    _isRequireSource = isRequireSource;
  }

  /**
   * Returns whether the php source is required for compiled files.
   */
  public boolean isRequireSource()
  {
    return _isRequireSource;
  }

  /**
   * Turns connection pooling on or off.
   */
  public void setConnectionPool(boolean isEnable)
  {
    _isConnectionPool = isEnable;
  }

  /**
   * Returns true if connections should be pooled.
   */
  public boolean isConnectionPool()
  {
    return _isConnectionPool;
  }

  private void initJavaClasses()
  {
    for (Map.Entry<String, Class<?>> entry : _javaInitClassMap.entrySet()) {
      String name = entry.getKey();
      Class<?> cls = entry.getValue();

      try {
        if (cls.isAnnotationPresent(ClassImplementation.class))
          _moduleContext.introspectJavaImplClass(name, cls, null);
        else
          _moduleContext.introspectJavaClass(name, cls, null, null);
      }
      catch (Exception e) {
        throw new QuercusRuntimeException(e);
      }
    }
  }

  /**
   * Adds a java class
   */
  public void addJavaClass(String name, Class<?> type)
    throws ConfigException
  {
    _javaInitClassMap.put(name, type);
  }

  /**
   * Adds a java class
   */
  public void addJavaClass(String phpName, String className)
  {
    try {
      Class<?> type = Class.forName(className, false, _loader);

      addJavaClass(phpName, type);
    }
    catch (ClassNotFoundException e) {
      throw new QuercusRuntimeException(L.l("`{0}' not valid: {1}",
                                            className,
                                            e.toString()), e);
    }
  }

  /**
   * Adds a impl class
   */
  public void addImplClass(String name, Class<?> type)
    throws ConfigException
  {
    throw new UnsupportedOperationException(
      "XXX: need to merge with ModuleContext: " + name);
    /*
    try {
      introspectJavaImplClass(name, type, null);
    } catch (Exception e) {
      throw ConfigException.create(e);
    }
    */
  }

  /**
   * Adds a java class
   */
  public JavaClassDef getJavaClassDefinition(Class<?> type, String className)
  {
    JavaClassDef def;

    if (_classNotFoundCache.get(className) != null)
      return null;

    def = _javaClassWrappers.get(className);

    if (def == null) {
      try {
        def = getModuleContext().getJavaClassDefinition(type, className);

        int id = getClassId(className);
        _classDefMap[id] = def;

        _javaClassWrappers.put(className, def);
      } catch (RuntimeException e) {
        throw e;
      } catch (Exception e) {
        throw new QuercusRuntimeException(e);
      }
    }

    def.init();

    return def;
  }

  /**
   * Adds a java class
   */
  public JavaClassDef getJavaClassDefinition(String className)
  {
    JavaClassDef def;

    if (_classNotFoundCache.get(className) != null)
      return null;

    def = _javaClassWrappers.get(className);

    if (def == null) {
      try {
        def = getModuleContext().getJavaClassDefinition(className);

        _javaClassWrappers.put(className, def);
      } catch (RuntimeException e) {
        _classNotFoundCache.put(className, className);

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

    def.init();

    return def;
  }

  /**
   * Finds the java class wrapper.
   */
  public ClassDef findJavaClassWrapper(String name)
  {
    ClassDef def = _javaClassWrappers.get(name);

    if (def != null)
      return def;

    return _lowerJavaClassWrappers.get(name.toLowerCase(Locale.ENGLISH));
  }

  /**
   * Sets an ini file.
   */
  public void setIniFile(Path path)
  {
    if (path != null && path.canRead()) {
      _iniFile = path;
    }
  }

  /**
   * Returns the ini file.
   */
  public Path getIniFile()
  {
    return _iniFile;
  }

  /**
   * Returns the IniDefinitions for all ini that have been defined by modules.
   */
  public IniDefinitions getIniDefinitions()
  {
    return _iniDefinitions;
  }

  /**
   * 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 void setIni(String name, Value value)
  {
    _iniDefinitions.get(name).set(this, value);
  }

  /**
   * Sets an ini value.
   */
  public void setIni(String name, String value)
  {
    _iniDefinitions.get(name).set(this, value);
  }

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

  /**
   * Returns an ini value as a long.
   */
  public long getIniLong(String name)
  {
    return _iniDefinitions.get(name).getAsLongValue(this).toLong();
  }

  /**
   * Returns an ini value.
   */
  public Value getIniValue(String name)
  {
    return _iniDefinitions.get(name).getValue(this);
  }

  /**
   * Returns an ini value.
   */
  public String getIniString(String name)
  {
    return _iniDefinitions.get(name).getValue(this).toJavaString();
  }

  /**
   * Sets a server env value.
   */
  public void setServerEnv(String name, String value)
  {
    // php/3j58
    if (isUnicodeSemantics())
      setServerEnv(createUnicodeString(name), createUnicodeString(value));
    else
      setServerEnv(createString(name), createString(value));
  }

  /**
   * Sets a server env value.
   */
  public void setServerEnv(StringValue name, StringValue value)
  {
    _serverEnvMap.put(name, value);
  }

  /**
   * Gets a server env value.
   */
  public Value getServerEnv(StringValue name)
  {
    return _serverEnvMap.get(name);
  }

  /**
   * Returns the server env map.
   */
  public HashMap<Value,Value> getServerEnvMap()
  {
    return _serverEnvMap;
  }

  /**
   * Returns the compile classloader
   */
  public ClassLoader getCompileClassLoader()
  {
    return null;
  }

  /**
   * Sets the compile classloader
   */
  public void setCompileClassLoader(ClassLoader loader)
  {
  }

  /**
   * Returns the relative path.
   */
  public final String getClassName(Path path)
  {
    if (path == null)
      return "tmp.eval";

    String pathName = path.getFullPath();
    String pwdName = getPwd().getFullPath();

    String relPath;

    if (pathName.startsWith(pwdName))
      relPath = pathName.substring(pwdName.length());
    else
      relPath = pathName;

    return "_quercus." + JavaCompilerUtil.mangleName(relPath);
  }

  /**
   * Returns an include path.
   */
  public Path getIncludeCache(StringValue include,
                              String includePath,
                              Path pwd,
                              Path scriptPwd)
  {
    IncludeKey key = new IncludeKey(include, includePath, pwd, scriptPwd);

    Path path = _includeCache.get(key);

    return path;
  }

  /**
   * Adds an include path.
   */
  public void putIncludeCache(StringValue include,
                              String includePath,
                              Path pwd,
                              Path scriptPwd,
                              Path path)
  {
    IncludeKey key = new IncludeKey(include, includePath, pwd, scriptPwd);

    _includeCache.put(key, path);
  }

  /**
   * Returns the definition cache hit count.
   */
  public long getDefCacheHitCount()
  {
    return _defCacheHitCount;
  }

  /**
   * Returns the definition cache miss count.
   */
  public long getDefCacheMissCount()
  {
    return _defCacheMissCount;
  }

  /**
   * Returns the definition state for an include.
   */
  /*
  public DefinitionState getDefinitionCache(DefinitionKey key)
  {
    SoftReference<DefinitionState> defStateRef = _defCache.get(key);

    if (defStateRef != null) {
      DefinitionState defState = defStateRef.get();

      if (defState != null) {
        _defCacheHitCount++;

        return defState.copyLazy();
      }
    }

    _defCacheMissCount++;

    return null;
  }
  */

  /**
   * Returns the definition state for an include.
   */
  /*
  public void putDefinitionCache(DefinitionKey key,
                                 DefinitionState defState)
  {
    _defCache.put(key, new SoftReference<DefinitionState>(defState.copy()));
  }
  */

  /**
   * Clears the definition cache.
   */
  public void clearDefinitionCache()
  {
    // _defCache.clear();
  }

  /**
   * Returns true if a precompiled page exists
   */
  public boolean includeExists(Path path)
  {
    return _pageManager.precompileExists(path);
  }

  /**
   * Parses a quercus program.
   *
   * @param path the source file path
   * @return the parsed program
   * @throws IOException
   */
  public QuercusPage parse(Path path)
    throws IOException
  {
    return _pageManager.parse(path);
  }

  /**
   * Parses a quercus program.
   *
   * @param path the source file path
   * @return the parsed program
   * @throws IOException
   */
  public QuercusPage parse(Path path, String fileName, int line)
    throws IOException
  {
    return _pageManager.parse(path, fileName, line);
  }

  /**
   * Parses a quercus program.
   *
   * @param path the source file path
   * @return the parsed program
   * @throws IOException
   */
  public QuercusPage parse(ReadStream is)
    throws IOException
  {
    return new InterpretedPage(QuercusParser.parse(this, is));
  }

  /**
   * Parses a quercus string.
   *
   * @param code the source code
   * @return the parsed program
   * @throws IOException
   */
  public QuercusProgram parseCode(StringValue code)
    throws IOException
  {
    QuercusProgram program = _evalCache.get(code);

    if (program == null) {
      program = QuercusParser.parseEval(this, code);
      _evalCache.put(code, program);
    }

    return program;
  }

  /**
   * Parses a quercus string.
   *
   * @param code the source code
   * @return the parsed program
   * @throws IOException
   */
  public QuercusProgram parseEvalExpr(StringValue code)
    throws IOException
  {
    // XXX: possible conflict with parse eval because of the
    // return value changes
    QuercusProgram program = _evalCache.get(code);

    if (program == null) {
      program = QuercusParser.parseEvalExpr(this, code);
      _evalCache.put(code, program);
    }

    return program;
  }

  /**
   * Parses a function.
   *
   * @param args the arguments
   * @param code the source code
   * @return the parsed program
   * @throws IOException
   */
  public AbstractFunction parseFunction(String name, String args, String code)
    throws IOException
  {
    return QuercusParser.parseFunction(this, name, args, code);
  }

  /**
   * Returns the function with the given name.
   */
  public AbstractFunction findFunction(StringValue name)
  {
    AbstractFunction fun = _funMap.get(name);

    if ((fun == null) && ! isStrict()) {
      fun = _lowerFunMap.get(name.toLowerCase(Locale.ENGLISH));
    }

    return fun;
  }

  /**
   * Returns the function with the given name.
   */
  public AbstractFunction findFunctionImpl(StringValue name)
  {
    AbstractFunction fun = _funMap.get(name);

    return fun;
  }

  /**
   * Returns the function with the given name.
   */
  public AbstractFunction findLowerFunctionImpl(StringValue lowerName)
  {
    AbstractFunction fun = _lowerFunMap.get(lowerName);

    return fun;
  }

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

    for (StringValue name : _funMap.keySet()) {
      internal.put(name);
    }

    return internal;
  }

  //
  // name to id mappings
  //

  /**
   * Returns the id for a function name.
   */
  public int getFunctionId(StringValue name)
  {
    int id = _functionNameMap.get(name);

    if (id >= 0) {
      return id;
    }

    if (! isStrict()) {
      name = name.toLowerCase(Locale.ENGLISH);
    }

    if (name.startsWith("\\")) {
      // php/0m18
      name = name.substring(1);
    }

    id = _functionNameMap.get(name);

    if (id >= 0) {
      return id;
    }

    synchronized (_functionNameMap) {
      id = _functionNameMap.get(name);

      if (id >= 0) {
        return id;
      }

      // 0 is used for an undefined function
      // php/1p0g
      id = _functionNameMap.size() + 1;

      _functionNameMap.put(name, id);

      extendFunctionMap(name, id);
    }

    return id;
  }

  protected void extendFunctionMap(StringValue name, int id)
  {
    if (_functionMap.length <= id) {
      AbstractFunction []functionMap = new AbstractFunction[id + 256];
      System.arraycopy(_functionMap, 0,
                       functionMap, 0, _functionMap.length);
      _functionMap = functionMap;
    }

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

    _functionMap[id] = new UndefinedFunction(id, name.toString(), globalId);
  }

  /**
   * Returns the id for a function name.
   */
  public int findFunctionId(StringValue name)
  {
    int id = _functionNameMap.get(name);

    if (id >= 0) {
      return id;
    }

    if (! isStrict()) {
      name = name.toLowerCase(Locale.ENGLISH);
    }

    if (name.startsWith("\\")) {
      name = name.substring(1);
    }

    id = _functionNameMap.get(name);

    // IntMap is internally synchronized
    return id;
  }

  /**
   * Returns the number of functions
   */
  public int getFunctionIdCount()
  {
    return _functionNameMap.size();
  }

  /**
   * Returns the undefined functions
   */
  public AbstractFunction []getFunctionMap()
  {
    return _functionMap;
  }

  public int setFunction(StringValue name, AbstractFunction fun)
  {
    int id = getFunctionId(name);

    _functionMap[id] = fun;

    return id;
  }

  /**
   * Returns the id for a class name.
   */
  public int getClassId(String className)
  {
    int id = _classNameMap.get(className);

    if (id >= 0) {
      return id;
    }

    if (className.startsWith("\\")) {
      className = className.substring(1);
    }

    id = _classNameMap.get(className);

    if (id >= 0) {
      return id;
    }

    synchronized (_classNameMap) {
      String name = className.toLowerCase(Locale.ENGLISH);

      id = _classNameMap.get(name);

      if (id >= 0) {
        _classNameMap.put(className, id);

        return id;
      }

      id = _classNameMap.size();

      if (_classDefMap.length <= id) {
        String []classNames = new String[id + 256];
        System.arraycopy(_classNames, 0,
                         classNames, 0,
                         _classNames.length);
        _classNames = classNames;

        ClassDef []classDefMap = new ClassDef[_classNames.length];
        System.arraycopy(_classDefMap, 0,
                         classDefMap, 0,
                         _classDefMap.length);
        _classDefMap = classDefMap;

        QuercusClass []classCacheMap = new QuercusClass[_classNames.length];
        System.arraycopy(_classCacheMap, 0,
                         classCacheMap, 0,
                         _classCacheMap.length);
        _classCacheMap = classCacheMap;
      }

      _classNames[id] = className;

      // _classMap[id] = new UndefinedClass(name);

      _classNameMap.put(className, id);
      _classNameMap.put(name, id);
    }

    return id;
  }

  public String getClassName(int id)
  {
    return _classNames[id];
  }

  /**
   * Returns the id for a function name.
   */
  public int findClassId(String name)
  {
    return _classNameMap.get(name);
  }

  /**
   * Returns the number of classes
   */
  public int getClassIdCount()
  {
    return _classNameMap.size();
  }

  /**
   * Returns the undefined functions
   */
  public ClassDef []getClassDefMap()
  {
    return _classDefMap;
  }

  /**
   * Returns the class def with the given index.
   */
  public ClassDef getClassDef(int id)
  {
    return _classDefMap[id];
  }

  /**
   * Returns the undefined functions
   */
  public QuercusClass []getClassCacheMap()
  {
    return _classCacheMap;
  }

  /**
   * Returns the undefined functions
   */
  public QuercusClass getCachedClass(int id)
  {
    return _classCacheMap[id];
  }

  /**
   * Returns the undefined functions
   */
  public void setCachedClass(int id, QuercusClass qClass)
  {
    _classCacheMap[id] = qClass;
  }

  /**
   * Returns the id for a constant
   */
  public int getConstantId(String name)
  {
    // php/3j12
    if (isUnicodeSemantics())
      return getConstantId(new UnicodeBuilderValue(name));
    else
      return getConstantId(new ConstStringValue(name));
  }

  /**
   * Returns the id for a constant
   */
  public int getConstantId(StringValue name)
  {
    int id = _constantNameMap.get(name);

    if (id >= 0)
      return id;

    synchronized (_constantNameMap) {
      id = _constantNameMap.get(name);

      if (id >= 0)
        return id;

      // php/313j
      id = _constantNameMap.size() + 1;

      if (_classDefMap.length <= id) {
        Value []constantMap = new Value[id + 256];
        System.arraycopy(_constantMap, 0,
                         constantMap, 0,
                         _constantMap.length);
        _constantMap = constantMap;

        Value []constantNameList = new Value[id + 256];
        System.arraycopy(_constantNameList, 0,
                         constantNameList, 0,
                         _constantNameList.length);
        _constantNameList = constantNameList;

        int []constantLowerMap = new int[_constantMap.length];
        System.arraycopy(_constantLowerMap, 0,
                         constantLowerMap, 0,
                         _constantLowerMap.length);
        _constantLowerMap = constantLowerMap;
      }

      // XXX: i18n
      _constantNameList[id] = name;

      // php/1a0g, php/1d06
      _constantNameMap.put(name, id);

      // php/050a - only case-insensitive constants should add lower case,
      // i.e. use addLowerConstantId
    }

    return id;
  }

  /**
   * Returns the id for a constant
   */
  public int addLowerConstantId(StringValue name)
  {
    int id = getConstantId(name);

    int lowerId = getConstantId(name.toLowerCase());

    _constantLowerMap[id] = lowerId;

    return id;
  }

  /**
   * Returns the name map.
   */
  public int getConstantLower(int id)
  {
    return _constantLowerMap[id];
  }

  /**
   * Returns the constant id.
   */
  public int getConstantLowerId(String name)
  {
    return getConstantId(name.toLowerCase(Locale.ENGLISH));
  }

  /**
   * Returns the name map.
   */
  public Value getConstantName(int id)
  {
    return _constantNameList[id];
  }

  /**
   * Returns the name map.
   */
  public Value []getConstantMap()
  {
    return _constantMap;
  }

  /**
   * Returns the number of defined constants
   */
  public int getConstantIdCount()
  {
    return _constantNameMap.size();
  }

  /**
   * Returns true if the variable is a superglobal.
   */
  public static boolean isSuperGlobal(StringValue name)
  {
    return _superGlobals.contains(name.toString());
  }

  /**
   * Returns the stdClass definition.
   */
  public QuercusClass getStdClass()
  {
    return _moduleContext.getStdClass();
  }

  /**
   * Returns the class with the given name.
   */
  public ClassDef findClass(String name)
  {
    int id = getClassId(name);

    return _classDefMap[id];
  }

  /**
   * Returns the class maps.
   */
  public HashMap<String, ClassDef> getClassMap()
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Returns the module with the given name.
   */
  public QuercusModule findModule(String name)
  {
    ModuleInfo moduleInfo =  _modules.get(name);
    QuercusModule module = null;

    if (moduleInfo != null)
      module = moduleInfo.getModule();
    else
      module = getModuleContext().findModule(name);

    if (module == null)
      throw new IllegalStateException(L.l("'{0}' is an unknown quercus module",
                                          name));

    return module;
  }

  /**
   * Returns a list of the modules that have some startup code to run.
   */
  public HashSet<ModuleStartupListener> getModuleStartupListeners()
  {
    return _moduleStartupListeners;
  }

  /**
   * Returns true if an extension is loaded.
   */
  public boolean isExtensionLoaded(String name)
  {
    return _extensionSet.contains(name)
           || _extensionSetLowerCase.contains(name.toLowerCase(Locale.ENGLISH));
  }

  /**
   * Returns the loaded extensions.
   */
  public HashSet<String> getLoadedExtensions()
  {
    return _extensionSet;
  }

  /**
   * Returns true if an extension is loaded.
   */
  public Value getExtensionFuncs(String name)
  {
    ArrayValue value = null;

    for (ModuleInfo moduleInfo : _modules.values()) {
      Set<String> extensionSet = moduleInfo.getLoadedExtensions();

      if (extensionSet.contains(name)) {
        for (String functionName : moduleInfo.getFunctions().keySet()) {
          if (value == null)
            value = new ArrayValueImpl();

          value.put(functionName);
        }
      }
    }

    if (value != null)
      return value;
    else
      return BooleanValue.FALSE;
  }

  public Collection<ModuleInfo> getModules()
  {
    return _modules.values();
  }

  /**
   * Initialize the engine
   */
  public void init()
  {
    initLocal();

    initModules();
    initClasses();

    if (_iniFile != null) {
      Env env = new Env(this);

      Value result = FileModule.parse_ini_file(env, _iniFile, false);

      if (result instanceof ArrayValue) {
        ArrayValue array = (ArrayValue) result;

        for (Map.Entry<Value,Value> entry : array.entrySet()) {
          String key = entry.getKey().toString();
          Value value = entry.getValue();

          setIni(key, value);
        }
      }
    }

    if (_isUnicodeSemantics == null) {
      _isUnicodeSemantics = getIniBoolean("unicode.semantics");
    }

    _moduleContext.setUnicodeSemantics(_isUnicodeSemantics);

    for (Map.Entry<String,String> entry : System.getenv().entrySet()) {
      _serverEnvMap.put(createString(entry.getKey()),
                        createString(entry.getValue()));
    }

    _workDir = getWorkDir();

    _iniDefinitions.addAll(_ini);

    _includeCache = new TimedCache<IncludeKey, Path>(getIncludeCacheMax(),
                                                     getIncludeCacheTimeout());
  }

  public void addInitModule(QuercusModule module)
  {
    ModuleInfo info = new ModuleInfo(module.getClass().getName(),
                                     module);

    _moduleInitList.add(info);
  }

  /**
   * Initializes from the ModuleContext
   */
  private void initModules()
  {
    HashSet<String> disableSet = null;

    String value = getIniString("disable_functions");
    if (value != null) {
      disableSet = new HashSet<String>();

      String[] values = value.split(",");

      for (String name : values) {
        disableSet.add(name.trim());
      }
    }


    for (ModuleInfo info : _moduleInitList) {
      initModuleInfo(info, disableSet);
    }

    for (ModuleInfo info : _moduleContext.getModules()) {
      initModuleInfo(info, disableSet);
    }
  }

  private void initModuleInfo(ModuleInfo info, HashSet<String> disableSet)
  {
    _modules.put(info.getName(), info);

    if (info.getModule() instanceof ModuleStartupListener)
      _moduleStartupListeners.add((ModuleStartupListener)info.getModule());

    for (String ext : info.getLoadedExtensions()) {
      _extensionSet.add(ext);
      _extensionSetLowerCase.add(ext.toLowerCase(Locale.ENGLISH));
    }

    Map<StringValue, Value> map;

    if (isUnicodeSemantics())
      map = info.getUnicodeConstMap();
    else
      map = info.getConstMap();

    if (map != null) {
      for (Map.Entry<StringValue,Value> entry : map.entrySet()) {
        int id = getConstantId(entry.getKey());

        _constantMap[id] = entry.getValue();
      }
    }

    _iniDefinitions.addAll(info.getIniDefinitions());

    for (Map.Entry<String, Method[]> entry
           : info.getFunctions().entrySet()) {
      String funName = entry.getKey();
      Method[] methods = entry.getValue();

      if (disableSet != null && disableSet.contains(funName)) {
        continue;
      }

      AbstractJavaMethod fun
        = _moduleContext.createStaticFunction(info.getModule(), methods[0]);

      for (int i = 1; i < methods.length; i++) {
        AbstractJavaMethod overload
          = _moduleContext.createStaticFunction(info.getModule(), methods[i]);

        fun = fun.overload(overload);
      }

      StringValue funNameV = createString(funName);

      _funMap.put(funNameV, fun);
      _lowerFunMap.put(funNameV.toLowerCase(Locale.ENGLISH), fun);

      setFunction(funNameV, fun);
    }
  }

  /**
   * Initializes from the ModuleContext
   */
  private void initClasses()
  {
    initJavaClasses();

    ModuleContext context = _moduleContext;

    for (Map.Entry<String,JavaClassDef> entry : context.getWrapperMap().entrySet()) {
      String name = entry.getKey();
      JavaClassDef def = entry.getValue();

      _javaClassWrappers.put(name, def);
      _lowerJavaClassWrappers.put(name.toLowerCase(Locale.ENGLISH), def);
    }

    for (Map.Entry<String,ClassDef> entry : context.getClassMap().entrySet()) {
      String name = entry.getKey();
      ClassDef def = entry.getValue();

      int id = getClassId(name);

      _classDefMap[id] = def;
    }
  }

  /**
   * Creates a string.  Because these strings are typically Java
   * constants, they fit into a lru cache.
   */
  public UnicodeBuilderValue createUnicodeString(String name)
  {
    UnicodeBuilderValue value = _unicodeMap.get(name);

    if (value == null) {
      value = new UnicodeBuilderValue(name);

      _unicodeMap.put(name, value);
    }

    return value;
  }

  /**
   * Creates a string.  Because these strings are typically Java
   * constants, they fit into a lru cache.
   */
  public StringValue createString(String name)
  {
    if (isUnicodeSemantics()) {
      UnicodeBuilderValue value = _unicodeMap.get(name);

      if (value == null) {
        if (isUnicodeSemantics()) {
          value = new UnicodeBuilderValue(name);
        }

        _unicodeMap.put(name, value);
      }

      return value;
    }
    else {
      ConstStringValue value = _stringMap.get(name);

      if (value == null) {
        value = new ConstStringValue(name);

        _stringMap.put(name, value);
      }

      return value;
    }
  }

  /**
   * Returns a named constant.
   */
  public Value getConstant(int id)
  {
    return _constantMap[id];
  }

  public StringValue createStaticName()
  {
    StringValue str = createString("s" + _staticId++);

    return str;
  }

  public Cache getSessionCache()
  {
    return null;
  }

  public void setSessionTimeout(long sessionTimeout)
  {
  }

  /**
   * Loads the session from the backing.
   */
  public SessionArrayValue loadSession(Env env, String sessionId)
  {
    long now = env.getCurrentTime();

    SessionArrayValue session
      = _sessionManager.getSession(env, sessionId, now);

    if (session == null)
      session = _sessionManager.createSession(env, sessionId, now);

    return session;
  }

  /**
   * Saves the session to the backing.
   */
  public void saveSession(Env env, SessionArrayValue session)
  {
    _sessionManager.saveSession(env, session);
  }

  /**
   * Removes the session from the backing.
   */
  public void destroySession(String sessionId)
  {
    _sessionManager.removeSession(sessionId);
  }

  /**
   * Loads a special value
   */
  public Object getSpecial(String key)
  {
    return _specialMap.get(key);
  }

  /**
   * Saves a special value
   */
  public void setSpecial(String key, Object value)
  {
    _specialMap.put(key, value);
  }

  public static Value objectToValue(Object obj)
  {
    if (obj == null)
      return NullValue.NULL;
    else if (Byte.class.equals(obj.getClass())
             || Short.class.equals(obj.getClass())
             || Integer.class.equals(obj.getClass())
             || Long.class.equals(obj.getClass())) {
      return LongValue.create(((Number) obj).longValue());
    } else if (Float.class.equals(obj.getClass())
               || Double.class.equals(obj.getClass())) {
      return DoubleValue.create(((Number) obj).doubleValue());
    } else if (String.class.equals(obj.getClass())) {
      // XXX: i18n
      return new ConstStringValue((String) obj);
    } else {
      // XXX: unknown types, e.g. Character?

      return null;
    }
  }

  /**
   * Initialize local configuration, e.g. finding the PHP and PEAR libraries
   */
  protected void initLocal()
  {
    StringBuilder sb = new StringBuilder(".");

    Path pwd = getPwd();

    String []paths = new String[] {
      "/usr/share/php", "/usr/lib/php", "/usr/local/lib/php",
      "/usr/share/pear", "/usr/lib/pear", "/usr/local/lib/pear"
    };

    for (String path : paths) {
      if (pwd.lookup(path).isDirectory()) {
        sb.append(":").append(pwd.lookup(path).getPath());
      }
    }

    setIni("include_path", sb.toString());
  }

  public void start()
  {
    try {
      _quercusTimer = new QuercusTimer();

      _envTimeoutThread = new EnvTimeoutThread();
      _envTimeoutThread.start();
    } catch (Exception e) {
      log.log(Level.FINE, e.getMessage(), e);
    }
  }

  public Env createEnv(QuercusPage page,
                       WriteStream out,
                       QuercusHttpServletRequest request,
                       QuercusHttpServletResponse response)
  {
    return new Env(this, page, out, request, response);
  }

  public ExprFactory createExprFactory()
  {
    return new ExprFactory();
  }

  protected Map<Env,Env> getActiveEnvSet()
  {
    return _activeEnvSet;
  }

  public void startEnv(Env env)
  {
    _activeEnvSet.put(env, env);
  }

  public void completeEnv(Env env)
  {
    _activeEnvSet.remove(env);
  }

  protected boolean isClosed()
  {
    return _isClosed;
  }

  public void close()
  {
    synchronized (this) {
      if (_isClosed) {
        return;
      }

      _isClosed = true;
    }

    _sessionManager.close();
    _pageManager.close();

    EnvTimeoutThread envTimeoutThread = _envTimeoutThread;
    _envTimeoutThread = null;

    if (envTimeoutThread != null) {
      envTimeoutThread.shutdown();
    }

    QuercusTimer quercusTimer = _quercusTimer;
    _quercusTimer = null;

    if (quercusTimer != null) {
      quercusTimer.shutdown();
    }
  }

  /**
   * Calls close().
   */
  @Override
  protected void finalize()
    throws Throwable
  {
    super.finalize();

    close();
  }

  static class IncludeKey {
    private final StringValue _include;
    private final String _includePath;
    private final Path _pwd;
    private final Path _scriptPwd;

    IncludeKey(StringValue include,
               String includePath,
               Path pwd,
               Path scriptPwd)
    {
      _include = include;
      _includePath = includePath;
      _pwd = pwd;
      _scriptPwd = scriptPwd;
    }

    public int hashCode()
    {
      int hash = 37;

      hash = 65537 * hash + _include.hashCode();
      hash = 65537 * hash + _includePath.hashCode();
      if (_pwd != null)
        hash = 65537 * hash + _pwd.hashCode();
      hash = 65537 * hash + _scriptPwd.hashCode();

      return hash;
    }

    public boolean equals(Object o)
    {
      if (! (o instanceof IncludeKey))
        return false;

      IncludeKey key = (IncludeKey) o;

      return (_include.equals(key._include)
              && _includePath.equals(key._includePath)
              && _pwd.equals(key._pwd)
              && _scriptPwd.equals(key._scriptPwd));
    }
  }

  class EnvTimeoutThread extends Thread {
    private volatile boolean _isRunnable = true;
    private final long _timeout = _envTimeout;

    private long _quantumCount;

    EnvTimeoutThread()
    {
      super("quercus-env-timeout");

      setDaemon(true);
      //setPriority(Thread.MAX_PRIORITY);
    }

    public void shutdown()
    {
      _isRunnable = false;

      LockSupport.unpark(this);

      try {
        Thread.sleep(ENV_TIMEOUT_UPDATE_INTERVAL * 2);
      }
      catch (InterruptedException e) {
      }
    }

    public void run()
    {
      while (_isRunnable) {
        if (_quantumCount >= _timeout) {
          _quantumCount = 0;

          try {
            ArrayList<Env> activeEnv
              = new ArrayList<Env>(_activeEnvSet.keySet());

            for (Env env : activeEnv) {
              env.updateTimeout();
            }

          } catch (Throwable e) {
          }
        }
        else {
          _quantumCount += ENV_TIMEOUT_UPDATE_INTERVAL;
        }

        LockSupport.parkNanos(ENV_TIMEOUT_UPDATE_INTERVAL * 1000000L);
      }
    }
  }

  static {
    _superGlobals.add("GLOBALS");
    _superGlobals.add("_COOKIE");
    _superGlobals.add("_ENV");
    _superGlobals.add("_FILES");
    _superGlobals.add("_GET");
    _superGlobals.add("_POST");
    _superGlobals.add("_SERVER");
    _superGlobals.add("_SESSION");
    _superGlobals.add("_REQUEST");

    /*
    String includePath;

    if (Path.isWindows())
      includePath = "."
                    + FileModule.PATH_SEPARATOR
                    + "C:\\php5\\pear";
    else
      includePath = "."
                    + FileModule.PATH_SEPARATOR
                    + "/usr/share/php"
                    + FileModule.PATH_SEPARATOR
                    + "/usr/share/pear";

    INI_INCLUDE_PATH = _ini.add(
      "include_path", includePath, IniDefinition.PHP_INI_ALL);
    */
  }

  public static final IniDefinition INI_INCLUDE_PATH
    = _ini.add("include_path", ".", IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_REGISTER_LONG_ARRAYS
    = _ini.add("register_long_arrays", true, IniDefinition.PHP_INI_PERDIR);
  public static final IniDefinition INI_ALWAYS_POPULATE_RAW_POST_DATA
    = _ini.add("always_populate_raw_post_data", false, IniDefinition.PHP_INI_PERDIR);

  // unicode ini
  public static final IniDefinition INI_UNICODE_SEMANTICS
    = _ini.add("unicode.semantics", false, IniDefinition.PHP_INI_SYSTEM);
  public static final IniDefinition INI_UNICODE_FALLBACK_ENCODING
    = _ini.add("unicode.fallback_encoding", "utf-8", IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_UNICODE_FROM_ERROR_MODE
    = _ini.add("unicode.from_error_mode", "2", IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_UNICODE_FROM_ERROR_SUBST_CHAR
    = _ini.add("unicode.from_error_subst_char", "3f", IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_UNICODE_HTTP_INPUT_ENCODING
    = _ini.add("unicode.http_input_encoding", null, IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_UNICODE_OUTPUT_ENCODING
    = _ini.add("unicode.output_encoding", null, IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_UNICODE_RUNTIME_ENCODING
    = _ini.add("unicode.runtime_encoding", null, IniDefinition.PHP_INI_ALL);
  public static final IniDefinition INI_UNICODE_SCRIPT_ENCODING
    = _ini.add("unicode.script_encoding", null, IniDefinition.PHP_INI_ALL);
}

