/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus.env; import com.caucho.quercus.function.AbstractFunction; import com.caucho.quercus.marshal.Marshal; import com.caucho.quercus.marshal.MarshalFactory; import com.caucho.vfs.WriteStream; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Array; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; /** * Represents a PHP array value. */ abstract public class ArrayValue extends Value { private static final Logger log = Logger.getLogger(ArrayValue.class.getName()); protected static final StringValue KEY = new ConstStringValue("key"); protected static final StringValue VALUE = new ConstStringValue("value"); public static final GetKey GET_KEY = new GetKey(); public static final GetValue GET_VALUE = new GetValue(); public static final StringValue ARRAY = new ConstStringValue("Array"); private Entry _current; protected ArrayValue() { } /** * Returns the type. */ @Override public String getType() { return "array"; } /** * Returns the ValueType. */ @Override public ValueType getValueType() { return ValueType.ARRAY; } // // marshal costs // /** * Cost to convert to a character */ @Override public int toCharMarshalCost() { return Marshal.COST_INCOMPATIBLE; } /** * Cost to convert to a string */ @Override public int toStringMarshalCost() { return Marshal.COST_INCOMPATIBLE; } /** * Cost to convert to a binary value */ @Override public int toBinaryValueMarshalCost() { return Marshal.COST_INCOMPATIBLE; } /** * Cost to convert to a StringValue */ @Override public int toStringValueMarshalCost() { return Marshal.COST_INCOMPATIBLE; } /** * Cost to convert to a UnicodeValue */ @Override public int toUnicodeValueMarshalCost() { return Marshal.COST_INCOMPATIBLE; } /** * Converts to a boolean. */ @Override public boolean toBoolean() { return getSize() != 0; } /** * Converts to a long. */ @Override public long toLong() { if (getSize() > 0) return 1; else return 0; } /** * Converts to a double. */ @Override public double toDouble() { return toLong(); } /** * Converts to a string. */ @Override public String toString() { return "Array"; } /** * Converts to an object. */ public Object toObject() { return null; } /** * Converts to an array if null. */ @Override public Value toAutoArray() { return this; } /** * Converts to a java object. */ @Override public Object toJavaObject() { return this; } protected Entry getCurrent() { return _current; } protected void setCurrent(Entry entry) { _current = entry; } // // Conversions // /** * Converts to an object. */ @Override public Value toArray() { return this; } /** * Converts to an array value */ @Override public ArrayValue toArrayValue(Env env) { return this; } /** * Converts to an object. */ @Override public Value toObject(Env env) { Value obj = env.createObject(); for (Entry entry = getHead(); entry != null; entry = entry._next) { Value key = entry.getKey(); // php/03oe obj.putField(env, key.toString(), entry.getValue()); } return obj; } /** * Converts to a java List object. */ @Override public Collection toJavaCollection(Env env, Class type) { Collection coll = null; if (type.isAssignableFrom(HashSet.class)) { coll = new HashSet(); } else if (type.isAssignableFrom(TreeSet.class)) { coll = new TreeSet(); } else { try { coll = (Collection) type.newInstance(); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); env.warning(L.l("Can't assign array to {0}", type.getName())); return null; } } for (Entry entry = getHead(); entry != null; entry = entry._next) { coll.add(entry.getValue().toJavaObject()); } return coll; } /** * Converts to a java List object. */ @Override public List toJavaList(Env env, Class type) { List list = null; if (type.isAssignableFrom(ArrayList.class)) { list = new ArrayList(); } else if (type.isAssignableFrom(LinkedList.class)) { list = new LinkedList(); } else if (type.isAssignableFrom(Vector.class)) { list = new Vector(); } else { try { list = (List) type.newInstance(); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); env.warning(L.l("Can't assign array to {0}", type.getName())); return null; } } for (Entry entry = getHead(); entry != null; entry = entry._next) { list.add(entry.getValue().toJavaObject()); } return list; } /** * Converts to a java object. */ @Override public Map toJavaMap(Env env, Class type) { Map map = null; if (type.isAssignableFrom(TreeMap.class)) { map = new TreeMap(); } else if (type.isAssignableFrom(LinkedHashMap.class)) { map = new LinkedHashMap(); } else { try { map = (Map) type.newInstance(); } catch (Throwable e) { log.log(Level.FINE, e.toString(), e); env.warning(L.l("Can't assign array to {0}", type.getName())); return null; } } for (Entry entry = getHead(); entry != null; entry = entry._next) { map.put(entry.getKey().toJavaObject(), entry.getValue().toJavaObject()); } return map; } @Override public boolean isCallable(Env env) { Value obj = get(LongValue.ZERO); Value nameV = get(LongValue.ONE); if (! nameV.isString()) { return false; } String name = nameV.toString(); if (obj.isObject()) { int p = name.indexOf("::"); // php/09lf if (p > 0) { String clsName = name.substring(0, p); name = name.substring(p + 2); QuercusClass cls = env.findClass(clsName); if (cls == null) { return false; } } return true; } else { QuercusClass cl = env.findClass(obj.toString()); if (cl == null) { return false; } return true; } } /** * Converts to a callable object. */ @Override public Callable toCallable(Env env) { Value obj = get(LongValue.ZERO); Value nameV = get(LongValue.ONE); if (! nameV.isString()) { env.warning(L.l("'{0}' ({1}) is an unknown callback name", nameV, nameV.getClass().getSimpleName())); return super.toCallable(env); } String name = nameV.toString(); if (obj.isObject()) { AbstractFunction fun; int p = name.indexOf("::"); // php/09lf if (p > 0) { String clsName = name.substring(0, p); name = name.substring(p + 2); QuercusClass cls = env.findClass(clsName); if (cls == null) { env.warning(L.l( "Callback: '{0}' is not a valid callback class for {1}", clsName, name)); return super.toCallable(env); } return new CallbackClassMethod(cls, env.createString(name), obj); } return new CallbackObjectMethod(env, obj, env.createString(name)); } else { QuercusClass cl = env.findClass(obj.toString()); if (cl == null) { env.warning( L.l("Callback: '{0}' is not a valid callback string for {1}", obj.toString(), obj)); return super.toCallable(env); } return new CallbackObjectMethod(env, cl, env.createString(name)); } } public final Value callCallback(Env env, Callable callback, Value key) { Value result; Value value = getRaw(key); if (value instanceof Var) { value = new ArgRef((Var) value); result = call(env, value); } else { Value aVar = new Var(value); result = callback.call(env, aVar); Value aNew = aVar.toValue(); if (aNew != value) put(key, aNew); } return result; } public final Value callCallback(Env env, Callable callback, Value key, Value a2) { Value result; Value value = getRaw(key); if (value instanceof Var) { value = new ArgRef((Var) value); result = callback.call(env, value, a2); } else { Value aVar = new Var(value); result = callback.call(env, aVar, a2); Value aNew = aVar.toValue(); if (aNew != value) put(key, aNew); } return result; } public final Value callCallback(Env env, Callable callback, Value key, Value a2, Value a3) { Value result; Value value = getRaw(key); if (value instanceof Var) { value = new ArgRef((Var) value); result = callback.call(env, value, a2, a3); } else { Value aVar = new Var(value); result = callback.call(env, aVar, a2, a3); Value aNew = aVar.toValue(); if (aNew != value) put(key, aNew); } return result; } /** * Returns true for an array. */ @Override public boolean isArray() { return true; } /** * Copy as a return value */ @Override public Value copyReturn() { return copy(); // php/3a5e } /** * Copy for assignment. */ @Override abstract public Value copy(); @Override public Value toLocalRef() { return copy(); } /** * Copy for serialization */ @Override abstract public Value copy(Env env, IdentityHashMap<Value,Value> map); /** * Returns the size. */ @Override abstract public int getSize(); /** * Returns the count(). */ @Override public int getCount(Env env) { return getSize(); } /** * Returns the count(). */ @Override public int getCountRecursive(Env env) { int count = getCount(env); for (Map.Entry<Value,Value> entry : entrySet()) { Value value = entry.getValue(); if (value.isArray()) count += value.getCountRecursive(env); } return count; } /** * Returns true if the value is empty */ @Override public boolean isEmpty() { return getSize() == 0; } /** * Clears the array */ abstract public void clear(); @Override public int cmp(Value rValue) { return cmpImpl(rValue, 1); } private int cmpImpl(Value rValue, int resultIfKeyMissing) { // "if key from operand 1 is not found in operand 2 then // arrays are uncomparable, otherwise - compare value by value" // php/335h if (!rValue.isArray()) return 1; int lSize = getSize(); int rSize = rValue.toArray().getSize(); if (lSize != rSize) return lSize < rSize ? -1 : 1; for (Map.Entry<Value,Value> entry : entrySet()) { Value lElementValue = entry.getValue(); Value rElementValue = rValue.get(entry.getKey()); if (!rElementValue.isset()) return resultIfKeyMissing; int cmp = lElementValue.cmp(rElementValue); if (cmp != 0) return cmp; } return 0; } /** * Returns true for less than */ @Override public boolean lt(Value rValue) { // php/335h return cmpImpl(rValue, 1) < 0; } /** * Returns true for less than or equal to */ @Override public boolean leq(Value rValue) { // php/335h return cmpImpl(rValue, 1) <= 0; } /** * Returns true for greater than */ @Override public boolean gt(Value rValue) { // php/335h return cmpImpl(rValue, -1) > 0; } /** * Returns true for greater than or equal to */ @Override public boolean geq(Value rValue) { // php/335h return cmpImpl(rValue, -1) >= 0; } /** * Adds a new value. */ @Override public Value put(Value key, Value value) { append(key, value); return value; } /** * Adds a new value. */ public final void put(StringValue keyBinary, StringValue keyUnicode, Value value, boolean isUnicode) { if (isUnicode) append(keyUnicode, value); else append(keyBinary, value); } /** * Add */ @Override abstract public Value put(Value value); /** * Add to front. */ abstract public ArrayValue unshift(Value value); /** * Splices. */ abstract public ArrayValue splice(int begin, int end, ArrayValue replace); /** * Slices. */ public ArrayValue slice(Env env, int start, int end, boolean isPreserveKeys) { ArrayValueImpl array = new ArrayValueImpl(); Iterator<Map.Entry<Value,Value>> iter = array.getIterator(env); for (int i = 0; i < end && iter.hasNext(); i++) { Map.Entry<Value,Value> entry = iter.next(); if (start <= i) { Value key = entry.getKey(); Value value = entry.getValue(); if ((key.isString()) || isPreserveKeys) array.put(key, value); else array.put(value); } } return array; } /** * Returns the value as an array. */ @Override public Value getArray(Value index) { Value value = get(index); Value array = value.toAutoArray(); if (value != array) { value = array; put(index, value); } return value; } /** * Returns the value as an argument which may be a reference. */ @Override abstract public Value getArg(Value index, boolean isTop); /** * Returns the field value, creating an object if it's unset. */ @Override public Value getObject(Env env, Value fieldName) { Value value = get(fieldName); Value object = value.toAutoObject(env); if (value != object) { value = object; put(fieldName, value); } return value; } /** * Sets the array ref. */ @Override abstract public Var putVar(); /** * Creatse a tail index. */ abstract public Value createTailKey(); /** * Returns a union of this array and the rValue as array. * If the rValue is not an array, the returned union contains the elements * of this array only. * * To append a value to this ArrayValue use the {@link #put(Value)} method. */ @Override public Value add(Value rValue) { rValue = rValue.toValue(); if (! rValue.isArray()) return copy(); ArrayValue result = new ArrayValueImpl(this); for (Map.Entry<Value,Value> entry : ((ArrayValue) rValue).entrySet()) { Value key = entry.getKey(); if (result.get(key) == UnsetValue.UNSET) { // php/330c drupal disabled textarea result.put(key, entry.getValue().copy()); } } return result; } @Override public Iterator<Map.Entry<Value, Value>> getBaseIterator(Env env) { return new EntryIterator(getHead()); } public Iterator<Map.Entry<Value, Value>> getIterator() { return new EntryIterator(getHead()); } @Override public Iterator<Value> getKeyIterator(Env env) { return new KeyIterator(getHead()); } @Override public Iterator<Value> getValueIterator(Env env) { return new ValueIterator(getHead()); } /** * Gets a new value. */ @Override abstract public Value get(Value key); /** * Returns the value in the array as-is. * (i.e. without calling toValue() on it). */ public Value getRaw(Value key) { return get(key); } /** * Returns true if the value is set. */ @Override public boolean isset(Value key) { Value value = get(key); // php/0d40 return value != null && value.isset(); } /** * Returns true if the key exists in the array. */ @Override public boolean keyExists(Value key) { Value value = get(key); // php/173m return value != UnsetValue.UNSET; } /** * Removes a value. */ @Override abstract public Value remove(Value key); /** * Returns the array ref. */ @Override abstract public Var getVar(Value index); /** * Returns an iterator of the entries. */ public Set<Value> keySet() { return new KeySet(); } /** * Returns a set of all the of the entries. */ public Set<Map.Entry<Value,Value>> entrySet() { return new EntrySet(); } /** * Returns a collection of the values. */ public Collection<Value> values() { return new ValueCollection(); } /** * Convenience for lib. */ public void put(String key, String value) { // XXX: this needs an Env arg because of i18n // XXX: but some modules have arrays that are static constants put(StringValue.create(key), StringValue.create(value)); } /** * Convenience for lib. */ public void put(Env env, String key, String value) { put(env.createString(key), env.createString(value)); } /** * Convenience for lib. */ public void put(String key, char value) { // XXX: this needs an Env arg because of i18n put(StringValue.create(key), StringValue.create(value)); } /** * Convenience for lib. */ public void put(String key, long value) { // XXX: this needs an Env arg because of i18n put(StringValue.create(key), LongValue.create(value)); } /** * Convenience for lib. */ public void put(Env env, String key, long value) { put(env.createString(key), LongValue.create(value)); } /** * Convenience for lib. */ public void put(String key, double value) { // XXX: this needs an Env arg because of i18n put(StringValue.create(key), new DoubleValue(value)); } /** * Convenience for lib. */ public void put(String key, boolean value) { // XXX: this needs an Env arg because of i18n put(StringValue.create(key), value ? BooleanValue.TRUE : BooleanValue.FALSE); } /** * Convenience for lib. */ public void put(Env env, String key, boolean value) { put(env.createString(key), value ? BooleanValue.TRUE : BooleanValue.FALSE); } /** * Convenience for lib. */ public void put(String value) { // XXX: this needs an Env arg because of i18n put(StringValue.create(value)); } /** * Convenience for lib. */ public void put(long value) { put(LongValue.create(value)); } /** * Appends as an argument - only called from compiled code * * XXX: change name to appendArg */ abstract public ArrayValue append(Value key, Value value); /** * Appends as an argument - only called from compiled code * * XXX: change name to appendArg */ public ArrayValue append(Value value) { put(value); return this; } /** * Puts all of the arg elements into this array. */ public void putAll(ArrayValue array) { for (Map.Entry<Value, Value> entry : array.entrySet()) put(entry.getKey(), entry.getValue()); } /** * Convert to an array. */ public static Value toArray(Value value) { value = value.toValue(); if (value instanceof ArrayValue) return value; else return new ArrayValueImpl().put(value); } /** * Prints the value. * @param env */ @Override public void print(Env env) { env.print("Array"); } /** * Pops the top value. */ abstract public Value pop(Env env); /** * Shuffles the array */ abstract public Value shuffle(); /** * Returns the head. */ // XX: php/153v getHead needed by grep for getRawValue() abstract public Entry getHead(); /** * Returns the tail. */ abstract protected Entry getTail(); /** * Returns the current value. */ @Override public Value current() { if (_current != null) return _current.getValue(); else return BooleanValue.FALSE; } /** * Returns the current key */ @Override public Value key() { if (_current != null) return _current.getKey(); else return NullValue.NULL; } /** * Returns true if there are more elements. */ @Override public boolean hasCurrent() { return _current != null; } /** * Returns the next value. */ @Override public Value next() { if (_current != null) _current = _current._next; return current(); } /** * Returns the previous value. */ @Override public Value prev() { if (_current != null) _current = _current._prev; return current(); } /** * The each iterator */ public Value each() { if (_current == null) return BooleanValue.FALSE; ArrayValue result = new ArrayValueImpl(); result.put(LongValue.ZERO, _current.getKey()); result.put(KEY, _current.getKey()); result.put(LongValue.ONE, _current.getValue()); result.put(VALUE, _current.getValue()); _current = _current._next; return result; } /** * Returns the first value. */ @Override public Value reset() { _current = getHead(); return current(); } /** * Returns the last value. */ @Override public Value end() { _current = getTail(); return current(); } /** * Returns the corresponding key if this array contains the given value * * @param value to search for in the array * * @return the key if it is found in the array, NULL otherwise */ abstract public Value contains(Value value); /** * Returns the corresponding key if this array contains the given value * * @param value to search for in the array * * @return the key if it is found in the array, NULL otherwise */ abstract public Value containsStrict(Value value); /** * 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 */ abstract public Value containsKey(Value key); /** * Returns an object array of this array. This is a copy of this object's * backing structure. Null elements are not included. * * @return an object array of this array */ public Map.Entry<Value, Value>[] toEntryArray() { ArrayList<Map.Entry<Value, Value>> array = new ArrayList<Map.Entry<Value, Value>>(getSize()); for (Entry entry = getHead(); entry != null; entry = entry._next) array.add(entry); Map.Entry<Value, Value>[]result = new Entry[array.size()]; return array.toArray(result); } /** * Sorts this array based using the passed Comparator * * @param comparator the comparator for sorting the array * @param resetKeys true if the keys should not be preserved * @param strict true if alphabetic keys should not be preserved */ public void sort(Comparator<Map.Entry<Value, Value>> comparator, boolean resetKeys, boolean strict) { Entry []entries; entries = new Entry[getSize()]; int i = 0; for (Entry entry = getHead(); entry != null; entry = entry._next) { entries[i++] = entry; } Arrays.sort(entries, comparator); clear(); long base = 0; if (! resetKeys) strict = false; for (int j = 0; j < entries.length; j++) { Value key = entries[j].getKey(); if (resetKeys && (! (key instanceof StringValue) || strict)) put(LongValue.create(base++), entries[j].getValue()); else put(entries[j].getKey(), entries[j].getValue()); } } /* * Serializes the value. * * @param sb holds result of serialization * @param serializeMap holds reference indexes */ @Override public void serialize(Env env, StringBuilder sb, SerializeMap serializeMap) { sb.append("a:"); sb.append(getSize()); sb.append(":{"); serializeMap.incrementIndex(); for (Entry entry = getHead(); entry != null; entry = entry._next) { entry.getKey().serialize(env, sb); entry.getRawValue().serialize(env, sb, serializeMap); } sb.append("}"); } /** * Exports the value. */ @Override public void varExport(StringBuilder sb) { sb.append("array ("); sb.append("\n"); //boolean isFirst = true; for (Entry entry = getHead(); entry != null; entry = entry._next) { sb.append(" "); entry.getKey().varExport(sb); sb.append(" => "); entry.getValue().varExport(sb); sb.append(",\n"); } sb.append(")"); } /** * Encodes the value in JSON. */ @Override public void jsonEncode(Env env, StringValue sb) { long length = 0; Iterator<Value> keyIter = getKeyIterator(env); while (keyIter.hasNext()) { Value key = keyIter.next(); if ((! key.isLongConvertible()) || key.toLong() != length) { jsonEncodeAssociative(env, sb); return; } length++; } sb.append('['); length = 0; for (Value value : values()) { if (length > 0) sb.append(','); value.jsonEncode(env, sb); length++; } sb.append(']'); } private void jsonEncodeAssociative(Env env, StringValue sb) { sb.append('{'); int length = 0; Iterator<Map.Entry<Value,Value>> iter = getIterator(env); while (iter.hasNext()) { Map.Entry<Value,Value> entry = iter.next(); if (length > 0) sb.append(','); entry.getKey().toStringValue().jsonEncode(env, sb); sb.append(':'); entry.getValue().jsonEncode(env, sb); length++; } sb.append('}'); } /** * Resets all numerical keys with the first index as base * * @param base the initial index * @param strict if true, string keys are also reset */ public boolean keyReset(long base, boolean strict) { Entry []entries; entries = new Entry[getSize()]; int i = 0; for (Entry entry = getHead(); entry != null; entry = entry._next) { entries[i++] = entry; } clear(); for (int j = 0; j < entries.length; j++) { Value key = entries[j].getKey(); if (! (key instanceof StringValue) || strict) put(LongValue.create(base++), entries[j].getValue()); else put(entries[j].getKey(), entries[j].getValue()); } return true; } /** * Test for equality * * @param rValue rhs ArrayValue to compare to * * @return true if this is equal to rValue, false otherwise */ @Override public boolean eq(Value rValue) { if (rValue == null) return false; for (Map.Entry<Value, Value> entry : entrySet()) { Value entryValue = entry.getValue(); Value entryKey = entry.getKey(); Value rEntryValue = rValue.get(entryKey); if ((rEntryValue instanceof ArrayValue) && ! entryValue.eq((ArrayValue) rEntryValue)) return false; if (! entryValue.eq(rEntryValue)) return false; } return true; } /** * Test for === * * @param rValue rhs ArrayValue to compare to * * @return true if this is equal to rValue, false otherwise */ @Override public boolean eql(Value rValue) { if (rValue == this) return true; else if (rValue == null) return false; else if (getSize() != rValue.getSize()) return false; rValue = rValue.toValue(); if (rValue == this) return true; else if (! (rValue instanceof ArrayValue)) return false; ArrayValue rArray = (ArrayValue) rValue; Iterator<Map.Entry<Value,Value>> iterA = entrySet().iterator(); Iterator<Map.Entry<Value,Value>> iterB = rArray.entrySet().iterator(); while (iterA.hasNext() && iterB.hasNext()) { Map.Entry<Value,Value> entryA = iterA.next(); Map.Entry<Value,Value> entryB = iterB.next(); if (! entryA.getKey().eql(entryB.getKey())) return false; if (! entryA.getValue().eql(entryB.getValue())) return false; } if (iterA.hasNext() || iterB.hasNext()) return false; else return true; } /** * Converts to a key. */ public Value toKey() { return ARRAY; } @Override public void varDumpImpl(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet) throws IOException { out.println("array(" + getSize() + ") {"); for (Map.Entry<Value,Value> mapEntry : entrySet()) { varDumpEntry(env, out, depth + 1, valueSet, mapEntry); out.println(); } printDepth(out, 2 * depth); out.print("}"); } protected void varDumpEntry(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet, Map.Entry<Value, Value> mapEntry) throws IOException { ArrayValue.Entry entry = (ArrayValue.Entry) mapEntry; entry.varDumpImpl(env, out, depth, valueSet); } @Override protected void printRImpl(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet) throws IOException { out.println("Array"); printDepth(out, 8 * depth); out.println("("); for (Map.Entry<Value,Value> mapEntry : entrySet()) { ArrayValue.Entry entry = (ArrayValue.Entry) mapEntry; entry.printRImpl(env, out, depth, valueSet); } printDepth(out, 8 * depth); out.println(")"); } protected void printREntry(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet, Map.Entry<Value, Value> mapEntry) throws IOException { ArrayValue.Entry entry = (ArrayValue.Entry) mapEntry; entry.printRImpl(env, out, depth, valueSet); } public static final class Entry implements Map.Entry<Value,Value>, Serializable { private final Value _key; private Value _value; // Var _var; Entry _prev; private Entry _next; private Entry _nextHash; public Entry(Value key) { _key = key; _value = NullValue.NULL; } public Entry(Value key, Value value) { _key = key; _value = value; } public Entry(Entry entry) { _key = entry._key; /* if (entry._var != null) _var = entry._var; else _value = entry._value.copyArrayItem(); */ _value = entry._value.copyArrayItem(); } public final Entry getNext() { return _next; } public final void setNext(final Entry next) { _next = next; } public final Entry getPrev() { return _prev; } public final void setPrev(final Entry prev) { _prev = prev; } public final Entry getNextHash() { return _nextHash; } public final void setNextHash(Entry next) { _nextHash = next; } public Value getRawValue() { // return _var != null ? _var : _value; return _value; } public Value getValue() { // return _var != null ? _var.toValue() : _value; return _value.toValue(); } public Value getKey() { return _key; } public Value toValue() { // The value may be a var // XXX: need test // return _var != null ? _var.toValue() : _value; return _value.toValue(); } public Var toVar() { Var var = _value.toVar(); _value = var; return var; } /** * Argument used/declared as a ref. */ public Var toRefVar() { // php/376a Var var = _value.toVar(); _value = var; return var; /* if (_var != null) return _var; else { _var = new Var(_value); return _var; } */ } /** * Converts to an argument value. */ public Value toArgValue() { // return _var != null ? _var.toValue() : _value; return _value.toValue(); } public Value setValue(Value value) { Value oldValue = _value; _value = value; // _var = null; return oldValue; } public Value set(Value value) { Value oldValue = _value; // XXX: make OO /* if (value instanceof Var) _var = (Var) value; else if (_var != null) _var.set(value); else _value = value; */ if (value instanceof Var) _value = (Var) value; else { _value = _value.set(value); } return oldValue; } /** * Converts to a variable reference (for function arguments) */ public Value toRef() { /* if (_var == null) _var = new Var(_value); return new RefVar(_var); */ Var var = _value.toVar(); _value = var; return new ArgRef(var); } /** * Converts to a variable reference (for function arguments) */ public Value toArgRef() { Var var = _value.toVar(); _value = var; return new ArgRef(var); /* if (_var == null) _var = new Var(_value); return new RefVar(_var); */ } public Value toArg() { Var var = _value.toVar(); _value = var; // php/0d14 return var; // return new RefVar(var); /* if (_var == null) _var = new Var(_value); return _var; */ } public void varDumpImpl(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet) throws IOException { printDepth(out, 2 * depth); out.print("["); if (_key instanceof StringValue) out.print("\"" + _key + "\""); else out.print(_key); out.println("]=>"); printDepth(out, 2 * depth); getRawValue().varDump(env, out, depth, valueSet); } protected void printRImpl(Env env, WriteStream out, int depth, IdentityHashMap<Value, String> valueSet) throws IOException { printDepth(out, 8 * depth); out.print(" ["); out.print(_key); out.print("] => "); if (getRawValue() != null) getRawValue().printR(env, out, depth + 1, valueSet); out.println(); } private void printDepth(WriteStream out, int depth) throws java.io.IOException { for (int i = depth; i > 0; i--) out.print(' '); } @Override public String toString() { return "ArrayValue.Entry[" + getKey() + "]"; } } /** * Returns the field keys. */ public Value []getKeyArray(Env env) { int len = getSize(); Value []keys = new Value[len]; Iterator<Value> iter = getKeyIterator(env); for (int i = 0; i < len; i++) { keys[i] = iter.next(); } return keys; } /** * Returns the field values. */ public Value []getValueArray(Env env) { int len = getSize(); Value []values = new Value[len]; Iterator<Value> iter = getValueIterator(env); for (int i = 0; i < len; i++) { values[i] = iter.next(); } return values; } /** * Takes the values of this array and puts them in a java array */ public Value[] keysToArray() { Value[] values = new Value[getSize()]; int i = 0; for (Entry ptr = getHead(); ptr != null; ptr = ptr.getNext()) { values[i++] = ptr.getKey(); } return values; } /** * Takes the values of this array and puts them in a java array */ public Value[] valuesToArray() { Value[] values = new Value[getSize()]; int i = 0; for (Entry ptr = getHead(); ptr != null; ptr = ptr.getNext()) { values[i++] = ptr.getValue(); } return values; } /** * Returns the keys. */ public Value getKeys() { return new ArrayValueImpl(keysToArray()); } /** * Returns the keys. */ public Value getValues() { return new ArrayValueImpl(valuesToArray()); } /** * Takes the values of this array, unmarshals them to objects of type * <i>elementType</i>, and puts them in a java array. */ @Override public Object valuesToArray(Env env, Class elementType) { int size = getSize(); Object array = Array.newInstance(elementType, size); MarshalFactory factory = env.getModuleContext().getMarshalFactory(); Marshal elementMarshal = factory.create(elementType); int i = 0; for (Entry ptr = getHead(); ptr != null; ptr = ptr.getNext()) { Array.set(array, i++, elementMarshal.marshal(env, ptr.getValue(), elementType)); } return array; } public class EntrySet extends AbstractSet<Map.Entry<Value,Value>> { EntrySet() { } @Override public int size() { return ArrayValue.this.getSize(); } @Override public Iterator<Map.Entry<Value,Value>> iterator() { return new EntryIterator(getHead()); } } public class KeySet extends AbstractSet<Value> { KeySet() { } @Override public int size() { return ArrayValue.this.getSize(); } @Override public Iterator<Value> iterator() { return new KeyIterator(getHead()); } } public class ValueCollection extends AbstractCollection<Value> { ValueCollection() { } @Override public int size() { return ArrayValue.this.getSize(); } @Override public Iterator<Value> iterator() { return new ValueIterator(getHead()); } } public static class EntryIterator implements Iterator<Map.Entry<Value,Value>> { private Entry _current; EntryIterator(Entry head) { _current = head; } public boolean hasNext() { return _current != null; } public Map.Entry<Value,Value> next() { if (_current != null) { Map.Entry<Value,Value> next = _current; _current = _current._next; return next; } else return null; } public void remove() { throw new UnsupportedOperationException(); } } public static class KeyIterator implements Iterator<Value> { private Entry _current; KeyIterator(Entry head) { _current = head; } public boolean hasNext() { return _current != null; } public Value next() { if (_current != null) { Value next = _current.getKey(); _current = _current._next; return next; } else return null; } public void remove() { throw new UnsupportedOperationException(); } } public static class ValueIterator implements Iterator<Value> { private Entry _current; ValueIterator(Entry head) { _current = head; } public boolean hasNext() { return _current != null; } public Value next() { if (_current != null) { Value next = _current.getValue(); _current = _current._next; return next; } else return null; } public void remove() { throw new UnsupportedOperationException(); } } public static class ValueComparator implements Comparator<Map.Entry<Value,Value>> { public static final ValueComparator CMP = new ValueComparator(); private ValueComparator() { } public int compare(Map.Entry<Value,Value> aEntry, Map.Entry<Value,Value> bEntry) { try { Value aValue = aEntry.getValue(); Value bValue = bEntry.getValue(); if (aValue.eq(bValue)) return 0; else if (aValue.lt(bValue)) return -1; else return 1; } catch (Throwable e) { throw new RuntimeException(e); } } } public static class KeyComparator implements Comparator<Map.Entry<Value,Value>> { public static final KeyComparator CMP = new KeyComparator(); private KeyComparator() { } public int compare(Map.Entry<Value,Value> aEntry, Map.Entry<Value,Value> bEntry) { try { Value aKey = aEntry.getKey(); Value bKey = bEntry.getKey(); if (aKey.eq(bKey)) return 0; else if (aKey.lt(bKey)) return -1; else return 1; } catch (Throwable e) { throw new RuntimeException(e); } } } public static abstract class AbstractGet { public abstract Value get(Map.Entry<Value, Value> entry); } public static class GetKey extends AbstractGet { public static final GetKey GET = new GetKey(); private GetKey() { } @Override public Value get(Map.Entry<Value, Value> entry) { return entry.getKey(); } } public static class GetValue extends AbstractGet { public static final GetValue GET = new GetValue(); private GetValue() { } @Override public Value get(Map.Entry<Value, Value> entry) { return entry.getValue(); } } }