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

package com.caucho.quercus.program;

import com.caucho.quercus.QuercusContext;
import com.caucho.quercus.QuercusModuleException;
import com.caucho.quercus.QuercusException;
import com.caucho.quercus.QuercusRuntimeException;
import com.caucho.quercus.annotation.*;
import com.caucho.quercus.env.*;
import com.caucho.quercus.expr.Expr;
import com.caucho.quercus.expr.LiteralExpr;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.marshal.JavaMarshal;
import com.caucho.quercus.marshal.Marshal;
import com.caucho.quercus.marshal.MarshalFactory;
import com.caucho.quercus.module.ModuleContext;
import com.caucho.util.L10N;
import com.caucho.vfs.WriteStream;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Represents an introspected Java class.
 */
public class JavaClassDef extends ClassDef {
  private final static Logger log
    = Logger.getLogger(JavaClassDef.class.getName());
  private final static L10N L = new L10N(JavaClassDef.class);

  private final ModuleContext _moduleContext;

  private final String _name;
  private final Class _type;

  private QuercusClass _quercusClass;

  private HashSet<String> _instanceOfSet;
  private HashSet<String> _instanceOfSetLowerCase;
  
  private final boolean _isAbstract;
  private final boolean _isInterface;
  private final boolean _isDelegate;
  private boolean _isPhpClass;
  
  private String _resourceType;

  private JavaClassDef _componentDef;

  protected volatile boolean _isInit;

  private final HashMap<String, Value> _constMap
    = new HashMap<String, Value>();
  
  private final HashMap<String, Object> _constJavaMap
    = new HashMap<String, Object>();

  private final MethodMap<AbstractJavaMethod> _functionMap
    = new MethodMap<AbstractJavaMethod>(null, this);

  private final HashMap<StringValue, AbstractJavaMethod> _getMap
    = new HashMap<StringValue, AbstractJavaMethod>();

  private final HashMap<StringValue, AbstractJavaMethod> _setMap
    = new HashMap<StringValue, AbstractJavaMethod>();

  // _fieldMap stores all public non-static fields
  // used by getField and setField
  private final HashMap<StringValue, FieldMarshalPair> _fieldMap
    = new HashMap<StringValue, FieldMarshalPair> ();

  private AbstractJavaMethod _cons;
  private AbstractJavaMethod __construct;
  
  private JavaMethod __fieldGet;
  private JavaMethod __fieldSet;
  
  private FunctionArrayDelegate _funArrayDelegate;
  private ArrayDelegate _arrayDelegate;
  
  private JavaMethod __call;
  private JavaMethod __toString;

  private Method _printRImpl;
  private Method _varDumpImpl;
  private Method _jsonEncode;
  private Method _entrySet;

  private TraversableDelegate _traversableDelegate;
  private CountDelegate _countDelegate;

  private Method _iteratorMethod;

  private Marshal _marshal;
  
  private String _extension;

  public JavaClassDef(ModuleContext moduleContext, String name, Class type)
  {
    super(null, name, null, new String[] {});

    _moduleContext = moduleContext;
    _name = name;
    _type = type;
    
    _isAbstract = Modifier.isAbstract(type.getModifiers());
    _isInterface = type.isInterface();
    _isDelegate = type.isAnnotationPresent(ClassImplementation.class);

    if (type.isArray() && ! isArray())
      throw new IllegalStateException(
        L.l("'{0}' needs to be called with JavaArrayClassDef", type));
  }
  
  public JavaClassDef(ModuleContext moduleContext,
                      String name,
                      Class type,
                      String extension)
  {
    this(moduleContext, name, type);

    _extension = extension;
    
    moduleContext.addExtensionClass(extension, name);
  }

  private void fillInstanceOfSet(Class type, boolean isTop)
  {
    if (type == null)
      return;
    
    if (isTop && _isDelegate) {
      _instanceOfSet.add(_name);
      _instanceOfSetLowerCase.add(_name.toLowerCase(Locale.ENGLISH));
    }
    else {
      String name = type.getSimpleName();
      
      _instanceOfSet.add(name);
      _instanceOfSetLowerCase.add(name.toLowerCase(Locale.ENGLISH));
    }

    fillInstanceOfSet(type.getSuperclass(), false);

    Class []ifaceList = type.getInterfaces();
    if (ifaceList != null) {
      for (Class iface : ifaceList)
        fillInstanceOfSet(iface, false);
    }
  }

  public static JavaClassDef create(ModuleContext moduleContext,
                                    String name, Class<?> type)
  {
    if (Double.class.isAssignableFrom(type)
        || Float.class.isAssignableFrom(type))
      return new DoubleClassDef(moduleContext);
    else if (Long.class.isAssignableFrom(type)
             || Integer.class.isAssignableFrom(type)
             || Short.class.isAssignableFrom(type)
             || Byte.class.isAssignableFrom(type))
      return new LongClassDef(moduleContext);
    else if (BigDecimal.class.isAssignableFrom(type))
      return new BigDecimalClassDef(moduleContext);
    else if (BigInteger.class.isAssignableFrom(type))
      return new BigIntegerClassDef(moduleContext);
    else if (String.class.isAssignableFrom(type)
             || Character.class.isAssignableFrom(type))
      return new StringClassDef(moduleContext);
    else if (Boolean.class.isAssignableFrom(type))
      return new BooleanClassDef(moduleContext);
    else if (Calendar.class.isAssignableFrom(type))
      return new CalendarClassDef(moduleContext);
    else if (Date.class.isAssignableFrom(type))
      return new DateClassDef(moduleContext);
    else if (URL.class.isAssignableFrom(type))
      return new URLClassDef(moduleContext);
    else if (Map.class.isAssignableFrom(type))
      return new JavaMapClassDef(moduleContext, name, type);
    else if (List.class.isAssignableFrom(type))
      return new JavaListClassDef(moduleContext, name, type);
    else if (Collection.class.isAssignableFrom(type)
             && ! Queue.class.isAssignableFrom(type))
      return new JavaCollectionClassDef(moduleContext, name, type);
    else
      return null;
  }

  /**
   * Returns the class name.
   */
  @Override
  public String getName()
  {
    return _name;
  }

  /**
   * Returns the class name.
   */
  public String getSimpleName()
  {
    return _type.getSimpleName();
  }

  public Class getType()
  {
    return _type;
  }
  
  /*
   * Returns the type of this resource.
   */
  public String getResourceType()
  {
    return _resourceType;
  }

  protected ModuleContext getModuleContext()
  {
    return _moduleContext;
  }
  
  /*
   * Returns the name of the extension that this class is part of.
   */
  @Override
  public String getExtension()
  {
    return _extension;
  }

  @Override
  public boolean isA(String name)
  {
    if (_instanceOfSet == null) {
      _instanceOfSet = new HashSet<String>();
      _instanceOfSetLowerCase = new HashSet<String>();
      
      fillInstanceOfSet(_type, true);
    }
    
    return (_instanceOfSet.contains(name)
            || _instanceOfSetLowerCase.contains(name.toLowerCase(Locale.ENGLISH)));
  }

  /**
   * Adds the interfaces to the set
   */
  @Override
  public void addInterfaces(HashSet<String> interfaceSet)
  {
    addInterfaces(interfaceSet, _type, true);
  }

  protected void addInterfaces(HashSet<String> interfaceSet,
                               Class type,
                               boolean isTop)
  {
    if (type == null)
      return;
    
    interfaceSet.add(_name.toLowerCase(Locale.ENGLISH));
    interfaceSet.add(type.getSimpleName().toLowerCase(Locale.ENGLISH));

    if (type.getInterfaces() != null) {
      for (Class iface : type.getInterfaces()) {
        addInterfaces(interfaceSet, iface, false);
      }
    }

    // php/1z21
    addInterfaces(interfaceSet, type.getSuperclass(), false);
  }

  private boolean hasInterface(String name, Class type)
  {
    Class[] interfaces = type.getInterfaces();

    if (interfaces != null) {
      for (Class intfc : interfaces) {
        if (intfc.getSimpleName().equalsIgnoreCase(name))
          return true;

        if (hasInterface(name, intfc))
          return true;
      }
    }

    return false;
  }

  @Override
  public boolean isAbstract()
  {
    return _isAbstract;
  }

  public boolean isArray()
  {
    return false;
  }

  @Override
  public boolean isInterface()
  {
    return _isInterface;
  }

  public boolean isDelegate()
  {
    return _isDelegate;
  }
  
  public void setPhpClass(boolean isPhpClass)
  {
    _isPhpClass = isPhpClass;
  }
  
  public boolean isPhpClass()
  {
    return _isPhpClass;
  }

  public JavaClassDef getComponentDef()
  {
    if (_componentDef == null) {
      Class compType = getType().getComponentType();
      _componentDef = _moduleContext.getJavaClassDefinition(compType.getName());
    }
    
    return _componentDef;
  }

  public Value wrap(Env env, Object obj)
  {
    if (! _isInit)
      init();
    
    if (_resourceType != null)
      return new JavaResourceValue(env, obj, this);
    else
      return new JavaValue(env, obj, this);
  }

  private int cmpObject(Object lValue, Object rValue)
  {
    if (lValue == rValue)
      return 0;

    if (lValue == null)
      return -1;

    if (rValue == null)
      return 1;

    if (lValue instanceof Comparable) {
      if (!(rValue instanceof Comparable))
        return -1;

      return ((Comparable) lValue).compareTo(rValue);
    }
    else if (rValue instanceof Comparable) {
      return 1;
    }

    if (lValue.equals(rValue))
      return 0;

    String lName = lValue.getClass().getName();
    String rName = rValue.getClass().getName();

    return lName.compareTo(rName);
  }

  public int cmpObject(Object lValue, Object rValue, JavaClassDef rClassDef)
  {
    int cmp = cmpObject(lValue, rValue);

    if (cmp != 0)
        return cmp;

    // attributes
    // XX: not sure how to do this, to imitate PHP objects,
    // should getters be involved as well?

    for (
      Map.Entry<StringValue, FieldMarshalPair> lEntry : _fieldMap.entrySet()
      ) {
      StringValue lFieldName = lEntry.getKey();
      FieldMarshalPair rFieldPair = rClassDef._fieldMap.get(lFieldName);

      if (rFieldPair == null)
        return 1;

      FieldMarshalPair lFieldPair = lEntry.getValue();

      try {
        Object lResult = lFieldPair._field.get(lValue);
        Object rResult = rFieldPair._field.get(lValue);

        int resultCmp = cmpObject(lResult, rResult);

        if (resultCmp != 0)
          return resultCmp;
      }
      catch (IllegalAccessException e) {
        log.log(Level.FINE,  L.l(e.getMessage()), e);
        return 0;
      }
    }

    return 0;
  }

  /**
   * Returns the field getter.
   *
   * @param name
   * @return Value attained through invoking getter
   */
  public Value getField(Env env, Value qThis, StringValue name)
  {
    AbstractJavaMethod get = _getMap.get(name);

    if (get != null) {
      try {
        return get.callMethod(env, getQuercusClass(), qThis);
      } catch (Exception e) {
        log.log(Level.FINE, L.l(e.getMessage()), e);

        return null;
      }
    }
    
    FieldMarshalPair fieldPair = _fieldMap.get(name);
    if (fieldPair != null) {
      try {
        Object result = fieldPair._field.get(qThis.toJavaObject());
        return fieldPair._marshal.unmarshal(env, result);
      } catch (Exception e) {
        log.log(Level.FINE,  L.l(e.getMessage()), e);

        return null;
      }
    }
    
    AbstractFunction phpGet = qThis.getQuercusClass().getFieldGet();
    
    if (phpGet != null) {
      return phpGet.callMethod(env, getQuercusClass(), qThis, name);
    }
    
    if (__fieldGet != null) {
      try {
        return __fieldGet.callMethod(env, getQuercusClass(), qThis, name);
      } catch (Exception e) {
        log.log(Level.FINE,  L.l(e.getMessage()), e);

        return null;
      }
    }

    return null;
  }

  public Value putField(Env env,
                        Value qThis,
                        StringValue name,
                        Value value)
  {
    AbstractJavaMethod setter = _setMap.get(name);

    if (setter != null) {
      try {
        return setter.callMethod(env, getQuercusClass(), qThis, value);
      } catch (Exception e) {
        log.log(Level.FINE,  L.l(e.getMessage()), e);

        return NullValue.NULL;
      }
    }

    FieldMarshalPair fieldPair = _fieldMap.get(name);

    if (fieldPair != null) {
      try {
        Class type = fieldPair._field.getType();
        Object marshaledValue = fieldPair._marshal.marshal(env, value, type);
        fieldPair._field.set(qThis.toJavaObject(), marshaledValue);

        return value;

      } catch (Exception e) {
        log.log(Level.FINE,  L.l(e.getMessage()), e);
        return NullValue.NULL;
      }
    }
    
    if (! qThis.isFieldInit()) {
      AbstractFunction phpSet = qThis.getQuercusClass().getFieldSet();
      
      if (phpSet != null) {
        qThis.setFieldInit(true);
        
        try {
          return phpSet.callMethod(env, getQuercusClass(), qThis, name, value);
          
        } finally {
          qThis.setFieldInit(false);
        }
      }
    }
    

    if (__fieldSet != null) {
      try {
        return __fieldSet.callMethod(env,
                                     getQuercusClass(),
                                     qThis,
                                     name,
                                     value);
      } catch (Exception e) {
        log.log(Level.FINE,  L.l(e.getMessage()), e);

        return NullValue.NULL;
      }
    }

    return null;
  }

  /**
   * Returns the marshal instance.
   */
  public Marshal getMarshal()
  {
    return _marshal;
  }

  /**
   * Creates a new instance.
   */
  @Override
  public ObjectValue newInstance(Env env, QuercusClass qClass)
  {
    // return newInstance();
    return null;
  }

  public Value newInstance()
  {
    return null;
    /*
    try {
      //Object obj = _type.newInstance();
      return new JavaValue(null, _type.newInstance(), this);
    } catch (Exception e) {
      throw new QuercusRuntimeException(e);
    }
    */
  }

  /**
   * Eval new
   */
  @Override
  public Value callNew(Env env, Value []args)
  {
    if (_cons != null) {
      if (__construct != null) {
        Value value = _cons.call(env, Value.NULL_ARGS);

        __construct.callMethod(env, __construct.getQuercusClass(), value, args);
        
        return value;
      }
      else {
        return _cons.call(env, args);
      }
    }
    else if (__construct != null)
      return __construct.call(env, args);
    else
      return NullValue.NULL;
  }

  /**
   * Returns the __call.
   */
  public AbstractFunction getCallMethod()
  {
    return __call;
  }
  
  @Override
  public AbstractFunction getCall()
  {
    return __call;
  }

  /**
   * Eval a method
   */
  public AbstractFunction findFunction(StringValue methodName)
  {
    return _functionMap.getRaw(methodName);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash,
                          Value []args)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis, args);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash,
                          Value a1)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis, a1);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash,
                          Value a1, Value a2)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis, a1, a2);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash,
                          Value a1, Value a2, Value a3)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis, a1, a2, a3);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash,
                          Value a1, Value a2, Value a3, Value a4)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis, a1, a2, a3, a4);
  }

  /**
   * Eval a method
   */
  public Value callMethod(Env env, Value qThis,
                          StringValue methodName, int hash,
                          Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    AbstractFunction fun = _functionMap.get(methodName, hash);

    return fun.callMethod(env, getQuercusClass(), qThis, a1, a2, a3, a4, a5);
  }

  public Set<? extends Map.Entry<Value,Value>> entrySet(Object obj)
  {
    try {
      if (_entrySet == null) {
        return null;
      }

      return (Set) _entrySet.invoke(obj);
    } catch (Exception e) {
      throw new QuercusException(e);
    }
  }

  /**
   * Initialize the quercus class.
   */
  @Override
  public void initClass(QuercusClass cl)
  {
    init();

    if (_cons != null) {
      cl.setConstructor(_cons);
      cl.addMethod(new StringBuilderValue("__construct"), _cons);
    }
    
    if (__construct != null) {
      cl.setConstructor(__construct);
      cl.addMethod(new StringBuilderValue("__construct"), __construct);
    }

    for (AbstractJavaMethod value : _functionMap.values()) {
      cl.addMethod(new StringBuilderValue(value.getName()), value);
    }

    if (__fieldGet != null)
      cl.setFieldGet(__fieldGet);

    if (__fieldSet != null)
      cl.setFieldSet(__fieldSet);

    if (__call != null)
      cl.setCall(__call);
    
    if (__toString != null) {
      cl.addMethod(new StringBuilderValue("__toString"), __toString);
    }

    if (_arrayDelegate != null)
      cl.setArrayDelegate(_arrayDelegate);
    else if (_funArrayDelegate != null)
      cl.setArrayDelegate(_funArrayDelegate);

    if (_traversableDelegate != null)
      cl.setTraversableDelegate(_traversableDelegate);
    else if (cl.getTraversableDelegate() == null
             && _iteratorMethod != null) {
      // adds support for Java classes implementing iterator()
      // php/
      cl.setTraversableDelegate(new JavaTraversableDelegate(_iteratorMethod));
    }

    if (_countDelegate != null)
      cl.setCountDelegate(_countDelegate);

    for (Map.Entry<String,Value> entry : _constMap.entrySet()) {
      cl.addConstant(entry.getKey(), new LiteralExpr(entry.getValue()));
    }
    
    for (Map.Entry<String,Object> entry : _constJavaMap.entrySet()) {
      cl.addJavaConstant(entry.getKey(), entry.getValue());
    }
  }

  /**
   * Finds the matching constant
   */
  public Value findConstant(Env env, String name)
  {
    return _constMap.get(name);
  }

  /**
   * Creates a new instance.
   */
  public void initInstance(Env env, Value value)
  {
  }

  /**
   * Returns the quercus class
   */
  public QuercusClass getQuercusClass()
  {
    if (_quercusClass == null) {
      init();

      _quercusClass = new QuercusClass(_moduleContext, this, null);
    }

    return _quercusClass;
  }

  /**
   * Returns the constructor
   */
  @Override
  public AbstractFunction findConstructor()
  {
    return null;
  }

  @Override
  public final void init()
  {
    if (_isInit)
      return;
    
    synchronized (this) {
      if (_isInit)
        return;

      super.init();

      try {
        initInterfaceList(_type);
        introspect();
      }
      finally {
        _isInit = true;
      }
    }
  }

  private void initInterfaceList(Class type)
  {
    Class[] ifaces = type.getInterfaces();

    if (ifaces == null)
      return;

    for (Class iface : ifaces) {
      JavaClassDef javaClassDef = _moduleContext.getJavaClassDefinition(iface);

      if (javaClassDef != null)
        addInterface(javaClassDef.getName());

      // recurse for parent interfaces
      initInterfaceList(iface);
    }
  }

  /**
   * Introspects the Java class.
   */
  private void introspect()
  {
    introspectConstants(_type);
    introspectMethods(_moduleContext, _type);
    introspectFields(_moduleContext, _type);

    _marshal = new JavaMarshal(this, false);

    AbstractJavaMethod consMethod = getConsMethod();
    
    if (consMethod != null) {
      if (consMethod.isStatic())
        _cons = consMethod;
      else
        __construct = consMethod;
    }
    
    
    //Method consMethod = getConsMethod(_type);
    
    /*
    if (consMethod != null) {
      if (Modifier.isStatic(consMethod.getModifiers()))
        _cons = new JavaMethod(_moduleContext, consMethod);
      else
        __construct = new JavaMethod(_moduleContext, consMethod);
    }
    */
    
    if (_cons == null) {
      Constructor []cons = _type.getConstructors();

      if (cons.length > 0) {
        int i;
        for (i = 0; i < cons.length; i++) {
          if (cons[i].isAnnotationPresent(Construct.class))
            break;
        }

        if (i < cons.length) {
          _cons = new JavaConstructor(_moduleContext, cons[i]);
        }
        else {
          _cons = new JavaConstructor(_moduleContext, cons[0]);
          for (i = 1; i < cons.length; i++) {
            _cons = _cons.overload(new JavaConstructor(_moduleContext,
                                                       cons[i]));
          }
        }

      } else
        _cons = null;
    }
    
    if (_cons != null)
      _cons.setConstructor(true);

    if (__construct != null)
      __construct.setConstructor(true);

    introspectAnnotations(_type);
  }

  private void introspectAnnotations(Class type)
  {
    try {
      if (type == null)
        return;

      // interfaces
      for (Class<?> iface : type.getInterfaces())
        introspectAnnotations(iface);

      // super-class
      introspectAnnotations(type.getSuperclass());

      // this
      for (Annotation annotation : type.getAnnotations()) {
        if (annotation.annotationType() == Delegates.class) {
          Class[] delegateClasses = ((Delegates) annotation).value();

          for (Class cl : delegateClasses) {
            boolean isDelegate = addDelegate(cl);

            if (! isDelegate)
              throw new IllegalArgumentException(
                L.l("unknown @Delegate class '{0}'", cl));
          }
        }
        else if (annotation.annotationType() == ResourceType.class) {
          _resourceType = ((ResourceType) annotation).value();
        }
      }
    } catch (RuntimeException e) {
      throw e;
    } catch (InstantiationException e) {
      throw new QuercusModuleException(e.getCause());
    } catch (Exception e) {
      throw new QuercusModuleException(e);
    }
  }

  private boolean addDelegate(Class cl)
    throws InstantiationException, IllegalAccessException
  {
    boolean isDelegate = false;

    if (TraversableDelegate.class.isAssignableFrom(cl)) {
      _traversableDelegate = (TraversableDelegate) cl.newInstance();
      isDelegate = true;
    }

    if (ArrayDelegate.class.isAssignableFrom(cl)) {
      _arrayDelegate = (ArrayDelegate) cl.newInstance();
      isDelegate = true;
    }

    if (CountDelegate.class.isAssignableFrom(cl)) {
      _countDelegate = (CountDelegate) cl.newInstance();
      isDelegate = true;
    }

    return isDelegate;
  }

  private <T> boolean addDelegate(Class<T> cl,
                                  ArrayList<T> delegates,
                                  Class<? extends Object> delegateClass)
  {
    if (!cl.isAssignableFrom(delegateClass))
      return false;

    for (T delegate : delegates) {
      if (delegate.getClass() == delegateClass) {
        return true;
      }
    }

    try {
      delegates.add((T) delegateClass.newInstance());
    }
    catch (InstantiationException e) {
      throw new QuercusModuleException(e);
    }
    catch (IllegalAccessException e) {
      throw new QuercusModuleException(e);
    }
    
    return true;
  }

  /*
  private Method getConsMethod(Class type)
  {
    Method []methods = type.getMethods();

    for (int i = 0; i < methods.length; i++) {
      Method method = methods[i];

      if (! method.getName().equals("__construct"))
        continue;
      if (! Modifier.isPublic(method.getModifiers()))
        continue;
      
      return method;
    }

    return null;
  }
  */
  
  private AbstractJavaMethod getConsMethod()
  {
    for (AbstractJavaMethod method : _functionMap.values()) {
      if (method.getName().equals("__construct"))
        return method;
    }
    
    return null;
  }

  /**
   * Introspects the Java class.
   */
  private void introspectFields(ModuleContext moduleContext, Class type)
  {
    if (type == null)
      return;

    if (! Modifier.isPublic(type.getModifiers()))
      return;

    // Introspect getXXX and setXXX
    // also register whether __get, __getField, __set, __setField exists
    Method[] methods = type.getMethods();

    for (Method method : methods) {
      if (Modifier.isStatic(method.getModifiers()))
        continue;
      
      if (method.isAnnotationPresent(Hide.class))
        continue;

      String methodName = method.getName();
      int length = methodName.length();

      if (length > 3) {
        if (methodName.startsWith("get")) {
          StringValue quercusName
            = javaToQuercusConvert(methodName.substring(3, length));

          AbstractJavaMethod existingGetter = _getMap.get(quercusName);
          AbstractJavaMethod newGetter = new JavaMethod(moduleContext, method);
          
          if (existingGetter != null) {
            newGetter = existingGetter.overload(newGetter);
          }
          
          _getMap.put(quercusName, newGetter);
        }
        else if (methodName.startsWith("is")) {
          StringValue quercusName
            = javaToQuercusConvert(methodName.substring(2, length));
          
          AbstractJavaMethod existingGetter = _getMap.get(quercusName);
          AbstractJavaMethod newGetter = new JavaMethod(moduleContext, method);
          
          if (existingGetter != null) {
            newGetter = existingGetter.overload(newGetter);
          }
          
          _getMap.put(quercusName, newGetter);
        }
        else if (methodName.startsWith("set")) {
          StringValue quercusName
            = javaToQuercusConvert(methodName.substring(3, length));
          
          AbstractJavaMethod existingSetter = _setMap.get(quercusName);
          AbstractJavaMethod newSetter = new JavaMethod(moduleContext, method);

          if (existingSetter != null)
            newSetter = existingSetter.overload(newSetter);

          _setMap.put(quercusName, newSetter);
        } else if ("__get".equals(methodName)) {
          if (_funArrayDelegate == null)
            _funArrayDelegate = new FunctionArrayDelegate();

          _funArrayDelegate.setArrayGet(new JavaMethod(moduleContext, method));
        } else if ("__set".equals(methodName)) {
          if (_funArrayDelegate == null)
            _funArrayDelegate = new FunctionArrayDelegate();
          
          _funArrayDelegate.setArrayPut(new JavaMethod(moduleContext, method));
        } else if ("__count".equals(methodName)) {
          FunctionCountDelegate delegate = new FunctionCountDelegate();
          
          delegate.setCount(new JavaMethod(moduleContext, method));
          
          _countDelegate = delegate;
        } else if ("__getField".equals(methodName)) {
          __fieldGet = new JavaMethod(moduleContext, method);
        } else if ("__setField".equals(methodName)) {
          __fieldSet = new JavaMethod(moduleContext, method);
        } else if ("__fieldGet".equals(methodName)) {
          __fieldGet = new JavaMethod(moduleContext, method);
        } else if ("__fieldSet".equals(methodName)) {
          __fieldSet = new JavaMethod(moduleContext, method);
        }
      }
    }

    // server/2v00
    /*
    if (__fieldGet != null)
      _getMap.clear();
    
    if (__fieldSet != null)
      _setMap.clear();
    */

    // Introspect public non-static fields
    Field[] fields = type.getFields();

    for (Field field : fields) {
      if (Modifier.isStatic(field.getModifiers()))
        continue;
      else if (field.isAnnotationPresent(Hide.class))
        continue;

      MarshalFactory factory = moduleContext.getMarshalFactory();
      Marshal marshal = factory.create(field.getType(), false);
      
      _fieldMap.put(new ConstStringValue(field.getName()),
                    new FieldMarshalPair(field, marshal));
    }


   // introspectFields(quercus, type.getSuperclass());
  }

  /**
   * helper for introspectFields
   *
   * @param s (eg: Foo, URL)
   * @return (foo, URL)
   */
  private StringValue javaToQuercusConvert(String s)
  {
    if (s.length() == 1) {
      return new ConstStringValue(
        new char[]{ Character.toLowerCase(s.charAt(0)) });
    }

    if (Character.isUpperCase(s.charAt(1)))
      return new ConstStringValue(s);
    else {
      StringBuilderValue sb = new StringBuilderValue();
      sb.append(Character.toLowerCase(s.charAt(0)));

      int length = s.length();
      for (int i = 1; i < length; i++) {
        sb.append(s.charAt(i));
      }

      return sb;
    }
  }

  /**
   * Introspects the Java class.
   */
  private void introspectConstants(Class type)
  {
    if (type == null)
      return;

    if (! Modifier.isPublic(type.getModifiers()))
      return;

    /* not needed because Class.getFields() is recursive
    Class []ifcs = type.getInterfaces();

    for (Class ifc : ifcs) {
      introspectConstants(ifc);
    }
    */

    Field []fields = type.getFields();

    for (Field field : fields) {
      if (_constMap.get(field.getName()) != null)
        continue;
      else if (_constJavaMap.get(field.getName()) != null)
        continue;
      else if (! Modifier.isPublic(field.getModifiers()))
        continue;
      else if (! Modifier.isStatic(field.getModifiers()))
        continue;
      else if (! Modifier.isFinal(field.getModifiers()))
        continue;
      else if (field.isAnnotationPresent(Hide.class))
        continue;

      try {
        Object obj = field.get(null);
        
        Value value = QuercusContext.objectToValue(obj);

        if (value != null)
          _constMap.put(field.getName().intern(), value);
        else
          _constJavaMap.put(field.getName().intern(), obj);
      } catch (Throwable e) {
        log.log(Level.FINEST, e.toString(), e);
      }
    }

    //introspectConstants(type.getSuperclass());
  }
  
  /**
   * Introspects the Java class.
   */
  private void introspectMethods(ModuleContext moduleContext,
                                 Class<?> type)
  {
    if (type == null)
      return;

    Method []methods = type.getMethods();

    for (Method method : methods) {
      if (! Modifier.isPublic(method.getModifiers()))
        continue;
      
      if (method.isAnnotationPresent(Hide.class))
        continue;

      if (_isPhpClass && method.getDeclaringClass() == Object.class)
        continue;
      
      if ("iterator".equals(method.getName())
          && method.getParameterTypes().length == 0
          && Iterator.class.isAssignableFrom(method.getReturnType())) {
        _iteratorMethod = method;
      }

      if ("printRImpl".equals(method.getName())) {
        _printRImpl = method;
      } else if ("varDumpImpl".equals(method.getName())) {
        _varDumpImpl = method;
      } else if (method.isAnnotationPresent(JsonEncode.class)) {
        _jsonEncode = method;
      } else if (method.isAnnotationPresent(EntrySet.class)) {
        _entrySet = method;
      } else if ("__call".equals(method.getName())) {
        __call = new JavaMethod(moduleContext, method);
      } else if ("__toString".equals(method.getName())) {
        __toString = new JavaMethod(moduleContext, method);
      } else {
        if (method.getName().startsWith("quercus_"))
          throw new UnsupportedOperationException(
            L.l("{0}: use @Name instead", method.getName()));
        
        JavaMethod newFun = new JavaMethod(moduleContext, method);
        StringValue funName = new StringBuilderValue(newFun.getName());
        AbstractJavaMethod fun = _functionMap.getRaw(funName);

        if (fun != null)
          fun = fun.overload(newFun);
        else
          fun = newFun;

        _functionMap.put(funName.toString(), fun);
      }
    }
    
    /* Class.getMethods() is recursive
    introspectMethods(moduleContext, type.getSuperclass());

    Class []ifcs = type.getInterfaces();

    for (Class ifc : ifcs) {
      introspectMethods(moduleContext, ifc);
    }
    */
  }
  
  public JavaMethod getToString()
  {
    return __toString;
  }

  public StringValue toString(Env env,
                              JavaValue value)
  {
    if (__toString == null)
      return null;

    return __toString.callMethod(
      env, getQuercusClass(), value, new Expr[0]).toStringValue();
  }
  
  public boolean jsonEncode(Env env, Object obj, StringValue sb)
  {
    if (_jsonEncode == null)
      return false;
    
    try {
      _jsonEncode.invoke(obj, env, sb);
      return true;
      
    } catch (InvocationTargetException e) {
      throw new QuercusRuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new QuercusRuntimeException(e);
    }
  }
  
  /**
   *
   * @return false if printRImpl not implemented
   * @throws IOException
   */
  public boolean printRImpl(Env env,
                            Object obj,
                            WriteStream out,
                            int depth,
                            IdentityHashMap<Value, String> valueSet)
    throws IOException
  {

    try {
      if (_printRImpl == null) {
        return false;

      }

      _printRImpl.invoke(obj, env, out, depth, valueSet);
      return true;
    } catch (InvocationTargetException e) {
      throw new QuercusRuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new QuercusRuntimeException(e);
    }
  }

  public boolean varDumpImpl(Env env,
                             Value obj,
                             Object javaObj,
                             WriteStream out,
                             int depth,
                             IdentityHashMap<Value, String> valueSet)
    throws IOException
  {
    try {
      if (_varDumpImpl == null)
        return false;

      _varDumpImpl.invoke(javaObj, env, obj, out, depth, valueSet);
      return true;
    } catch (InvocationTargetException e) {
      throw new QuercusRuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new QuercusRuntimeException(e);
    }
  }
  
  private class JavaTraversableDelegate
    implements TraversableDelegate
  {
    private Method _iteratorMethod;
    
    public JavaTraversableDelegate(Method iterator)
    {
      _iteratorMethod = iterator;
    }
    
    public Iterator<Map.Entry<Value, Value>>
      getIterator(Env env, ObjectValue qThis)
    {
      try {
        Iterator iterator
          = (Iterator) _iteratorMethod.invoke(qThis.toJavaObject());
        
        return new JavaIterator(env, iterator);
      } catch (InvocationTargetException e) {
        throw new QuercusRuntimeException(e);
      } catch (IllegalAccessException e) {
        throw new QuercusRuntimeException(e);
      }
    }

    public Iterator<Value> getKeyIterator(Env env, ObjectValue qThis)
    {
      try {
        Iterator iterator =
          (Iterator) _iteratorMethod.invoke(qThis.toJavaObject());
        
        return new JavaKeyIterator(iterator);
      } catch (InvocationTargetException e) {
        throw new QuercusRuntimeException(e);
      } catch (IllegalAccessException e) {
        throw new QuercusRuntimeException(e);
      }
    }
    
    public Iterator<Value> getValueIterator(Env env, ObjectValue qThis)
    {
      try {
        Iterator iterator =
          (Iterator) _iteratorMethod.invoke(qThis.toJavaObject());

        return new JavaValueIterator(env, iterator);
      } catch (InvocationTargetException e) {
        throw new QuercusRuntimeException(e);
      } catch (IllegalAccessException e) {
        throw new QuercusRuntimeException(e);
      }
    }
  }

  private class JavaKeyIterator
    implements Iterator<Value>
  {
    private Iterator _iterator;
    private int _index;
    
    public JavaKeyIterator(Iterator iterator)
    {
      _iterator = iterator;
    }
    
    public Value next()
    {
      _iterator.next();

      return LongValue.create(_index++);
    }
    
    public boolean hasNext()
    {
      return _iterator.hasNext();
    }
    
    public void remove()
    {
      throw new UnsupportedOperationException();
    }
  }

  private class JavaValueIterator
    implements Iterator<Value>
  {
    private Env _env;
    private Iterator _iterator;
    
    public JavaValueIterator(Env env, Iterator iterator)
    {
      _env = env;
      _iterator = iterator;
    }
    
    public Value next()
    {
      return _env.wrapJava(_iterator.next());
    }
    
    public boolean hasNext()
    {
      if (_iterator != null)
        return _iterator.hasNext();
      else
        return false;
    }
    
    public void remove()
    {
      throw new UnsupportedOperationException();
    }
  }

  private class JavaIterator
    implements Iterator<Map.Entry<Value, Value>>
  {
    private Env _env;
    private Iterator _iterator;
    
    private int _index;
    
    public JavaIterator(Env env, Iterator iterator)
    {
      _env = env;
      _iterator = iterator;
    }
    
    public Map.Entry<Value, Value> next()
    {
      Object next = _iterator.next();
      int index = _index++;
      
      if (next instanceof Map.Entry) {
        Map.Entry entry = (Map.Entry) next;
        
        if (entry.getKey() instanceof Value
            && entry.getValue() instanceof Value)
        {
          return (Map.Entry<Value, Value>) entry;
        }
        else {
          Value key = _env.wrapJava(entry.getKey());
          Value val = _env.wrapJava(entry.getValue());
          
          return new JavaEntry(key, val);
        }
      }
      else {
        return new JavaEntry(LongValue.create(index), _env.wrapJava(next));
      }
    }
    
    public boolean hasNext()
    {
      if (_iterator != null)
        return _iterator.hasNext();
      else
        return false;
    }
    
    public void remove()
    {
      throw new UnsupportedOperationException();
    }
  }

  private class JavaEntry
    implements Map.Entry<Value, Value>
  {
    private Value _key;
    private Value _value;
    
    public JavaEntry(Value key, Value value)
    {
      _key = key;
      _value = value;
    }
    
    public Value getKey()
    {
      return _key;
    }
    
    public Value getValue()
    {
      return _value;
    }
    
    public Value setValue(Value value)
    {
      throw new UnsupportedOperationException();
    }
  }

  private class MethodMarshalPair {
    public Method _method;
    public Marshal _marshal;

    public MethodMarshalPair(Method method,
                              Marshal marshal)
    {
      _method = method;
      _marshal = marshal;
    }
  }

  private class FieldMarshalPair {
    public Field _field;
    public Marshal _marshal;

    public FieldMarshalPair(Field field,
                             Marshal marshal)
    {
      _field = field;
      _marshal = marshal;
    }
  }
  
  private static class LongClassDef extends JavaClassDef {
    LongClassDef(ModuleContext module)
    {
      super(module, "Long", Long.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return LongValue.create(((Number) obj).longValue());
    }
  }
  
  private static class DoubleClassDef extends JavaClassDef {
    DoubleClassDef(ModuleContext module)
    {
      super(module, "Double", Double.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return new DoubleValue(((Number) obj).doubleValue());
    }
  }
  
  private static class BigIntegerClassDef extends JavaClassDef {
    BigIntegerClassDef(ModuleContext module)
    {
      super(module, "BigInteger", BigInteger.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return new BigIntegerValue(env, (BigInteger) obj, this);
    }
  }
  
  private static class BigDecimalClassDef extends JavaClassDef {
    BigDecimalClassDef(ModuleContext module)
    {
      super(module, "BigDecimal", BigDecimal.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return new BigDecimalValue(env, (BigDecimal) obj, this);
    }
  }
  
  private static class StringClassDef extends JavaClassDef {
    StringClassDef(ModuleContext module)
    {
      super(module, "String", String.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return env.createString((String) obj);
    }
  }
  
  private static class BooleanClassDef extends JavaClassDef {
    BooleanClassDef(ModuleContext module)
    {
      super(module, "Boolean", Boolean.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      if (Boolean.TRUE.equals(obj))
        return BooleanValue.TRUE;
      else
        return BooleanValue.FALSE;
    }
  }
  
  private static class CalendarClassDef extends JavaClassDef {
    CalendarClassDef(ModuleContext module)
    {
      super(module, "Calendar", Calendar.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return new JavaCalendarValue(env, (Calendar)obj, this);
    }
  }
  
  private static class DateClassDef extends JavaClassDef {
    DateClassDef(ModuleContext module)
    {
      super(module, "Date", Date.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return new JavaDateValue(env, (Date)obj, this);
    }
  }
  
  private static class URLClassDef extends JavaClassDef {
    URLClassDef(ModuleContext module)
    {
      super(module, "URL", URL.class);
    }

    @Override
    public Value wrap(Env env, Object obj)
    {
      return new JavaURLValue(env, (URL)obj, this);
    }
  }
}

