/*
 * 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.env;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.caucho.quercus.QuercusException;
import com.caucho.quercus.QuercusRuntimeException;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.quercus.marshal.Marshal;
import com.caucho.quercus.program.ClassField;
import com.caucho.util.L10N;
import com.caucho.vfs.WriteStream;

/**
 * Represents a PHP expression value.
 */
@SuppressWarnings("serial")
abstract public class Value implements java.io.Serializable
{
  protected static final L10N L = new L10N(Value.class);

  private static final Value []NULL_ARG_VALUES = new Value[0];

  public static final Value []NULL_VALUE_ARRAY = new Value[0];
  public static final Value []NULL_ARGS = new Value[0];

  //
  // Properties
  //

  /**
   * Returns the value's class name.
   */
  public String getClassName()
  {
    return getType();
  }

  /**
   * Returns the backing QuercusClass.
   */
  public QuercusClass getQuercusClass()
  {
    return null;
  }

  /**
   * Returns the Quercus class for this obj/name.
   */
  public QuercusClass findQuercusClass(Env env)
  {
    QuercusClass cls = getQuercusClass();

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

    String name = toString();

    return env.getClass(name);
  }

  /**
   * Returns the called class
   */
  public Value getCalledClass(Env env)
  {
    QuercusClass qClass = getQuercusClass();

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

      return BooleanValue.FALSE;
    }
  }

  //
  // Predicates and Relations
  //

  /**
   * Returns true for an implementation of a class
   */
  public boolean isA(Env env, String name)
  {
    return false;
  }

  /**
   * Returns true for an implementation of a class
   */
  final public boolean isA(Env env, Value value)
  {
    if (value.isObject()) {
      // php/03p7
      return isA(env, value.getClassName());
    }
    else if (value instanceof QuercusClass) {
      // php/1277
      return isA(env, value.getClassName());
    }
    else {
      return isA(env, value.toJavaString());
    }
  }

  /**
   * Checks if 'this' is a valid protected call for 'className'
   */
  public void checkProtected(Env env, String className)
  {
  }

  /**
   * Checks if 'this' is a valid private call for 'className'
   */
  public void checkPrivate(Env env, String className)
  {
  }

  /**
   * Returns the ValueType.
   */
  public ValueType getValueType()
  {
    return ValueType.VALUE;
  }

  /**
   * Returns true for an array.
   */
  public boolean isArray()
  {
    return false;
  }

  /**
   * Returns true for a double-value.
   */
  public boolean isDoubleConvertible()
  {
    return false;
  }

  /**
   * Returns true for a long-value.
   */
  public boolean isLongConvertible()
  {
    return false;
  }

  /**
   * Returns true for a long-value.
   */
  public boolean isLong()
  {
    return false;
  }

  /**
   * Returns true for a long-value.
   */
  public boolean isDouble()
  {
    return false;
  }

  /**
   * Returns true for a null.
   */
  public boolean isNull()
  {
    return false;
  }

  /**
   * Returns true for a number.
   */
  public boolean isNumberConvertible()
  {
    return isLongConvertible() || isDoubleConvertible();
  }

  /**
   * Matches is_numeric
   */
  public boolean isNumeric()
  {
    return false;
  }

  /**
   * Returns true for an object.
   */
  public boolean isObject()
  {
    return false;
  }

  /**
   * Returns true for a resource.
   */
  public boolean isResource()
  {
    return false;
  }

  /**
   * Returns true for a StringValue.
   */
  public boolean isString()
  {
    return false;
  }

  /**
   * Returns true for a BinaryValue.
   */
  public boolean isBinary()
  {
    return false;
  }

  /**
   * Returns true for a UnicodeValue.
   */
  public boolean isUnicode()
  {
    return false;
  }

  /**
   * Returns true for a BooleanValue
   */
  public boolean isBoolean()
  {
    return false;
  }

  /**
   * Returns true for a DefaultValue
   */
  public boolean isDefault()
  {
    return false;
  }

  //
  // marshal costs
  //

  /**
   * Cost to convert to a boolean
   */
  public int toBooleanMarshalCost()
  {
    return Marshal.COST_TO_BOOLEAN;
  }

  /**
   * Cost to convert to a byte
   */
  public int toByteMarshalCost()
  {
    return Marshal.COST_INCOMPATIBLE;
  }

  /**
   * Cost to convert to a short
   */
  public int toShortMarshalCost()
  {
    return Marshal.COST_INCOMPATIBLE;
  }

  /**
   * Cost to convert to an integer
   */
  public int toIntegerMarshalCost()
  {
    return Marshal.COST_INCOMPATIBLE;
  }

  /**
   * Cost to convert to a long
   */
  public int toLongMarshalCost()
  {
    return Marshal.COST_INCOMPATIBLE;
  }

  /**
   * Cost to convert to a double
   */
  public int toDoubleMarshalCost()
  {
    return Marshal.COST_INCOMPATIBLE;
  }

  /**
   * Cost to convert to a float
   */
  public int toFloatMarshalCost()
  {
    return toDoubleMarshalCost() + 10;
  }

  /**
   * Cost to convert to a character
   */
  public int toCharMarshalCost()
  {
    return Marshal.COST_TO_CHAR;
  }

  /**
   * Cost to convert to a string
   */
  public int toStringMarshalCost()
  {
    return Marshal.COST_TO_STRING;
  }

  /**
   * Cost to convert to a byte[]
   */
  public int toByteArrayMarshalCost()
  {
    return Marshal.COST_TO_BYTE_ARRAY;
  }

  /**
   * Cost to convert to a char[]
   */
  public int toCharArrayMarshalCost()
  {
    return Marshal.COST_TO_CHAR_ARRAY;
  }

  /**
   * Cost to convert to a Java object
   */
  public int toJavaObjectMarshalCost()
  {
    return Marshal.COST_TO_JAVA_OBJECT;
  }

  /**
   * Cost to convert to a binary value
   */
  public int toBinaryValueMarshalCost()
  {
    return Marshal.COST_TO_STRING + 1;
  }

  /**
   * Cost to convert to a StringValue
   */
  public int toStringValueMarshalCost()
  {
    return Marshal.COST_TO_STRING + 1;
  }

  /**
   * Cost to convert to a UnicodeValue
   */
  public int toUnicodeValueMarshalCost()
  {
    return Marshal.COST_TO_STRING + 1;
  }

  //
  // predicates
  //

  /**
   * Returns true if the value is set.
   */
  public boolean isset()
  {
    return true;
  }

  /**
   * Returns true if the value is empty
   */
  public boolean isEmpty()
  {
    return false;
  }

  /**
   * Returns true if the value is empty
   */
  public boolean isEmpty(Env env, Value index)
  {
    return isEmpty();
  }

  /**
   * Returns true if there are more elements.
   */
  public boolean hasCurrent()
  {
    return false;
  }

  /**
   * Returns true for equality
   */
  public Value eqValue(Value rValue)
  {
    return eq(rValue) ? BooleanValue.TRUE : BooleanValue.FALSE;
  }

  /**
   * Returns true for equality
   */
  public boolean eq(Value rValue)
  {
    if (rValue.isArray())
      return false;
    else if (rValue.isObject()) {
      return rValue.eq(this);
    }
    else if (rValue.isBoolean())
      return toBoolean() == rValue.toBoolean();
    else if (isLongConvertible() && rValue.isLongConvertible())
      return toLong() == rValue.toLong();
    else if (isNumberConvertible() || rValue.isNumberConvertible())
      return toDouble() == rValue.toDouble();
    else
      return toString().equals(rValue.toString());
  }

  /**
   * Returns true for equality
   */
  public boolean eql(Value rValue)
  {
    return this == rValue.toValue();
  }

  /**
   * Returns a negative/positive integer if this Value is
   * lessthan/greaterthan rValue.
   */
  public int cmp(Value rValue)
  {
    // This is tricky: implemented according to Table 15-5 of
    // http://us2.php.net/manual/en/language.operators.comparison.php

    Value lVal = toValue();
    Value rVal = rValue.toValue();

    if (lVal instanceof StringValue && rVal instanceof NullValue)
      return ((StringValue) lVal).cmpString(StringValue.EMPTY);

    if (lVal instanceof NullValue && rVal instanceof StringValue)
      return StringValue.EMPTY.cmpString((StringValue) rVal);

    if (lVal instanceof StringValue && rVal instanceof StringValue)
      return ((StringValue) lVal).cmpString((StringValue) rVal);

    if (lVal instanceof NullValue
        || lVal instanceof BooleanValue
        || rVal instanceof NullValue
        || rVal instanceof BooleanValue)
    {
      boolean lBool = toBoolean();
      boolean rBool    = rValue.toBoolean();

      if (!lBool && rBool) return -1;
      if (lBool && !rBool) return 1;
      return 0;
    }

    if (lVal.isObject() && rVal.isObject())
      return ((ObjectValue) lVal).cmpObject((ObjectValue) rVal);

    if ((lVal instanceof StringValue
         || lVal instanceof NumberValue
         || lVal instanceof ResourceValue)
        && (rVal instanceof StringValue
            || rVal instanceof NumberValue
            || rVal instanceof ResourceValue))
      return NumberValue.compareNum(lVal, rVal);

    if (lVal instanceof ArrayValue) return 1;
    if (rVal instanceof ArrayValue) return -1;
    if (lVal instanceof ObjectValue) return 1;
    if (rVal instanceof ObjectValue) return -1;

    // XXX: proper default case?
    throw new RuntimeException(
      "values are incomparable: " + lVal + " <=> " + rVal);
  }

  /**
   * Returns true for less than
   */
  public boolean lt(Value rValue)
  {
    return cmp(rValue) < 0;
  }

  /**
   * Returns true for less than or equal to
   */
  public boolean leq(Value rValue)
  {
    return cmp(rValue) <= 0;
  }

  /**
   * Returns true for greater than
   */
  public boolean gt(Value rValue)
  {
    return cmp(rValue) > 0;
  }

  /**
   * Returns true for greater than or equal to
   */
  public boolean geq(Value rValue)
  {
    return cmp(rValue) >= 0;
  }

  //
  // Conversions
  //

  public static long toLong(boolean b)
  {
    return b ? 1 : 0;
  }

  public static long toLong(double d)
  {
    return (long) d;
  }

  public static long toLong(long l)
  {
    return l;
  }

  public static long toLong(CharSequence s)
  {
    return StringValue.parseLong(s);
  }

  public static double toDouble(boolean b)
  {
    return b ? 1.0 : 0.0;
  }

  public static double toDouble(double d)
  {
    return d;
  }

  public static double toDouble(long l)
  {
    return l;
  }

  public static double toDouble(CharSequence s)
  {
    return StringValue.toDouble(s.toString());
  }

  /**
   * Converts to a boolean.
   */
  public boolean toBoolean()
  {
    return true;
  }

  /**
   * Converts to a long.
   */
  public long toLong()
  {
    return toBoolean() ? 1 : 0;
  }

  /**
   * Converts to an int
   */
  public int toInt()
  {
    return (int) toLong();
  }

  /**
   * Converts to a double.
   */
  public double toDouble()
  {
    return 0;
  }

  /**
   * Converts to a char
   */
  public char toChar()
  {
    String s = toString();

    if (s == null || s.length() < 1)
      return 0;
    else
      return s.charAt(0);
  }

  /**
   * Converts to a string.
   *
   * @param env
   */
  public StringValue toString(Env env)
  {
    return toStringValue(env);
  }

  /**
   * Converts to an array.
   */
  public ArrayValue toArray()
  {
    return new ArrayValueImpl().append(this);
  }

  /**
   * Converts to an array if null.
   */
  public Value toAutoArray()
  {
    Env.getCurrent().warning(L.l("'{0}' cannot be used as an array.",
                                 toDebugString()));

    return this;
  }

  /**
   * Casts to an array.
   */
  public ArrayValue toArrayValue(Env env)
  {
    env.warning(L.l("'{0}' ({1}) is not assignable to ArrayValue",
                    this, getType()));

    return null;
  }

  /**
   * Converts to an object if null.
   */
  public Value toAutoObject(Env env)
  {
    return this;
  }

  /**
   * Converts to an object.
   */
  public Value toObject(Env env)
  {
    ObjectValue obj = env.createObject();

    obj.putField(env, env.createString("scalar"), this);

    return obj;
  }

  /**
   * Converts to a java object.
   */
  public Object toJavaObject()
  {
    return null;
  }

  /**
   * Converts to a java object.
   */
  public Object toJavaObject(Env env, Class<?> type)
  {
    env.warning(L.l("Can't convert {0} to Java {1}",
                    getClass().getName(), type.getName()));

    return null;
  }

  /**
   * Converts to a java object.
   */
  public Object toJavaObjectNotNull(Env env, Class<?> type)
  {
    env.warning(L.l("Can't convert {0} to Java {1}",
                    getClass().getName(), type.getName()));

    return null;
  }

  /**
   * Converts to a java boolean object.
   */
  public Boolean toJavaBoolean()
  {
    return toBoolean() ? Boolean.TRUE : Boolean.FALSE;
  }

  /**
   * Converts to a java byte object.
   */
  public Byte toJavaByte()
  {
    return new Byte((byte) toLong());
  }

  /**
   * Converts to a java short object.
   */
  public Short toJavaShort()
  {
    return new Short((short) toLong());
  }

  /**
   * Converts to a java Integer object.
   */
  public Integer toJavaInteger()
  {
    return new Integer((int) toLong());
  }

  /**
   * Converts to a java Long object.
   */
  public Long toJavaLong()
  {
    return new Long((int) toLong());
  }

  /**
   * Converts to a java Float object.
   */
  public Float toJavaFloat()
  {
    return new Float((float) toDouble());
  }

  /**
   * Converts to a java Double object.
   */
  public Double toJavaDouble()
  {
    return new Double(toDouble());
  }

  /**
   * Converts to a java Character object.
   */
  public Character toJavaCharacter()
  {
    return new Character(toChar());
  }

  /**
   * Converts to a java String object.
   */
  public String toJavaString()
  {
    return toString();
  }

  /**
   * Converts to a java Collection object.
   */
  public Collection<?> toJavaCollection(Env env, Class<?> type)
  {
    env.warning(L.l("Can't convert {0} to Java {1}",
            getClass().getName(), type.getName()));

    return null;
  }

  /**
   * Converts to a java List object.
   */
  public List<?> toJavaList(Env env, Class<?> type)
  {
    env.warning(L.l("Can't convert {0} to Java {1}",
            getClass().getName(), type.getName()));

    return null;
  }

  /**
   * Converts to a java Map object.
   */
  public Map<?,?> toJavaMap(Env env, Class<?> type)
  {
    env.warning(L.l("Can't convert {0} to Java {1}",
            getClass().getName(), type.getName()));

    return null;
  }

  /**
   * Converts to a Java Calendar.
   */
  public Calendar toJavaCalendar()
  {
    Calendar cal = Calendar.getInstance();

    cal.setTimeInMillis(toLong());

    return cal;
  }

  /**
   * Converts to a Java Date.
   */
  public Date toJavaDate()
  {
    return new Date(toLong());
  }

  /**
   * Converts to a Java URL.
   */
  public URL toJavaURL(Env env)
  {
    try {
      return new URL(toString());
    }
    catch (MalformedURLException e) {
      env.warning(e.getMessage());
      return null;
    }
  }

  /**
   * Converts to a Java Enum.
   */
  public Enum toJavaEnum(Env env, Class cls)
  {
    String s = toString();

    if (s == null) {
      return null;
    }

    try {
      return Enum.valueOf(cls, s);
    }
    catch (IllegalArgumentException e) {
      env.warning(e);

      return null;
    }
  }

  /**
   * Converts to a Java BigDecimal.
   */
  public BigDecimal toBigDecimal()
  {
    return new BigDecimal(toString());
  }

  /**
   * Converts to a Java BigInteger.
   */
  public BigInteger toBigInteger()
  {
    return new BigInteger(toString());
  }

  /**
   * Converts to an exception.
   */
  public QuercusException toException(Env env, String file, int line)
  {
    putField(env, env.createString("file"), env.createString(file));
    putField(env, env.createString("line"), LongValue.create(line));

    return new QuercusLanguageException(this);
  }

  /**
   * Converts to a raw value.
   */
  public Value toValue()
  {
    return this;
  }

  /**
   * Converts to a key.
   */
  public Value toKey()
  {
    throw new QuercusRuntimeException(L.l("{0} is not a valid key", this));
  }

  /**
   * Convert to a ref.
   */
  public Value toRef()
  {
    return this;
  }

  /**
   * Convert to a function argument value, e.g. for
   *
   * function foo($a)
   *
   * where $a is never assigned or modified
   */
  public Value toLocalValueReadOnly()
  {
    return this;
  }

  /**
   * Convert to a function argument value, e.g. for
   *
   * function foo($a)
   *
   * where $a is never assigned, but might be modified, e.g. $a[3] = 9
   */
  public Value toLocalValue()
  {
    return this;
  }

  /**
   * Convert to a function argument value, e.g. for
   *
   * function foo($a)
   *
   * where $a may be assigned.
   */
  public Value toLocalRef()
  {
    return this;
  }

  /**
   * Convert to a function argument value, e.g. for
   *
   * function foo($a)
   *
   * where $a is used as a variable in the function
   */
  public Var toLocalVar()
  {
    return toLocalRef().toVar();
  }

  /**
   * Convert to a function argument reference value, e.g. for
   *
   * function foo(&$a)
   *
   * where $a is used as a variable in the function
   */
  public Var toLocalVarDeclAsRef()
  {
    return new Var(this);
  }

  /**
   * Converts to a local $this, which can depend on the calling class
   */
  public Value toLocalThis(QuercusClass qClass)
  {
    return this;
  }

  /**
   * Convert to a function argument reference value, e.g. for
   *
   * function foo(&$a)
   *
   * where $a is never assigned in the function
   */
  public Value toRefValue()
  {
    return this;
  }

  /**
   * Converts to a Var.
   */
  public Var toVar()
  {
    return new Var(this);
  }

  /**
   * Convert to a function argument reference value, e.g. for
   *
   * function foo(&$a)
   *
   * where $a is used as a variable in the function
   */
  public Value toArgRef()
  {
    Env.getCurrent()
      .warning(L.l(
        "'{0}' is an invalid reference, because only "
        + "variables may be passed by reference.",
        this));

    return NullValue.NULL;
  }

  /**
   * Converts to a StringValue.
   */
  public StringValue toStringValue()
  {
    return toStringValue(Env.getInstance());
  }

  /**
   * Converts to a StringValue.
   */
  public StringValue toStringValue(Env env)
  {
    return toStringBuilder(env);
  }

  /**
   * Converts to a Unicode string.  For unicode.semantics=false, this will
   * still return a StringValue. For unicode.semantics=true, this will
   * return a UnicodeStringValue.
   */
  public StringValue toUnicode(Env env)
  {
    return toUnicodeValue(env);
  }

  /**
   * Converts to a UnicodeValue for marshaling, so it will create a
   * UnicodeValue event when unicode.semantics=false.
   */
  public StringValue toUnicodeValue()
  {
    return toUnicodeValue(Env.getInstance());
  }

  /**
   * Converts to a UnicodeValue for marshaling, so it will create a
   * UnicodeValue event when unicode.semantics=false.
   */
  public StringValue toUnicodeValue(Env env)
  {
    // php/0ci0
    return new UnicodeBuilderValue(env.createString(toString()));
  }

  /**
   * Converts to a BinaryValue.
   */
  public StringValue toBinaryValue()
  {
    return toBinaryValue(Env.getInstance());
  }

  /**
   * Converts to a BinaryValue.
   */
  public StringValue toBinaryValue(String charset)
  {
    return toBinaryValue();
  }

  /**
   * Converts to a BinaryValue.
   */
  public StringValue toBinaryValue(Env env)
  {
    StringValue bb = env.createBinaryBuilder();

    bb.append(this);

    return bb;

      /*
    try {
      int length = 0;
      while (true) {
        bb.ensureCapacity(bb.getLength() + 256);

        int sublen = is.read(bb.getBuffer(),
                             bb.getOffset(),
                             bb.getLength() - bb.getOffset());

        if (sublen <= 0)
          return bb;
        else {
          length += sublen;
          bb.setOffset(length);
        }
      }
    } catch (IOException e) {
      throw new QuercusException(e);
    }
      */
  }

  /**
   * Returns a byteArrayInputStream for the value.
   * See TempBufferStringValue for how this can be overriden
   *
   * @return InputStream
   */
  public InputStream toInputStream()
  {
    return new CharSequenceInputStream(toString());
  }

  /**
   * Converts to a string builder
   */
  public StringValue toStringBuilder()
  {
    return toStringBuilder(Env.getInstance());
  }

  /**
   * Converts to a string builder
   */
  public StringValue toStringBuilder(Env env)
  {
    return env.createUnicodeBuilder().appendUnicode(this);
  }

  /**
   * Converts to a string builder
   */
  public StringValue toStringBuilder(Env env, Value value)
  {
    return toStringBuilder(env).appendUnicode(value);
  }

  /**
   * Converts to a string builder
   */
  public StringValue toStringBuilder(Env env, StringValue value)
  {
    return toStringBuilder(env).appendUnicode(value);
  }

  /**
   * Converts to a string builder
   */
  public StringValue copyStringBuilder()
  {
    return toStringBuilder();
  }

  /**
   * Converts to a long vaule
   */
  public LongValue toLongValue()
  {
    return LongValue.create(toLong());
  }

  /**
   * Converts to a double vaule
   */
  public DoubleValue toDoubleValue()
  {
    return new DoubleValue(toDouble());
  }

  /**
   * Returns true for a callable object.
   */
  public boolean isCallable(Env env, boolean isCheckSyntaxOnly, Value nameRef)
  {
    return false;
  }

  /**
   * Returns the callable's name for is_callable()
   */
  public String getCallableName()
  {
    return null;
  }

  /**
   * Converts to a callable
   */
  public Callable toCallable(Env env, boolean isOptional)
  {
    if (! isOptional) {
      env.warning(L.l("Callable: '{0}' is not a valid callable argument",
                      toString()));

      return new CallbackError(toString());
    }
    else {
      return null;
    }

  }

  //
  // Operations
  //

  /**
   * Append to a string builder.
   */
  public StringValue appendTo(UnicodeBuilderValue sb)
  {
    return sb.append(toString());
  }

  /**
   * Append to a binary builder.
   */
  public StringValue appendTo(StringBuilderValue sb)
  {
    return sb.append(toString());
  }

  /**
   * Append to a binary builder.
   */
  public StringValue appendTo(BinaryBuilderValue sb)
  {
    return sb.appendBytes(toString());
  }

  /**
   * Append to a binary builder.
   */
  public StringValue appendTo(LargeStringBuilderValue sb)
  {
    return sb.append(toString());
  }

  /**
   * Copy for assignment.
   */
  public Value copy()
  {
    return this;
  }

  /**
   * Copy as an array item
   */
  public Value copyArrayItem()
  {
    return copy();
  }

  /**
   * Copy as a return value
   */
  public Value copyReturn()
  {
    // php/3a5d

    return this;
  }

  /**
   * Copy for serialization
   */
  public final Value copy(Env env)
  {
    return copy(env, new IdentityHashMap<Value,Value>());
  }

  /**
   * Copy for serialization
   */
  public Value copy(Env env, IdentityHashMap<Value,Value> map)
  {
    return this;
  }

  /**
   * Copy for serialization
   */
  public Value copyTree(Env env, CopyRoot root)
  {
    return this;
  }

  /**
   * Clone for the clone keyword
   */
  public Value clone(Env env)
  {
    return this;
  }

  /**
   * Copy for saving a method's arguments.
   */
  public Value copySaveFunArg()
  {
    return copy();
  }

  /**
   * Returns the type.
   */
  public String getType()
  {
    return "value";
  }

  /**
   * Returns the SPL object hash.
   */
  public StringValue getObjectHash(Env env)
  {
    return env.getEmptyString();
  }

  /**
   * Returns the resource type.
   */
  public String getResourceType()
  {
    return null;
  }

  /**
   * Returns the current key
   */
  public Value key()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Returns the current value
   */
  public Value current()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Returns the next value
   */
  public Value next()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Returns the previous value
   */
  public Value prev()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Returns the end value.
   */
  public Value end()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Returns the array pointer.
   */
  public Value reset()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Shuffles the array.
   */
  public Value shuffle()
  {
    return BooleanValue.FALSE;
  }

  /**
   * Pops the top array element.
   */
  public Value pop(Env env)
  {
    env.warning("cannot pop a non-array");

    return NullValue.NULL;
  }

  /**
   * Finds the method name.
   */
  public AbstractFunction findFunction(StringValue methodName)
  {
    return null;
  }

  //
  // function invocation
  //

  /**
   * Evaluates the function.
   */
  public Value call(Env env, Value []args)
  {
    Callable call = toCallable(env, false);

    if (call != null)
      return call.call(env, args);
    else
      return env.warning(L.l("{0} is not a valid function",
                             this));
  }

  /**
   * Evaluates the function, returning a reference.
   */
  public Value callRef(Env env, Value []args)
  {
    AbstractFunction fun = env.getFunction(this);

    if (fun != null)
      return fun.callRef(env, args);
    else
      return env.warning(L.l("{0} is not a valid function",
                             this));
  }

  /**
   * Evaluates the function, returning a copy
   */
  public Value callCopy(Env env, Value []args)
  {
    AbstractFunction fun = env.getFunction(this);

    if (fun != null)
      return fun.callCopy(env, args);
    else
      return env.warning(L.l("{0} is not a valid function",
                             this));
  }

  /**
   * Evaluates the function.
   */
  public Value call(Env env)
  {
    return call(env, NULL_ARG_VALUES);
  }

  /**
   * Evaluates the function.
   */
  public Value callRef(Env env)
  {
    return callRef(env, NULL_ARG_VALUES);
  }

  /**
   * Evaluates the function with an argument .
   */
  public Value call(Env env, Value a1)
  {
    return call(env, new Value[] { a1 });
  }

  /**
   * Evaluates the function with an argument .
   */
  public Value callRef(Env env, Value a1)
  {
    return callRef(env, new Value[] { a1 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value call(Env env, Value a1, Value a2)
  {
    return call(env, new Value[] { a1, a2 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value callRef(Env env, Value a1, Value a2)
  {
    return callRef(env, new Value[] { a1, a2 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value call(Env env, Value a1, Value a2, Value a3)
  {
    return call(env, new Value[] { a1, a2, a3 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value callRef(Env env, Value a1, Value a2, Value a3)
  {
    return callRef(env, new Value[] { a1, a2, a3 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value call(Env env, Value a1, Value a2, Value a3, Value a4)
  {
    return call(env, new Value[] { a1, a2, a3, a4 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value callRef(Env env, Value a1, Value a2, Value a3, Value a4)
  {
    return callRef(env, new Value[] { a1, a2, a3, a4 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value call(Env env, Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    return call(env, new Value[] { a1, a2, a3, a4, a5 });
  }

  /**
   * Evaluates the function with arguments
   */
  public Value callRef(Env env,
                       Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    return callRef(env, new Value[] { a1, a2, a3, a4, a5 });
  }

  //
  // Methods invocation
  //

  /**
   * Evaluates a method.
   */
  public Value callMethod(Env env,
                          StringValue methodName, int hash,
                          Value []args)
  {
    if (isNull()) {
      return env.error(L.l("Method call '{0}' is not allowed for a null value.",
                           methodName));
    }
    else {
      return env.error(L.l("'{0}' is an unknown method of {1}.",
                           methodName,
                           toDebugString()));
    }
  }

  /**
   * Evaluates a method.
   */
  public final Value callMethod(Env env,
                                StringValue methodName,
                                Value []args)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash, args);
  }


  /**
   * Evaluates a method.
   */
  public Value callMethodRef(Env env,
                             StringValue methodName, int hash,
                             Value []args)
  {
    return callMethod(env, methodName, hash, args);
  }

  /**
   * Evaluates a method.
   */
  public final Value callMethodRef(Env env,
                                   StringValue methodName,
                                   Value []args)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash, args);
  }

  /**
   * Evaluates a method with 0 args.
   */
  public Value callMethod(Env env, StringValue methodName, int hash)
  {
    return callMethod(env, methodName, hash, NULL_ARG_VALUES);
  }

  /**
   * Evaluates a method with 0 args.
   */
  public final Value callMethod(Env env, StringValue methodName)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash);
  }

  /**
   * Evaluates a method with 0 args.
   */
  public Value callMethodRef(Env env, StringValue methodName, int hash)
  {
    return callMethodRef(env, methodName, hash, NULL_ARG_VALUES);
  }

  /**
   * Evaluates a method with 0 args.
   */
  public final Value callMethodRef(Env env, StringValue methodName)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash);
  }

  /**
   * Evaluates a method with 1 arg.
   */
  public Value callMethod(Env env,
                          StringValue methodName, int hash,
                          Value a1)
  {
    return callMethod(env, methodName, hash, new Value[] { a1 });
  }

  /**
   * Evaluates a method with 1 arg.
   */
  public final Value callMethod(Env env,
                                StringValue methodName,
                                Value a1)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash, a1);
  }

  /**
   * Evaluates a method with 1 arg.
   */
  public Value callMethodRef(Env env,
                             StringValue methodName, int hash,
                             Value a1)
  {
    return callMethodRef(env, methodName, hash, new Value[] { a1 });
  }

  /**
   * Evaluates a method with 1 arg.
   */
  public final Value callMethodRef(Env env,
                                   StringValue methodName,
                                   Value a1)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash, a1);
  }

  /**
   * Evaluates a method with 2 args.
   */
  public Value callMethod(Env env,
                          StringValue methodName, int hash,
                          Value a1, Value a2)
  {
    return callMethod(env, methodName, hash, new Value[] { a1, a2 });
  }

  /**
   * Evaluates a method with 2 args.
   */
  public final Value callMethod(Env env,
                                StringValue methodName,
                                Value a1, Value a2)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash,
                      a1, a2);
  }

  /**
   * Evaluates a method with 2 args.
   */
  public Value callMethodRef(Env env,
                             StringValue methodName, int hash,
                             Value a1, Value a2)
  {
    return callMethodRef(env, methodName, hash, new Value[] { a1, a2 });
  }

  /**
   * Evaluates a method with 2 args.
   */
  public final Value callMethodRef(Env env,
                                   StringValue methodName,
                                   Value a1, Value a2)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash,
                         a1, a2);
  }

  /**
   * Evaluates a method with 3 args.
   */
  public Value callMethod(Env env,
                          StringValue methodName, int hash,
                          Value a1, Value a2, Value a3)
  {
    return callMethod(env, methodName, hash, new Value[] { a1, a2, a3 });
  }

  /**
   * Evaluates a method with 3 args.
   */
  public final Value callMethod(Env env,
                                StringValue methodName,
                                Value a1, Value a2, Value a3)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash,
                      a1, a2, a3);
  }

  /**
   * Evaluates a method with 3 args.
   */
  public Value callMethodRef(Env env,
                             StringValue methodName, int hash,
                             Value a1, Value a2, Value a3)
  {
    return callMethodRef(env, methodName, hash, new Value[] { a1, a2, a3 });
  }

  /**
   * Evaluates a method with 3 args.
   */
  public final Value callMethodRef(Env env,
                                   StringValue methodName,
                                   Value a1, Value a2, Value a3)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash,
                         a1, a2, a3);
  }

  /**
   * Evaluates a method with 4 args.
   */
  public Value callMethod(Env env,
                          StringValue methodName, int hash,
                          Value a1, Value a2, Value a3, Value a4)
  {
    return callMethod(env, methodName, hash,
                      new Value[] { a1, a2, a3, a4 });
  }

  /**
   * Evaluates a method with 4 args.
   */
  public final Value callMethod(Env env,
                                StringValue methodName,
                                Value a1, Value a2, Value a3, Value a4)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash,
                      a1, a2, a3, a4);
  }

  /**
   * Evaluates a method with 4 args.
   */
  public Value callMethodRef(Env env,
                             StringValue methodName, int hash,
                             Value a1, Value a2, Value a3, Value a4)
  {
    return callMethodRef(env, methodName, hash,
                         new Value[] { a1, a2, a3, a4 });
  }

  /**
   * Evaluates a method with 4 args.
   */
  public final Value callMethodRef(Env env,
                                   StringValue methodName,
                                   Value a1, Value a2, Value a3, Value a4)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash,
                         a1, a2, a3, a4);
  }

  /**
   * Evaluates a method with 5 args.
   */
  public Value callMethod(Env env,
                          StringValue methodName, int hash,
                          Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    return callMethod(env, methodName, hash,
                      new Value[] { a1, a2, a3, a4, a5 });
  }

  /**
   * Evaluates a method with 5 args.
   */
  public final Value callMethod(Env env,
                             StringValue methodName,
                             Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethod(env, methodName, hash,
                         a1, a2, a3, a4, a5);
  }

  /**
   * Evaluates a method with 5 args.
   */
  public Value callMethodRef(Env env,
                             StringValue methodName, int hash,
                             Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    return callMethodRef(env, methodName, hash,
                         new Value[] { a1, a2, a3, a4, a5 });
  }

  /**
   * Evaluates a method with 5 args.
   */
  public final Value callMethodRef(Env env,
                             StringValue methodName,
                             Value a1, Value a2, Value a3, Value a4, Value a5)
  {
    int hash = methodName.hashCodeCaseInsensitive();

    return callMethodRef(env, methodName, hash,
                         a1, a2, a3, a4, a5);
  }

  //
  // Arithmetic operations
  //

  /**
   * Negates the value.
   */
  public Value neg()
  {
    return LongValue.create(- toLong());
  }

  /**
   * Negates the value.
   */
  public Value pos()
  {
    return LongValue.create(toLong());
  }

  /**
   * Adds to the following value.
   */
  public Value add(Value rValue)
  {
    if (getValueType().isLongAdd() && rValue.getValueType().isLongAdd())
      return LongValue.create(toLong() + rValue.toLong());

    return DoubleValue.create(toDouble() + rValue.toDouble());
  }

  /**
   * Multiplies to the following value.
   */
  public Value add(long lLong)
  {
    return new DoubleValue(lLong + toDouble());
  }

  /**
   * Pre-increment the following value.
   */
  public Value preincr(int incr)
  {
    return increment(incr);
  }

  /**
   * Post-increment the following value.
   */
  public Value postincr(int incr)
  {
    return increment(incr);
  }

  /**
   * Return the next integer
   */
  public Value addOne()
  {
    return add(1);
  }

  /**
   * Return the previous integer
   */
  public Value subOne()
  {
    return sub(1);
  }

  /**
   * Pre-increment the following value.
   */
  public Value preincr()
  {
    return increment(1);
  }

  /**
   * Post-increment the following value.
   */
  public Value postincr()
  {
    return increment(1);
  }

  /**
   * Pre-increment the following value.
   */
  public Value predecr()
  {
    return increment(-1);
  }

  /**
   * Post-increment the following value.
   */
  public Value postdecr()
  {
    return increment(-1);
  }

  /**
   * Increment the following value.
   */
  public Value increment(int incr)
  {
    long lValue = toLong();

    return LongValue.create(lValue + incr);
  }

  /**
   * Subtracts to the following value.
   */
  public Value sub(Value rValue)
  {
    if (getValueType().isLongAdd() && rValue.getValueType().isLongAdd())
      return LongValue.create(toLong() - rValue.toLong());

    return DoubleValue.create(toDouble() - rValue.toDouble());
  }

  /**
   * Subtracts
   */
  public Value sub(long rLong)
  {
    return new DoubleValue(toDouble() - rLong);
  }


  /**
   * Substracts from the previous value.
   */
  public Value sub_rev(long lLong)
  {
    if (getValueType().isLongAdd())
      return LongValue.create(lLong - toLong());
    else
      return new DoubleValue(lLong - toDouble());
  }

  /**
   * Multiplies to the following value.
   */
  public Value mul(Value rValue)
  {
    if (getValueType().isLongAdd() && rValue.getValueType().isLongAdd())
      return LongValue.create(toLong() * rValue.toLong());
    else
      return new DoubleValue(toDouble() * rValue.toDouble());
  }

  /**
   * Multiplies to the following value.
   */
  public Value mul(long r)
  {
    if (isLongConvertible())
      return LongValue.create(toLong() * r);
    else
      return new DoubleValue(toDouble() * r);
  }

  /**
   * Divides the following value.
   */
  public Value div(Value rValue)
  {
    if (getValueType().isLongAdd() && rValue.getValueType().isLongAdd()) {
      long l = toLong();
      long r = rValue.toLong();

      if (r != 0 && l % r == 0)
        return LongValue.create(l / r);
      else
        return new DoubleValue(toDouble() / rValue.toDouble());
    }
    else
      return new DoubleValue(toDouble() / rValue.toDouble());
  }

  /**
   * Multiplies to the following value.
   */
  public Value div(long r)
  {
    long l = toLong();

    if (r != 0 && l % r == 0)
      return LongValue.create(l / r);
    else
      return new DoubleValue(toDouble() / r);
  }

  /**
   * modulo the following value.
   */
  public Value mod(Value rValue)
  {
    double lDouble = toDouble();
    double rDouble = rValue.toDouble();

    return LongValue.create((long) lDouble % rDouble);
  }

  /**
   * Shifts left by the value.
   */
  public Value lshift(Value rValue)
  {
    long lLong = toLong();
    long rLong = rValue.toLong();

    return LongValue.create(lLong << rLong);
  }

  /**
   * Shifts right by the value.
   */
  public Value rshift(Value rValue)
  {
    long lLong = toLong();
    long rLong = rValue.toLong();

    return LongValue.create(lLong >> rLong);
  }

  /*
   * Binary And.
   */
  public Value bitAnd(Value rValue)
  {
    return LongValue.create(toLong() & rValue.toLong());
  }

  /*
   * Binary or.
   */
  public Value bitOr(Value rValue)
  {
    return LongValue.create(toLong() | rValue.toLong());
  }

  /**
   * Binary xor.
   */
  public Value bitXor(Value rValue)
  {
    return LongValue.create(toLong() ^ rValue.toLong());
  }

  /**
   * Absolute value.
   */
  public Value abs()
  {
    if (getValueType().isDoubleCmp())
      return new DoubleValue(Math.abs(toDouble()));
    else
      return LongValue.create(Math.abs(toLong()));
  }

  /**
   * Returns the next array index based on this value.
   */
  public long nextIndex(long oldIndex)
  {
    return oldIndex;
  }

  //
  // string functions
  //

  /**
   * Returns the length as a string.
   */
  public int length()
  {
    return toStringValue().length();
  }

  //
  // Array functions
  //

  /**
   * Returns the array size.
   */
  public int getSize()
  {
    return 1;
  }

  /**
   * Returns the count, as returned by the global php count() function
   */
  public int getCount(Env env)
  {
    return 1;
  }

  /**
   * Returns the count, as returned by the global php count() function
   */
  public int getCountRecursive(Env env)
  {
    return getCount(env);
  }

  /**
   * Returns an iterator for the key => value pairs.
   */
  public Iterator<Map.Entry<Value, Value>> getIterator(Env env)
  {
    return getBaseIterator(env);
  }

  /**
   * Returns an iterator for the key => value pairs.
   */
  public Iterator<Map.Entry<Value, Value>> getBaseIterator(Env env)
  {
    Set<Map.Entry<Value, Value>> emptySet = Collections.emptySet();

    return emptySet.iterator();
  }

  /**
   * Returns an iterator for the field keys.
   * The default implementation uses the Iterator returned
   * by {@link #getIterator(Env)}; derived classes may override and
   * provide a more efficient implementation.
   */
  public Iterator<Value> getKeyIterator(Env env)
  {
    final Iterator<Map.Entry<Value, Value>> iter = getIterator(env);

    return new Iterator<Value>() {
      public boolean hasNext() { return iter.hasNext(); }
      public Value next()      { return iter.next().getKey(); }
      public void remove()     { iter.remove(); }
    };
  }

  /**
   * Returns the field keys.
   */
  public Value []getKeyArray(Env env)
  {
    return NULL_VALUE_ARRAY;
  }

  /**
   * Returns the field values.
   */
  public Value []getValueArray(Env env)
  {
    return NULL_VALUE_ARRAY;
  }

  /**
   * Returns an iterator for the field values.
   * The default implementation uses the Iterator returned
   * by {@link #getIterator(Env)}; derived classes may override and
   * provide a more efficient implementation.
   */
  public Iterator<Value> getValueIterator(Env env)
  {
    final Iterator<Map.Entry<Value, Value>> iter = getIterator(env);

    return new Iterator<Value>() {
      public boolean hasNext() { return iter.hasNext(); }
      public Value next()      { return iter.next().getValue(); }
      public void remove()     { iter.remove(); }
    };
  }

  //
  // Object field references
  //

  /**
   * Returns the field value
   */
  public Value getField(Env env, StringValue name)
  {
    return NullValue.NULL;
  }

  /**
   * Returns the field ref.
   */
  public Var getFieldVar(Env env, StringValue name)
  {
    return getField(env, name).toVar();
  }

  /**
   * Returns the field used as a method argument
   */
  public Value getFieldArg(Env env, StringValue name, boolean isTop)
  {
    return getFieldVar(env, name);
  }

  /**
   * Returns the field ref for an argument.
   */
  public Value getFieldArgRef(Env env, StringValue name)
  {
    return getFieldVar(env, name);
  }

  /**
   * Returns the value for a field, creating an object if the field
   * is unset.
   */
  public Value getFieldObject(Env env, StringValue name)
  {
    Value v = getField(env, name);

    if (! v.isset()) {
      v = env.createObject();

      putField(env, name, v);
    }

    return v;
  }

  /**
   * Returns the value for a field, creating an object if the field
   * is unset.
   */
  public Value getFieldArray(Env env, StringValue name)
  {
    Value v = getField(env, name);

    Value array = v.toAutoArray();

    if (v != array) {
      putField(env, name, array);

      return array;
    }
    else if (array.isString()) {
      // php/0484
      return getFieldVar(env, name);
    }
    else {
      return v;
    }
  }

  /**
   * Returns the field ref.
   */
  public Value putField(Env env, StringValue name, Value object)
  {
    return NullValue.NULL;
  }

  public final Value putField(Env env, StringValue name, Value value,
                              Value innerIndex, Value innerValue)
  {
    Value result = value.append(innerIndex, innerValue);

    return putField(env, name, result);
  }

  public void setFieldInit(boolean isInit)
  {
  }

  /**
   * Returns true if the object is in a __set() method call.
   * Prevents infinite recursion.
   */
  public boolean isFieldInit()
  {
    return false;
  }

  /**
   * Returns true if the object has this field.
   */
  public boolean isFieldExists(Env env, StringValue name) {
    return getField(env, name) == UnsetValue.UNSET;
  }

  /**
   * Returns true if the field is set
   */
  public boolean issetField(Env env, StringValue name)
  {
    return false;
  }

  /**
   * Removes the field ref.
   */
  public void unsetField(StringValue name)
  {
  }

  /**
   * Removes the field ref.
   */
  public void unsetArray(Env env, StringValue name, Value index)
  {
  }

  /**
   * Removes the field ref.
   */
  public void unsetThisArray(Env env, StringValue name, Value index)
  {
  }

  /**
   * Appends a value to an array that is a field of an object.
   */
  public Value putThisFieldArray(Env env,
                                   Value obj,
                                   StringValue fieldName,
                                   Value index,
                                   Value value)
  {
    // php/03mm
    return put(index, value);
  }

  /**
   * Returns the field as a Var or Value.
   */
  public Value getThisField(Env env, StringValue name)
  {
    return getField(env, name);
  }

  /**
   * Returns the field as a Var.
   */
  public Var getThisFieldVar(Env env, StringValue name)
  {
    return getThisField(env, name).toVar();
  }

  /**
   * Returns the field used as a method argument
   */
  public Value getThisFieldArg(Env env, StringValue name)
  {
    return getThisFieldVar(env, name);
  }

  /**
   * Returns the field ref for an argument.
   */
  public Value getThisFieldArgRef(Env env, StringValue name)
  {
    return getThisFieldVar(env, name);
  }

  /**
   * Returns the value for a field, creating an object if the field
   * is unset.
   */
  public Value getThisFieldObject(Env env, StringValue name)
  {
    Value v = getThisField(env, name);

    if (! v.isset()) {
      v = env.createObject();

      putThisField(env, name, v);
    }

    return v;
  }

  /**
   * Returns the value for a field, creating an object if the field
   * is unset.
   */
  public Value getThisFieldArray(Env env, StringValue name)
  {
    Value v = getThisField(env, name);

    Value array = v.toAutoArray();

    if (v == array)
      return v;
    else {
      putField(env, name, array);

      return array;
    }
  }

  public final void initField(Env env,
                              ClassField field,
                              boolean isInitFieldValues)
  {
    Value value = NullValue.NULL;

    if (isInitFieldValues) {
      value = field.evalInitExpr(env);
    }

    initField(env, field.getName(), field.getCanonicalName(), value);
  }

  public final void initField(Env env,
                              StringValue canonicalName,
                              Value value)
  {
    StringValue name = ClassField.getOrdinaryName(canonicalName);

    initField(env, name, canonicalName, value);
  }

  public void initField(Env env,
                        StringValue name,
                        StringValue canonicalName,
                        Value value)
  {
    putThisField(env, canonicalName, value);
  }

  public void initIncompleteField(Env env,
                                  StringValue name,
                                  Value value,
                                  FieldVisibility visibility)
  {
    putThisField(env, name, value);
  }

  /**
   * Returns the field ref.
   */
  public Value putThisField(Env env, StringValue name, Value object)
  {
    return putField(env, name, object);
  }

  /**
   * Sets an array field ref.
   */
  public Value putThisField(Env env,
                            StringValue name,
                            Value array,
                            Value index,
                            Value value)
  {
    Value result = array.append(index, value);

    putThisField(env, name, result);

    return value;
  }

  /**
   * Returns true if the field is set
   */
  public boolean issetThisField(Env env, StringValue name)
  {
    return issetField(env, name);
  }

  /**
   * Removes the field ref.
   */
  public void unsetThisField(StringValue name)
  {
    unsetField(name);
  }

  /**
   * Removes the field ref.
   */
  public void unsetThisPrivateField(String className, StringValue name)
  {
    unsetField(name);
  }

  /**
   * Returns the static field.
   */
  public Value getStaticFieldValue(Env env, StringValue name)
  {
    env.error(L.l("No calling class found for '{0}'", this));

    return NullValue.NULL;
  }

  /**
  * Returns the static field reference.
  */
  public Var getStaticFieldVar(Env env, StringValue name)
  {
    env.error(L.l("No calling class found for '{0}'", this));

    throw new IllegalStateException();
  }

  /**
   * Sets the static field.
   */
  public Value setStaticFieldRef(Env env, StringValue name, Value value)
  {
    env.error(L.l("No calling class found for '{0}'", this));

    throw new IllegalStateException();
  }

  //
  // field convenience
  //

  public Value putField(Env env, String name, Value value)
  {
    return putThisField(env, env.createString(name), value);
  }

  /**
   * Returns the array ref.
   */
  public Value get(Value index)
  {
    return UnsetValue.UNSET;
  }

  /**
   * Helper method that calls get(Value).
   */
  final public Value get(long index) {
    return get(LongValue.create(index));
  }

  /**
   * Returns a reference to the array value.
   */
  public Var getVar(Value index)
  {
    Value value = get(index);

    if (value.isVar())
      return (Var) value;
    else
      return new Var(value);
  }

  /**
   * Returns a reference to the array value.
   */
  public Value getRef(Value index)
  {
    return get(index);
  }

  /**
   * Returns the array ref as a function argument.
   */
  public Value getArg(Value index, boolean isTop)
  {
    return get(index);
  }

  /**
   * Returns the array value, copying on write if necessary.
   */
  public Value getDirty(Value index)
  {
    return get(index);
  }

  /**
   * Returns the value for a field, creating an array if the field
   * is unset.
   */
  public Value getArray()
  {
    return this;
  }

  /**
   * Returns the value for a field, creating an array if the field
   * is unset.
   */
  public Value getArray(Value index)
  {
    Value var = getVar(index);

    return var.toAutoArray();
  }

  /**
   * Returns the value for the variable, creating an object if the var
   * is unset.
   */
  public Value getObject(Env env)
  {
    return NullValue.NULL;
  }

  /**
   * Returns the value for a field, creating an object if the field
   * is unset.
   */
  public Value getObject(Env env, Value index)
  {
    Value var = getVar(index);

    if (var.isset())
      return var.toValue();
    else {
      var.set(env.createObject());

      return var.toValue();
    }
  }

  public boolean isVar()
  {
    return false;
  }

  /**
   * Sets the value ref.
   */
  public Value set(Value value)
  {
    return value;
  }

  /**
   * Sets the array ref and returns the value
   */
  public Value put(Value index, Value value)
  {
    Env.getCurrent().warning(L.l("{0} cannot be used as an array",
                                 toDebugString()));

    return value;
  }

  /**
   * Sets the array ref.
   */
  public final Value put(Value index, Value value,
                         Value innerIndex, Value innerValue)
  {
    Value result = value.append(innerIndex, innerValue);

    put(index, result);

    return innerValue;
  }

  /**
   * Appends an array value
   */
  public Value put(Value value)
  {
    /*
    Env.getCurrent().warning(L.l("{0} cannot be used as an array",
                                 toDebugString()));
                                 */


    return value;
  }

  /**
   * Sets the array value, returning the new array, e.g. to handle
   * string update ($a[0] = 'A').  Creates an array automatically if
   * necessary.
   */
  public Value append(Value index, Value value)
  {
    Value array = toAutoArray();

    if (array.isArray())
      return array.append(index, value);
    else
      return array;
  }

  /**
   * Sets the array tail, returning the Var of the tail.
   */
  public Var putVar()
  {
    return new Var();
  }

  /**
   * Appends a new array.
   */
  public Value putArray(Env env)
  {
    Value value = new ArrayValueImpl();

    put(value);

    return value;
  }

  /**
   * Sets the array tail, returning a reference to the tail.
   */
  public Value getArgTail(Env env, boolean isTop)
  {
    return putVar();
  }

  /**
   * Appends a new object
   */
  public Value putObject(Env env)
  {
    Value value = env.createObject();

    put(value);

    return value;
  }

  /**
   * Return true if the array value is set
   */
  public boolean isset(Value index)
  {
    return false;
  }

  /**
   * Returns true if the key exists in the array.
   */
  public boolean keyExists(Value key)
  {
    return isset(key);
  }

  /**
   * Returns the corresponding value if this array contains the given key
   *
   * @param key to search for in the array
   *
   * @return the value if it is found in the array, NULL otherwise
   */
  public Value containsKey(Value key)
  {
    return null;
  }

  /**
   * Return unset the value.
   */
  public Value remove(Value index)
  {
    return UnsetValue.UNSET;
  }

  /**
   * Takes the values of this array, unmarshalls them to objects of type
   * <i>elementType</i>, and puts them in a java array.
   */
  public Object valuesToArray(Env env, Class<?> elementType)
  {
    env.error(L.l("Can't assign {0} with type {1} to {2}[]",
                  this,
                  this.getClass(),
                  elementType));
    return null;
  }

  /**
   * Returns the character at the named index.
   */
  public Value charValueAt(long index)
  {
    return NullValue.NULL;
  }

  /**
   * Sets the character at the named index.
   */
  public Value setCharValueAt(long index, Value value)
  {
    return NullValue.NULL;
  }

  /**
   * Prints the value.
   * @param env
   */
  public void print(Env env)
  {
    env.print(toString(env));
  }

  /**
   * Prints the value.
   * @param env
   */
  public void print(Env env, WriteStream out)
  {
    try {
      out.print(toString(env));
    } catch (IOException e) {
      throw new QuercusRuntimeException(e);
    }
  }

  /**
   * Serializes the value.
   *
   * @param env
   * @param sb holds result of serialization
   * @param serializeMap holds reference indexes
   */
  public void serialize(Env env,
                        StringBuilder sb,
                        SerializeMap serializeMap)
  {
    serializeMap.incrementIndex();

    serialize(env, sb);
  }

  /**
   * Encodes the value in JSON.
   */
  public void jsonEncode(Env env, JsonEncodeContext context, StringValue sb)
  {
    env.warning(L.l("type is unsupported; json encoded as null"));

    sb.append("null");
  }

  /**
   * Serializes the value.
   */
  public void serialize(Env env, StringBuilder sb)
  {
    throw new UnsupportedOperationException(getClass().getName());
  }

  /**
   * Exports the value.
   */
  public StringValue varExport(Env env)
  {
    StringValue sb = env.createStringBuilder();

    varExportImpl(sb, 0);

    return sb;
  }

  /**
   * Exports the value.
   */
  protected void varExportImpl(StringValue sb, int level)
  {
    throw new UnsupportedOperationException(getClass().getName());
  }

  /**
   * Binds a Java object to this object.
   */
  public void setJavaObject(Object obj)
  {
    // XXX: nam: do nothing for now, need to refactor the PHP-Java interface
    //throw new UnsupportedOperationException(getClass().getName());
  }

  //
  // Java generator code
  //

  /**
   * Generates code to recreate the expression.
   *
   * @param out the writer to the Java source code.
   */
  public void generate(PrintWriter out)
    throws IOException
  {
  }

  protected static void printJavaChar(PrintWriter out, char ch)
  {
    switch (ch) {
      case '\r':
        out.print("\\r");
        break;
      case '\n':
        out.print("\\n");
        break;
      //case '\"':
      //  out.print("\\\"");
      //  break;
      case '\'':
        out.print("\\\'");
        break;
      case '\\':
        out.print("\\\\");
        break;
      default:
        out.print(ch);
        break;
    }
  }

  protected static void printJavaString(PrintWriter out, StringValue s)
  {
    if (s == null) {
      out.print("");
      return;
    }

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

      switch (ch) {
      case '\r':
        out.print("\\r");
        break;
      case '\n':
        out.print("\\n");
        break;
      case '\"':
        out.print("\\\"");
        break;
      case '\'':
        out.print("\\\'");
        break;
      case '\\':
        out.print("\\\\");
        break;
      default:
        out.print(ch);
        break;
      }
    }
  }

  public String toInternString()
  {
    return toString().intern();
  }

  public String toDebugString()
  {
    return toString();
  }

  public final void varDump(Env env,
                            WriteStream out,
                            int depth,
                            IdentityHashMap<Value, String> valueSet)
    throws IOException
  {
    if (valueSet.get(this) != null) {
      out.print("*recursion*");
      return;
    }

    valueSet.put(this, "printing");

    try {
      varDumpImpl(env, out, depth, valueSet);
    }
    finally {
      valueSet.remove(this);
    }
  }

  protected void varDumpImpl(Env env,
                             WriteStream out,
                             int depth,
                             IdentityHashMap<Value, String> valueSet)
    throws IOException
  {
    out.print("resource(" + toString() + ")");
  }

  public final void printR(Env env,
                           WriteStream out,
                           int depth,
                           IdentityHashMap<Value, String> valueSet)
    throws IOException
  {
    if (valueSet.get(this) != null) {
      out.print("*recursion*");
      return;
    }

    valueSet.put(this, "printing");

    try {
      printRImpl(env, out, depth, valueSet);
    }
    finally {
      valueSet.remove(this);
    }
  }

  protected void printRImpl(Env env,
                            WriteStream out,
                            int depth,
                            IdentityHashMap<Value, String> valueSet)
    throws IOException
  {
    out.print(toString());
  }

  protected void printDepth(WriteStream out, int depth)
    throws IOException
  {
    for (int i = 0; i < depth; i++)
      out.print(' ');
  }

  public int getHashCode()
  {
    return hashCode();
  }

  @Override
  public int hashCode()
  {
    return 1021;
  }
}

