/*
* Copyright (c) 1998-2008 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 com.caucho.quercus.expr.Expr;
import com.caucho.quercus.expr.UnicodeLiteralExpr;
import com.caucho.quercus.expr.StringLiteralExpr;
import com.caucho.quercus.function.AbstractFunction;
import com.caucho.vfs.WriteStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.AbstractSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* Represents a PHP object value.
*/
public class ObjectExtValue extends ObjectValue
implements Serializable
{
/**
*
*/
private static final long serialVersionUID = 1L;
private static final int DEFAULT_SIZE = 16;
private MethodMap<AbstractFunction> _methodMap;
private Entry []_entries;
private int _hashMask;
private int _size;
private boolean _isFieldInit;
public ObjectExtValue(QuercusClass cl)
{
super(cl);
_methodMap = cl.getMethodMap();
_entries = new Entry[DEFAULT_SIZE];
_hashMask = _entries.length - 1;
}
public ObjectExtValue(Env env, ObjectExtValue copy, CopyRoot root)
{
super(copy.getQuercusClass());
root.putCopy(copy, this);
_methodMap = copy._methodMap;
_size = copy._size;
_isFieldInit = copy._isFieldInit;
Entry []copyEntries = copy._entries;
_entries = new Entry[copyEntries.length];
_hashMask = copy._hashMask;
int len = copyEntries.length;
for (int i = 0; i < len; i++) {
Entry entry = copyEntries[i];
for (; entry != null; entry = entry._next) {
Entry entryCopy = entry.copyTree(env, root);
entryCopy._next = _entries[i];
if (_entries[i] != null)
_entries[i]._prev = entryCopy;
_entries[i] = entryCopy;
}
}
_incompleteObjectName = copy._incompleteObjectName;
}
public ObjectExtValue(Env env,
IdentityHashMap<Value,Value> copyMap,
ObjectExtValue copy)
{
super(copy.getQuercusClass());
_methodMap = copy._methodMap;
_size = copy._size;
_isFieldInit = copy._isFieldInit;
Entry []copyEntries = copy._entries;
_entries = new Entry[copyEntries.length];
_hashMask = copy._hashMask;
int len = copyEntries.length;
for (int i = 0; i < len; i++) {
Entry entry = copyEntries[i];
for (; entry != null; entry = entry._next) {
Entry entryCopy = new Entry(env, copyMap, entry);
entryCopy._next = _entries[i];
if (_entries[i] != null)
_entries[i]._prev = entryCopy;
_entries[i] = entryCopy;
}
}
_incompleteObjectName = copy._incompleteObjectName;
}
private void init()
{
_entries = new Entry[DEFAULT_SIZE];
_hashMask = _entries.length - 1;
_size = 0;
}
@Override
protected void setQuercusClass(QuercusClass cl)
{
super.setQuercusClass(cl);
_methodMap = cl.getMethodMap();
}
/*
* Initializes the incomplete class.
*/
@Override
public void initObject(Env env, QuercusClass cls)
{
setQuercusClass(cls);
Entry []entries = _entries;
_entries = new Entry[_entries.length];
_hashMask = _entries.length - 1;
_size = 0;
cls.initObject(env, this);
EntryIterator iter = new EntryIterator(entries);
while (iter.hasNext()) {
Entry newField = iter.next();
Entry entry = getThisEntry(newField._key);
if (entry != null)
entry._value = newField._value;
else
initField(newField._key, newField._value, newField._visibility);
}
}
/**
* Returns the number of entries.
*/
@Override
public int getSize()
{
return _size;
}
/**
* Gets a field value.
*/
@Override
public final Value getField(Env env, StringValue name)
{
Entry entry = getEntry(env, name);
if (entry != null)
return entry._value.toValue();
return getFieldExt(env, name);
}
/**
* Gets a field value.
*/
@Override
public Value getThisField(Env env, StringValue name)
{
Entry entry = getThisEntry(name);
if (entry != null)
return entry._value.toValue();
return getFieldExt(env, name);
}
/**
* Returns fields not explicitly specified by this value.
*/
protected Value getFieldExt(Env env, StringValue name)
{
return _quercusClass.getField(env, this, name);
}
/**
* Returns the array ref.
*/
@Override
public Var getFieldRef(Env env, StringValue name)
{
Entry entry = getEntry(env, name);
if (entry != null) {
Value value = entry._value;
if (value instanceof Var)
return (Var) value;
Var var = new Var(value);
entry._value = var;
return var;
}
Value value = getFieldExt(env, name);
if (value != UnsetValue.UNSET) {
if (value instanceof Var)
return (Var) value;
else
return new Var(value);
}
// php/3d28
entry = createEntry(name, FieldVisibility.PUBLIC);
value = entry._value;
if (value instanceof Var)
return (Var) value;
Var var = new Var(value);
entry.setValue(var);
return var;
}
/**
* Returns the array ref.
*/
@Override
public Var getThisFieldRef(Env env, StringValue name)
{
Entry entry = getThisEntry(name);
if (entry != null) {
Value value = entry._value;
if (value instanceof Var)
return (Var) value;
Var var = new Var(value);
entry._value = var;
return var;
}
Value value = getFieldExt(env, name);
if (value != UnsetValue.UNSET) {
if (value instanceof Var)
return (Var) value;
else
return new Var(value);
}
entry = createEntry(name, FieldVisibility.PUBLIC);
value = entry._value;
if (value instanceof Var)
return (Var) value;
Var var = new Var(value);
entry.setValue(var);
return var;
}
/**
* Returns the value as an argument which may be a reference.
*/
@Override
public Value getFieldArg(Env env, StringValue name, boolean isTop)
{
Entry entry = getEntry(env, name);
if (entry != null) {
Value value = entry.getValue();
if (isTop || ! value.isset())
return entry.toArg();
else
return value;
}
Value value = getFieldExt(env, name);
if (value != UnsetValue.UNSET)
return value;
return new ArgGetFieldValue(env, this, name);
}
/**
* Returns the value as an argument which may be a reference.
*/
@Override
public Value getThisFieldArg(Env env, StringValue name)
{
Entry entry = getThisEntry(name);
if (entry != null)
return entry.toArg();
Value value = getFieldExt(env, name);
if (value != UnsetValue.UNSET)
return value;
return new ArgGetFieldValue(env, this, name);
}
/**
* Returns the value as an argument which may be a reference.
*/
@Override
public Value getFieldArgRef(Env env, StringValue name)
{
Entry entry = getEntry(env, name);
if (entry != null)
return entry.toArg();
Value value = getFieldExt(env, name);
if (value != UnsetValue.UNSET)
return value;
return new ArgGetFieldValue(env, this, name);
}
/**
* Returns the value as an argument which may be a reference.
*/
@Override
public Value getThisFieldArgRef(Env env, StringValue name)
{
Entry entry = getThisEntry(name);
if (entry != null)
return entry.toArg();
Value value = getFieldExt(env, name);
if (value != UnsetValue.UNSET)
return value;
return new ArgGetFieldValue(env, this, name);
}
/**
* Adds a new value.
*/
@Override
public Value putField(Env env, StringValue name, Value value)
{
Entry entry = getEntry(env, name);
if (entry == null) {
Value oldValue = putFieldExt(env, name, value);
if (oldValue != null)
return oldValue;
if (! _isFieldInit) {
AbstractFunction fieldSet = _quercusClass.getFieldSet();
if (fieldSet != null) {
_isFieldInit = true;
Value retVal = fieldSet.callMethod(env, this, name, value);
_isFieldInit = false;
return retVal;
}
}
}
entry = createEntry(name, FieldVisibility.PUBLIC);
Value oldValue = entry._value;
if (value instanceof Var) {
Var var = (Var) value;
// for function return optimization
var.setReference();
entry._value = var;
}
else if (oldValue instanceof Var) {
oldValue.set(value);
}
else {
entry._value = value;
}
return value;
}
/**
* Sets/adds field to this object.
*/
@Override
public Value putThisField(Env env, StringValue name, Value value)
{
Entry entry = getThisEntry(name);
if (entry == null) {
Value oldValue = putFieldExt(env, name, value);
if (oldValue != null)
return oldValue;
if (! _isFieldInit) {
AbstractFunction fieldSet = _quercusClass.getFieldSet();
if (fieldSet != null) {
//php/09k7
_isFieldInit = true;
Value retVal = fieldSet.callMethod(env, this, name, value);
_isFieldInit = false;
return retVal;
}
}
}
entry = createEntry(name, FieldVisibility.PUBLIC);
Value oldValue = entry._value;
if (value instanceof Var) {
Var var = (Var) value;
// for function return optimization
var.setReference();
entry._value = var;
}
else if (oldValue instanceof Var) {
oldValue.set(value);
}
else {
entry._value = value;
}
return value;
}
protected Value putFieldExt(Env env, StringValue name, Value value)
{
return null;
}
/**
* Adds a new value to the object.
*/
@Override
public void initField(StringValue key,
Value value,
FieldVisibility visibility)
{
Entry entry = createEntry(key, visibility);
entry._value = value;
}
/**
* Removes a value.
*/
@Override
public void unsetField(StringValue name)
{
int hash = name.hashCode() & _hashMask;
for (Entry entry = _entries[hash];
entry != null;
entry = entry._next) {
if (name.equals(entry.getKey())) {
Entry prev = entry._prev;
Entry next = entry._next;
if (prev != null)
prev._next = next;
else
_entries[hash] = next;
if (next != null)
next._prev = prev;
_size--;
return;
}
}
}
/**
* Gets a new value.
*/
private Entry getEntry(Env env, StringValue name)
{
int hash = name.hashCode() & _hashMask;
for (Entry entry = _entries[hash]; entry != null; entry = entry._next) {
if (name.equals(entry._key)) {
if (entry._visibility == FieldVisibility.PRIVATE) {
QuercusClass cls = env.getCallingClass();
// XXX: this really only checks access from outside of class scope
// php/091m
if (cls != _quercusClass) {
env.error(L.l("Can't access private field '{0}::${1}'",
_quercusClass.getName(), name));
}
}
return entry;
}
}
return null;
}
/**
* Gets a new value.
*/
private Entry getThisEntry(StringValue name)
{
int hash = name.hashCode() & _hashMask;
for (Entry entry = _entries[hash]; entry != null; entry = entry._next) {
if (name.equals(entry._key))
return entry;
}
return null;
}
/**
* Creates the entry for a key.
*/
private Entry createEntry(StringValue name, FieldVisibility visibility)
{
int hash = name.hashCode() & _hashMask;
for (Entry entry = _entries[hash];
entry != null;
entry = entry._next) {
if (name.equals(entry._key))
return entry;
}
_size++;
Entry newEntry = new Entry(name, visibility);
Entry next = _entries[hash];
if (next != null) {
newEntry._next = next;
next._prev = newEntry;
}
_entries[hash] = newEntry;
// XXX: possibly resize
return newEntry;
}
//
// array methods
//
/**
* Returns the array value with the given key.
*/
@Override
public Value get(Value key)
{
ArrayDelegate delegate = _quercusClass.getArrayDelegate();
// php/066q vs. php/0906
//return getField(null, key.toString());
if (delegate != null)
return delegate.get(this, key);
else
return super.get(key);
}
/**
* Sets the array value with the given key.
*/
@Override
public Value put(Value key, Value value)
{
// php/0d94
ArrayDelegate delegate = _quercusClass.getArrayDelegate();
if (delegate != null)
return delegate.put(this, key, value);
else
return super.put(key, value);
}
/**
* Appends a new array value
*/
@Override
public Value put(Value value)
{
// php/0d94
ArrayDelegate delegate = _quercusClass.getArrayDelegate();
if (delegate != null)
return delegate.put(this, value);
else
return super.put(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)
{
put(index, value);
return this;
}
/**
* Unsets the array value
*/
@Override
public Value remove(Value key)
{
ArrayDelegate delegate = _quercusClass.getArrayDelegate();
if (delegate != null)
return delegate.unset(this, key);
else
return super.remove(key);
}
//
// Foreach/Traversable functions
//
/**
* Returns an iterator for the key => value pairs.
*/
@Override
public Iterator<Map.Entry<Value, Value>> getIterator(Env env)
{
TraversableDelegate delegate = _quercusClass.getTraversableDelegate();
if (delegate != null)
return delegate.getIterator(env, this);
else
return new KeyValueIterator(_entries);
}
/**
* Returns an iterator for the keys.
*/
@Override
public Iterator<Value> getKeyIterator(Env env)
{
TraversableDelegate delegate = _quercusClass.getTraversableDelegate();
if (delegate != null)
return delegate.getKeyIterator(env, this);
return new KeyIterator(_entries);
}
/**
* Returns an iterator for the values.
*/
@Override
public Iterator<Value> getValueIterator(Env env)
{
TraversableDelegate delegate = _quercusClass.getTraversableDelegate();
if (delegate != null)
return delegate.getValueIterator(env, this);
return new ValueIterator(_entries);
}
//
// method calls
//
/**
* Finds the method name.
*/
@Override
public AbstractFunction findFunction(String methodName)
{
return _quercusClass.findFunction(methodName);
}
/**
* Evaluates a method.
*/
@Override
public Value callMethod(Env env, int hash, char []name, int nameLen,
Expr []args)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, args);
else if (_quercusClass.getCall() != null) {
Expr []newArgs = new Expr[args.length + 1];
String mtd = toMethod(name, nameLen);
if (env.isUnicodeSemantics())
newArgs[0] = new UnicodeLiteralExpr(mtd);
else {
try {
newArgs[0] = new StringLiteralExpr(new StringBuilderValue(mtd.getBytes(env.getQuercus().getScriptEncoding())));
} catch (UnsupportedEncodingException e) {
newArgs[0] = new StringLiteralExpr(new ConstStringValue(mtd));
}
}
System.arraycopy(args, 0, newArgs, 1, args.length);
return _quercusClass.getCall().callMethod(env, this, newArgs);
}
else
return env.error(L.l("Call to undefined method {0}::{1}",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethod(Env env, int hash, char []name, int nameLen,
Value []args)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, args);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl(args));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethod(Env env, int hash, char []name, int nameLen)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl());
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethod(Env env, int hash, char []name, int nameLen,
Value a1)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, a1);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethod(Env env, int hash, char []name, int nameLen,
Value a1, Value a2)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, a1, a2);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* calls the function.
*/
@Override
public Value callMethod(Env env,
int hash, char []name, int nameLen,
Value a1, Value a2, Value a3)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, a1, a2, a3);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2)
.append(a3));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* calls the function.
*/
@Override
public Value callMethod(Env env,
int hash, char []name, int nameLen,
Value a1, Value a2, Value a3, Value a4)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, a1, a2, a3, a4);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2)
.append(a3)
.append(a4));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* calls the function.
*/
@Override
public Value callMethod(Env env,
int hash, char []name, int nameLen,
Value a1, Value a2, Value a3, Value a4, Value a5)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethod(env, this, a1, a2, a3, a4, a5);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethod(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2)
.append(a3)
.append(a4)
.append(a5));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Expr []args)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
return _quercusClass.callMethodRef(env, this, hash, name, nameLen, args);
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Value []args)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this, args);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl(args));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl());
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Value a1)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this, a1);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Value a1, Value a2)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this, a1, a2);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Value a1, Value a2, Value a3)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this, a1, a2, a3);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2)
.append(a3));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Value a1, Value a2, Value a3, Value a4)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this, a1, a2, a3, a4);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2)
.append(a3)
.append(a4));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callMethodRef(Env env, int hash, char []name, int nameLen,
Value a1, Value a2, Value a3, Value a4, Value a5)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _methodMap.get(hash, name, nameLen);
if (fun != null)
return fun.callMethodRef(env, this, a1, a2, a3, a4, a5);
else if ((fun = _quercusClass.getCall()) != null) {
return fun.callMethodRef(env,
this,
env.createString(name, nameLen),
new ArrayValueImpl()
.append(a1)
.append(a2)
.append(a3)
.append(a4)
.append(a5));
}
else
return env.error(L.l("Call to undefined method {0}::{1}()",
getName(), toMethod(name, nameLen)));
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Evaluates a method.
*/
@Override
public Value callClassMethod(Env env, AbstractFunction fun, Value []args)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
return fun.callMethod(env, this, args);
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Returns the value for the variable, creating an object if the var
* is unset.
*/
@Override
public Value getObject(Env env)
{
return this;
}
@Override
public Value getObject(Env env, Value index)
{
// php/3d92
env.error(L.l("Can't use object '{0}' as array", getName()));
return NullValue.NULL;
}
/**
* Copy for assignment.
*/
@Override
public Value copy()
{
return this;
}
/**
* Copy for serialization
*/
@Override
public Value copy(Env env, IdentityHashMap<Value,Value> map)
{
Value oldValue = map.get(this);
if (oldValue != null)
return oldValue;
// php/4048 - needs to be deep copy
return new ObjectExtValue(env, map, this);
}
/**
* Copy for serialization
*/
@Override
public Value copyTree(Env env, CopyRoot root)
{
// php/420c
Value copy = root.getCopy(this);
if (copy != null)
return copy;
else
return new CopyObjectExtValue(env, this, root);
}
/**
* Clone the object
*/
@Override
public Value clone()
{
ObjectExtValue newObject = new ObjectExtValue(_quercusClass);
for (Map.Entry<Value,Value> entry : entrySet()) {
newObject.putThisField(null,
(StringValue) entry.getKey(),
entry.getValue());
}
return newObject;
}
// XXX: need to check the other copy, e.g. for sessions
/*
* Serializes the value.
*
* @param sb holds result of serialization
* @param serializeMap holds reference indexes
*/
@Override
public void serialize(Env env,
StringBuilder sb, SerializeMap serializeMap)
{
Integer index = serializeMap.get(this);
if (index != null) {
sb.append("r:");
sb.append(index);
sb.append(";");
return;
}
serializeMap.put(this);
serializeMap.incrementIndex();
sb.append("O:");
sb.append(_className.length());
sb.append(":\"");
sb.append(_className);
sb.append("\":");
sb.append(getSize());
sb.append(":{");
Iterator<Entry> iter = new EntryIterator(_entries);
while (iter.hasNext()) {
Entry entry = iter.next();
sb.append("s:");
Value key = entry.getKey();
int len = key.length();
if (entry._visibility == FieldVisibility.PROTECTED) {
sb.append(len + 3);
sb.append(":\"");
sb.append("\u0000*\u0000");
}
else if (entry._visibility == FieldVisibility.PRIVATE) {
sb.append(len + 3);
sb.append(":\"");
sb.append("\u0000A\u0000");
}
else {
sb.append(len);
sb.append(":\"");
}
sb.append(key);
sb.append("\";");
Value value = ((Entry) entry).getRawValue();
value.serialize(env, sb, serializeMap);
}
sb.append("}");
}
/**
* Exports the value.
*/
@Override
public void varExport(StringBuilder sb)
{
sb.append(getName());
sb.append("::__set_state(array(\n");
for (Map.Entry<Value,Value> entry : entrySet()) {
sb.append(" ");
entry.getKey().varExport(sb);
sb.append(" => ");
entry.getValue().varExport(sb);
sb.append(",\n");
}
sb.append("))");
}
/**
* Append to a string builder.
*/
public StringValue appendTo(UnicodeBuilderValue sb)
{
return sb.append(toReprString(Env.getInstance()));
}
/**
* Append to a binary builder.
*/
public StringValue appendTo(StringBuilderValue sb)
{
return sb.append(toReprString(Env.getInstance()));
}
/**
* Append to a binary builder.
*/
public StringValue appendTo(BinaryBuilderValue sb)
{
return sb.appendBytes(toReprString(Env.getInstance()));
}
/**
* Append to a binary builder.
*/
public StringValue appendTo(LargeStringBuilderValue sb)
{
return sb.append(toReprString(Env.getInstance()));
}
/**
* Converts to a string builder
*/
@Override
public StringValue toStringBuilder(Env env)
{
return toReprString(env).toStringBuilder(env);
}
/**
* Converts to a java String object.
*/
public String toJavaString()
{
return toReprString(Env.getInstance()).toString();
}
/**
* Converts to a string.
* @param env
*/
@Override
public StringValue toReprString(Env env)
{
QuercusClass oldClass = env.setCallingClass(_quercusClass);
try {
AbstractFunction fun = _quercusClass.findFunction("__toString");
if (fun != null)
return fun.callMethod(env, this, new Expr[0]).toStringValue(env);
else
return env.createStringOld(_className + "[]");
} finally {
env.setCallingClass(oldClass);
}
}
/**
* Converts to a string.
* @param env
*/
@Override
public void print(Env env)
{
env.print(toReprString(env));
}
/**
* Converts to an array.
*/
@Override
public Value toArray()
{
ArrayValue array = new ArrayValueImpl();
for (Map.Entry<Value,Value> entry : entrySet()) {
array.put(entry.getKey(), entry.getValue());
}
return array;
}
/**
* Converts to an object.
*/
@Override
public Value toObject(Env env)
{
return this;
}
/**
* Converts to an object.
*/
@Override
public Object toJavaObject()
{
return this;
}
@Override
public Set<? extends Map.Entry<Value,Value>> entrySet()
{
return new EntrySet();
}
/**
* Returns a Set of entries, sorted by key.
*/
public Set<? extends Map.Entry<Value,Value>> sortedEntrySet()
{
return new TreeSet<Map.Entry<Value, Value>>(entrySet());
}
//
// debugging
//
//XXX: push up to super, and use varDumpObject
public void varDumpImpl(Env env,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException
{
int size = getSize();
if (isIncompleteObject())
size++;
out.println("object(" + getName() + ") (" + size + ") {");
if (isIncompleteObject()) {
printDepth(out, 2 * (depth + 1));
out.println("[\"__Quercus_Incomplete_Class_name\"]=>");
printDepth(out, 2 * (depth + 1));
Value value = env.createStringOld(getIncompleteObjectName());
value.varDump(env, out, depth + 1, valueSet);
out.println();
}
for (Map.Entry<Value,Value> mapEntry : sortedEntrySet()) {
ObjectExtValue.Entry entry = (ObjectExtValue.Entry) mapEntry;
entry.varDumpImpl(env, out, depth + 1, valueSet);
}
printDepth(out, 2 * depth);
out.print("}");
}
@Override
protected void printRImpl(Env env,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException
{
out.print(getName());
out.print(' ');
out.println("Object");
printDepth(out, 4 * depth);
out.println("(");
for (Map.Entry<Value,Value> mapEntry : sortedEntrySet()) {
ObjectExtValue.Entry entry = (ObjectExtValue.Entry) mapEntry;
entry.printRImpl(env, out, depth + 1, valueSet);
}
printDepth(out, 4 * depth);
out.println(")");
}
//
// Java Serialization
//
private void writeObject(ObjectOutputStream out)
throws IOException
{
out.writeObject(_className);
out.writeInt(_size);
for (Map.Entry<Value,Value> entry : entrySet()) {
out.writeObject(entry.getKey());
out.writeObject(entry.getValue());
}
}
private void readObject(ObjectInputStream in)
throws ClassNotFoundException, IOException
{
Env env = Env.getInstance();
String name = (String) in.readObject();
QuercusClass cl = env.findClass(name);
init();
if (cl != null) {
setQuercusClass(cl);
}
else {
cl = env.getQuercus().getStdClass();
setQuercusClass(cl);
setIncompleteObjectName(name);
}
int size = in.readInt();
for (int i = 0; i < size; i++) {
putThisField(env,
(StringValue) in.readObject(),
(Value) in.readObject());
}
}
public void cleanup(Env env)
{
AbstractFunction fun = getQuercusClass().getDestructor();
if (fun != null)
fun.callMethod(env, this);
}
private static String toMethod(char []key, int keyLength)
{
return new String(key, 0, keyLength);
}
@Override
public String toString()
{
return getClass().getSimpleName() + "@" + System.identityHashCode(this) + "[" + _className + "]";
}
public class EntrySet extends AbstractSet<Map.Entry<Value,Value>> {
EntrySet()
{
}
@Override
public int size()
{
return ObjectExtValue.this.getSize();
}
@Override
public Iterator<Map.Entry<Value,Value>> iterator()
{
return new KeyValueIterator(ObjectExtValue.this._entries);
}
}
public static class EntryIterator
implements Iterator<Entry>
{
private final Entry []_list;
private int _index;
private Entry _entry;
EntryIterator(Entry []list)
{
_list = list;
}
public boolean hasNext()
{
if (_entry != null)
return true;
for (; _index < _list.length && _list[_index] == null; _index++) {
}
return _index < _list.length;
}
public Entry next()
{
if (_entry != null) {
Entry entry = _entry;
_entry = entry._next;
return entry;
}
for (; _index < _list.length && _list[_index] == null; _index++) {
}
if (_list.length <= _index)
return null;
Entry entry = _list[_index++];
_entry = entry._next;
return entry;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
public static class KeyValueIterator
implements Iterator<Map.Entry<Value,Value>>
{
private final Entry []_list;
private int _index;
private Entry _entry;
KeyValueIterator(Entry []list)
{
_list = list;
}
public boolean hasNext()
{
if (_entry != null)
return true;
for (; _index < _list.length && _list[_index] == null; _index++) {
}
return _index < _list.length;
}
public Map.Entry<Value,Value> next()
{
if (_entry != null) {
Entry entry = _entry;
_entry = entry._next;
return entry;
}
for (; _index < _list.length && _list[_index] == null; _index++) {
}
if (_list.length <= _index)
return null;
Entry entry = _list[_index++];
_entry = entry._next;
return entry;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
public static class ValueIterator
implements Iterator<Value>
{
private final Entry []_list;
private int _index;
private Entry _entry;
ValueIterator(Entry []list)
{
_list = list;
}
public boolean hasNext()
{
if (_entry != null)
return true;
for (; _index < _list.length && _list[_index] == null; _index++) {
}
return _index < _list.length;
}
public Value next()
{
if (_entry != null) {
Entry entry = _entry;
_entry = entry._next;
return entry._value;
}
for (; _index < _list.length && _list[_index] == null; _index++) {
}
if (_list.length <= _index)
return null;
Entry entry = _list[_index++];
_entry = entry._next;
return entry._value;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
public static class KeyIterator
implements Iterator<Value>
{
private final Entry []_list;
private int _index;
private Entry _entry;
KeyIterator(Entry []list)
{
_list = list;
}
public boolean hasNext()
{
if (_entry != null)
return true;
for (; _index < _list.length && _list[_index] == null; _index++) {
}
return _index < _list.length;
}
public Value next()
{
if (_entry != null) {
Entry entry = _entry;
_entry = entry._next;
return entry._key;
}
for (; _index < _list.length && _list[_index] == null; _index++) {
}
if (_list.length <= _index)
return null;
Entry entry = _list[_index++];
_entry = entry._next;
return entry._key;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
public final static class Entry
implements Map.Entry<Value,Value>,
Comparable<Map.Entry<Value, Value>>
{
private final StringValue _key;
private final FieldVisibility _visibility;
private Value _value;
Entry _prev;
Entry _next;
public Entry(StringValue key)
{
_key = key;
_visibility = FieldVisibility.PUBLIC;
_value = NullValue.NULL;
}
public Entry(StringValue key, FieldVisibility visibility)
{
_key = key;
_visibility = visibility;
_value = NullValue.NULL;
}
public Entry(StringValue key, Value value)
{
_key = key;
_visibility = FieldVisibility.PUBLIC;
_value = value;
}
public Entry(StringValue key, Value value, FieldVisibility visibility)
{
_key = key;
_visibility = visibility;
_value = value;
}
public Entry(Env env, IdentityHashMap<Value,Value> map, Entry entry)
{
_key = entry._key;
_visibility = entry._visibility;
_value = entry._value.copy(env, map);
}
public Value getValue()
{
return _value.toValue();
}
public Value getRawValue()
{
return _value;
}
public Value getKey()
{
return _key;
}
public final boolean isPrivate()
{
return _visibility == FieldVisibility.PRIVATE;
}
public Value toValue()
{
// The value may be a var
// XXX: need test
return _value.toValue();
}
/**
* Argument used/declared as a ref.
*/
public Var toRefVar()
{
Var var = _value.toRefVar();
_value = var;
return var;
}
/**
* Converts to an argument value.
*/
public Value toArgValue()
{
return _value.toValue();
}
public Value setValue(Value value)
{
Value oldValue = toValue();
_value = value;
return oldValue;
}
/**
* Converts to a variable reference (for function arguments)
*/
public Value toRef()
{
Value value = _value;
if (value instanceof Var)
return new RefVar((Var) value);
else {
Var var = new Var(_value);
_value = var;
return new RefVar(var);
}
}
/**
* Converts to a variable reference (for function arguments)
*/
public Value toArgRef()
{
Value value = _value;
if (value instanceof Var)
return new RefVar((Var) value);
else {
Var var = new Var(_value);
_value = var;
return new RefVar(var);
}
}
public Value toArg()
{
Value value = _value;
if (value instanceof Var)
return value;
else {
Var var = new Var(_value);
_value = var;
return var;
}
}
Entry copyTree(Env env, CopyRoot root)
{
Value copy = root.getCopy(_value);
if (copy == null)
copy = _value.copyTree(env, root);
return new Entry(_key, copy, _visibility);
}
public int compareTo(Map.Entry<Value, Value> other)
{
if (other == null)
return 1;
Value thisKey = getKey();
Value otherKey = other.getKey();
if (thisKey == null)
return otherKey == null ? 0 : -1;
if (otherKey == null)
return 1;
return thisKey.cmp(otherKey);
}
public void varDumpImpl(Env env,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException
{
String suffix = "";
if (_visibility == FieldVisibility.PROTECTED)
suffix = ":protected";
else if (_visibility == FieldVisibility.PRIVATE)
suffix = ":private";
printDepth(out, 2 * depth);
out.println("[\"" + getKey() + suffix + "\"]=>");
printDepth(out, 2 * depth);
_value.varDump(env, out, depth, valueSet);
out.println();
}
protected void printRImpl(Env env,
WriteStream out,
int depth,
IdentityHashMap<Value, String> valueSet)
throws IOException
{
String suffix = "";
if (_visibility == FieldVisibility.PROTECTED)
suffix = ":protected";
else if (_visibility == FieldVisibility.PRIVATE)
suffix = ":private";
printDepth(out, 4 * depth);
out.print("[" + getKey() + suffix + "] => ");
_value.printR(env, out, depth + 1, valueSet);
out.println();
}
private void printDepth(WriteStream out, int depth)
throws java.io.IOException
{
for (int i = 0; i < depth; i++)
out.print(' ');
}
@Override
public String toString()
{
return "ObjectExtValue.Entry[" + getKey() + "]";
}
}
}