/* * 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 SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.es; import com.caucho.util.CharBuffer; import com.caucho.util.IntMap; import java.util.HashMap; import java.util.Iterator; /** * Implementation class for a JavaScript Object. */ public class ESObject extends ESBase { static ESId TO_STRING = ESId.intern("toString"); static ESId VALUE_OF = ESId.intern("valueOf"); static ESId CALL = ESId.intern("call"); static ESId CONSTRUCT = ESId.intern("construct"); static int DIRTY = 0; static int CLEAN = DIRTY + 1; static int COW = CLEAN + 1; int copyState = DIRTY; ESString []propNames; ESBase []propValues; ESBase []propWatch; int []propFlags; int size; int fill; int mask; int mark; // for printing protected boolean snapPrototype; protected ESObject() { } /** * Simple constructor for parentless objects. */ public ESObject(String className, ESBase proto) { init(className, proto, 16); } protected ESObject(String className, ESBase proto, int hashSize) { init(className, proto, hashSize < 16 ? 16 : hashSize); } private void init(String className, ESBase proto, int hashSize) { if (proto == null) { Global resin = Global.getGlobalProto(); proto = resin == null ? null : resin.objProto; } if (className == null && proto != null) className = proto.className; prototype = proto; propNames = new ESString[hashSize]; propValues = new ESBase[hashSize]; propFlags = new int[hashSize]; mask = propNames.length - 1; size = 0; fill = 0; copyState = DIRTY; this.className = className == null ? "Object" : className; } void init(String className, ESBase proto) { init(className, proto, 16); } void setClean() { copyState = CLEAN; } /** * Expands the property table */ private void resize(int newSize) { ESString []newNames = new ESString[newSize]; ESBase []newValues = new ESBase[newSize]; int []newFlags = new int[newSize]; ESBase []newWatch = null; if (propWatch != null) newWatch = new ESBase[newSize]; mask = newNames.length - 1; for (int i = 0; i < propNames.length; i++) { if (propValues[i] == null && (propFlags[i] & WATCH) == 0) continue; int hash = propNames[i].hashCode() & mask; while (true) { if (newNames[hash] == null) { newNames[hash] = propNames[i]; newValues[hash] = propValues[i]; newFlags[hash] = propFlags[i]; if (newWatch != null) newWatch[hash] = propWatch[i]; break; } hash = (hash + 1) & mask; } } propNames = newNames; propValues = newValues; propFlags = newFlags; propWatch = newWatch; fill = size; } private void refill() { for (int i = 0; i < propNames.length; i++) { if (propValues[i] == null && (propFlags[i] & WATCH) == 0) { propNames[i] = null; continue; } int hash = propNames[i].hashCode() & mask; while (true) { if (propValues[hash] == null && (propFlags[hash] & WATCH) == 0) { propNames[hash] = propNames[i]; propValues[hash] = propValues[i]; propFlags[hash] = propFlags[i]; propNames[i] = null; propValues[i] = null; propFlags[i] = 0; break; } hash = (hash + 1) & mask; } } fill = size; } /** * Gets a property value. */ public ESBase getProperty(ESString name) throws Throwable { int hash = name.hashCode() & mask; while (true) { ESString propName = propNames[hash]; if (propName == name || name.equals(propName)) { ESBase value = propValues[hash]; return value == null ? prototype.getProperty(name) : value; } else if (propName == null) { ESBase value = prototype.getProperty(name); if (snapPrototype) setProperty(name, value); return value; } hash = (hash + 1) & mask; } } protected boolean canPut(ESString name) { int hash = name.hashCode() & mask; while (true) { ESString propName = propNames[hash]; if (name.equals(propName) && propValues[hash] != null) return (propFlags[hash] & READ_ONLY) == 0; else if (propName == null) { if (prototype instanceof ESObject) return ((ESObject) prototype).canPut(name); else return true; } hash = (hash + 1) & mask; } } /* public ESBase callWatch(ESString name, int hash, ESBase value) throws Exception { Global resin = Global.getGlobalProto(); Call call = resin.getCall(); call.top = 1; if (propWatch[hash] instanceof ESClosure) call.setArg(-1, ((ESClosure) propWatch[hash]).scope[0]); else call.setArg(-1, null); call.setArg(0, name); call.setArg(1, propValues[hash]); call.setArg(2, value); value = propWatch[hash].call(call, 3); resin.freeCall(call); return value; } */ /** * Puts a new value in the property table with the appropriate flags */ public void setProperty(ESString name, ESBase value) throws Throwable { if (copyState != DIRTY) { if (copyState == COW) copyAll(); copyState = DIRTY; } if (value == esEmpty) value = esUndefined; int hash = name.hashCode() & mask; while (true) { ESString propName = propNames[hash]; if (propValues[hash] == null) { if (! prototype.canPut(name)) return; if (propName == null) fill++; propNames[hash] = name; /* if ((propFlags[hash] & WATCH) != 0) value = callWatch(name, hash, value); */ propValues[hash] = value; propFlags[hash] = 0; size++; if (propNames.length <= 4 * size) { resize(4 * propNames.length); } else if (propNames.length <= 2 * fill) refill(); return; } else if (propName != name && ! propName.equals(name)) { hash = (hash + 1) & mask; continue; } else if ((propFlags[hash] & READ_ONLY) != 0) return; else { /* if ((propFlags[hash] & WATCH) != 0) value = callWatch(name, hash, value); */ propValues[hash] = value; return; } } } public void put(ESString name, ESBase value, int flags) { int hash = name.hashCode() & mask; while (true) { ESString propName = propNames[hash]; if (propName == null || propValues[hash] == null || propName.equals(name)) { if (propName == null) fill++; if (propValues[hash] == null) size++; propNames[hash] = name; propValues[hash] = value; propFlags[hash] = flags; if (propNames.length <= 4 * size) { resize(4 * propNames.length); } else if (propNames.length <= 2 * fill) refill(); return; } hash = (hash + 1) & mask; } } public void put(String name, ESBase value, int flags) { ESId id = ESId.intern(name); put(id, value, flags); } /** * Deletes the entry. Returns true if successful. */ public ESBase delete(ESString name) throws Throwable { if (copyState != DIRTY) { if (copyState == COW) copyAll(); copyState = DIRTY; } int hash = name.hashCode() & mask; while (true) { ESString hashName = propNames[hash]; if (hashName == null) return ESBoolean.FALSE; else if (propValues[hash] != null && hashName.equals(name)) { if ((propFlags[hash] & DONT_DELETE) != 0) return ESBoolean.FALSE; else { propValues[hash] = null; size--; return ESBoolean.TRUE; } } hash = (hash + 1) & mask; } } public void watch(ESString name, ESBase fun) { if (copyState != DIRTY) { if (copyState == COW) copyAll(); copyState = DIRTY; } int hash = name.hashCode() & mask; while (true) { ESString propName = propNames[hash]; if (propValues[hash] == null) { if (! prototype.canPut(name)) return; if (propName == null) fill++; propNames[hash] = name; propValues[hash] = esEmpty; propFlags[hash] = WATCH; if (propWatch == null) propWatch = new ESBase[propFlags.length]; propWatch[hash] = fun; size++; if (propNames.length <= 4 * size) resize(4 * propNames.length); else if (propNames.length <= 2 * fill) refill(); return; } else if (propName != name && ! propName.equals(name)) { hash = (hash + 1) & mask; continue; } else if ((propFlags[hash] & READ_ONLY) != 0) return; else { propFlags[hash] |= WATCH; if (propWatch == null) propWatch = new ESBase[propFlags.length]; propWatch[hash] = fun; return; } } } public void unwatch(ESString name) { if (copyState != DIRTY) { if (copyState == COW) copyAll(); copyState = DIRTY; } int hash = name.hashCode() & mask; while (true) { ESString propName = propNames[hash]; if (propName == null) return; else if (propName.equals(name)) { propFlags[hash] &= ~WATCH; return; } } } /** * Sets the named property */ public void put(int i, ESBase value, int flags) { put(ESString.create(i), value, flags); } public Iterator keys() throws ESException { return new PropertyEnumeration(this); } public ESBase typeof() throws ESException { return ESString.create("object"); } /** * XXX: not right */ public ESBase toPrimitive(int hint) throws Throwable { Global resin = Global.getGlobalProto(); Call eval = resin.getCall(); eval.global = resin.getGlobal(); try { ESBase fun = hasProperty(hint == STRING ? TO_STRING : VALUE_OF); if (fun instanceof ESClosure || fun instanceof Native) { eval.stack[0] = this; eval.top = 1; ESBase value = fun.call(eval, 0); if (value instanceof ESBase && ! (value instanceof ESObject)) return value; } fun = hasProperty(hint == STRING ? VALUE_OF : TO_STRING); if (fun instanceof ESClosure || fun instanceof Native) { eval.stack[0] = this; eval.top = 1; ESBase value = fun.call(eval, 0); if (value instanceof ESBase && ! (value instanceof ESObject)) return value; } throw new ESException("cannot convert object to primitive type"); } finally { resin.freeCall(eval); } } public ESObject toObject() { return this; } public Object toJavaObject() throws ESException { return this; } /** * Returns a string rep of the object */ public double toNum() throws Throwable { ESBase value = toPrimitive(NUMBER); if (value instanceof ESObject) throw new ESException("toPrimitive must return primitive"); return value.toNum(); } /** * Returns a string rep of the object */ public ESString toStr() throws Throwable { ESBase prim = toPrimitive(STRING); if (prim instanceof ESObject) throw new ESException("toPrimitive must return primitive"); return prim.toStr(); } public ESString toSource(IntMap map, boolean isLoopPass) throws Throwable { CharBuffer cb = new CharBuffer(); Global resin = Global.getGlobalProto(); int mark = map.get(this); if (mark > 0 && isLoopPass) return null; else if (mark > 0) { cb.append("#" + mark + "="); map.put(this, -mark); } else if (mark == 0 && isLoopPass) { map.put(this, resin.addMark()); return null; } else if (mark < 0 && ! isLoopPass) { return ESString.create("#" + -mark + "#"); } cb.append("{"); if (isLoopPass) map.put(this, 0); Iterator e = keys(); boolean isFirst = true; while (e.hasNext()) { if (! isFirst) cb.append(", "); isFirst = false; ESString key = (ESString) e.next(); cb.append(key); cb.append(":"); ESBase value = getProperty(key); if (isLoopPass) value.toSource(map, isLoopPass); else cb.append(value.toSource(map, isLoopPass)); } cb.append("}"); return new ESString(cb.toString()); } public boolean toBoolean() { return true; } ESObject dup() { return new ESObject(); } public Object copy(HashMap refs) { Object ref = refs.get(this); if (ref != null) return ref; ESObject copy = dup(); refs.put(this, copy); copy(refs, copy); return copy; } private void copyAll() { copyState = DIRTY; int len = propValues.length; ESString []newPropNames = new ESString[len]; int []newPropFlags = new int[len]; ESBase []newPropValues = new ESBase[len]; System.arraycopy(propNames, 0, newPropNames, 0, len); System.arraycopy(propFlags, 0, newPropFlags, 0, len); System.arraycopy(propValues, 0, newPropValues, 0, len); propNames = newPropNames; propFlags = newPropFlags; propValues = newPropValues; if (propWatch != null) { ESBase []newPropWatch = new ESBase[len]; System.arraycopy(propWatch, 0, newPropWatch, 0, len); propWatch = newPropWatch; } } ESObject resinCopy() { ESObject obj = dup(); copy(obj); return obj; } protected void copy(Object newObj) { ESObject obj = (ESObject) newObj; obj.prototype = prototype; obj.className = className; obj.propNames = propNames; obj.propValues = propValues; obj.propFlags = propFlags; obj.propWatch = propWatch; obj.size = size; obj.fill = fill; obj.mask = mask; obj.copyState = copyState; if (obj.copyState == DIRTY) { throw new RuntimeException(); } else if (copyState == CLEAN) { copyState = COW; obj.copyState = COW; } } protected void copy(HashMap refs, Object newObj) { ESObject obj = (ESObject) newObj; obj.prototype = (ESBase) prototype.copy(refs); obj.className = className; obj.propNames = propNames; obj.propValues = propValues; obj.propFlags = propFlags; obj.propWatch = propWatch; obj.size = size; obj.fill = fill; obj.mask = mask; obj.copyState = copyState; if (obj.copyState == DIRTY) { obj.copyAll(); } else if (copyState == CLEAN) { copyState = COW; obj.copyState = COW; } } ESObject shallowCopy() { ESObject obj = dup(); shallowCopy(obj); return obj; } protected void shallowCopy(Object newObj) { ESObject obj = (ESObject) newObj; obj.prototype = prototype; obj.className = className; int len = propValues.length; if (propWatch != null) { obj.propWatch = new ESBase[len]; System.arraycopy(propWatch, 0, obj.propWatch, 0, len); } obj.propNames = new ESString[len]; obj.propFlags = new int[len]; obj.propValues = new ESBase[len]; ESString []newNames = obj.propNames; ESString []oldNames = propNames; ESBase []newValues = obj.propValues; ESBase []oldValues = propValues; int []newFlags = obj.propFlags; int []oldFlags = propFlags; for (int i = 0; i < len; i++) { newNames[i] = oldNames[i]; newValues[i] = oldValues[i]; newFlags[i] = oldFlags[i]; } obj.size = size; obj.mask = mask; obj.fill = fill; obj.copyState = DIRTY; } public boolean ecmaEquals(ESBase b) throws Throwable { if (b instanceof ESObject || b instanceof ESThunk) return this == b; else return toPrimitive(NONE).ecmaEquals(b); } public ESBase call(Call call, int length) throws Throwable { ESBase callFun = hasProperty(CALL); if (callFun != null) { call.setThis(this); return callFun.call(call, length); } throw new ESNullException(toStr() + " is not a function"); } public ESBase construct(Call call, int length) throws Throwable { ESBase callFun = hasProperty(CONSTRUCT); if (callFun != null) { call.setThis(this); return callFun.construct(call, length); } throw new ESNullException(toStr() + " is not a constructor"); } }