001: /*
002: * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.es;
030:
031: import com.caucho.util.CharBuffer;
032: import com.caucho.util.IntMap;
033:
034: import java.util.HashMap;
035: import java.util.Iterator;
036:
037: /**
038: * Implementation class for a JavaScript Object.
039: */
040: public class ESObject extends ESBase {
041: static ESId TO_STRING = ESId.intern("toString");
042: static ESId VALUE_OF = ESId.intern("valueOf");
043: static ESId CALL = ESId.intern("call");
044: static ESId CONSTRUCT = ESId.intern("construct");
045: static int DIRTY = 0;
046: static int CLEAN = DIRTY + 1;
047: static int COW = CLEAN + 1;
048:
049: int copyState = DIRTY;
050:
051: ESString[] propNames;
052: ESBase[] propValues;
053: ESBase[] propWatch;
054: int[] propFlags;
055: int size;
056: int fill;
057: int mask;
058:
059: int mark; // for printing
060: protected boolean snapPrototype;
061:
062: protected ESObject() {
063: }
064:
065: /**
066: * Simple constructor for parentless objects.
067: */
068: public ESObject(String className, ESBase proto) {
069: init(className, proto, 16);
070: }
071:
072: protected ESObject(String className, ESBase proto, int hashSize) {
073: init(className, proto, hashSize < 16 ? 16 : hashSize);
074: }
075:
076: private void init(String className, ESBase proto, int hashSize) {
077: if (proto == null) {
078: Global resin = Global.getGlobalProto();
079: proto = resin == null ? null : resin.objProto;
080: }
081: if (className == null && proto != null)
082: className = proto.className;
083: prototype = proto;
084:
085: propNames = new ESString[hashSize];
086: propValues = new ESBase[hashSize];
087: propFlags = new int[hashSize];
088: mask = propNames.length - 1;
089: size = 0;
090: fill = 0;
091: copyState = DIRTY;
092:
093: this .className = className == null ? "Object" : className;
094: }
095:
096: void init(String className, ESBase proto) {
097: init(className, proto, 16);
098: }
099:
100: void setClean() {
101: copyState = CLEAN;
102: }
103:
104: /**
105: * Expands the property table
106: */
107: private void resize(int newSize) {
108: ESString[] newNames = new ESString[newSize];
109: ESBase[] newValues = new ESBase[newSize];
110: int[] newFlags = new int[newSize];
111: ESBase[] newWatch = null;
112: if (propWatch != null)
113: newWatch = new ESBase[newSize];
114:
115: mask = newNames.length - 1;
116:
117: for (int i = 0; i < propNames.length; i++) {
118: if (propValues[i] == null && (propFlags[i] & WATCH) == 0)
119: continue;
120:
121: int hash = propNames[i].hashCode() & mask;
122:
123: while (true) {
124: if (newNames[hash] == null) {
125: newNames[hash] = propNames[i];
126: newValues[hash] = propValues[i];
127: newFlags[hash] = propFlags[i];
128: if (newWatch != null)
129: newWatch[hash] = propWatch[i];
130: break;
131: }
132: hash = (hash + 1) & mask;
133: }
134: }
135:
136: propNames = newNames;
137: propValues = newValues;
138: propFlags = newFlags;
139: propWatch = newWatch;
140: fill = size;
141: }
142:
143: private void refill() {
144: for (int i = 0; i < propNames.length; i++) {
145: if (propValues[i] == null && (propFlags[i] & WATCH) == 0) {
146: propNames[i] = null;
147: continue;
148: }
149:
150: int hash = propNames[i].hashCode() & mask;
151:
152: while (true) {
153: if (propValues[hash] == null
154: && (propFlags[hash] & WATCH) == 0) {
155: propNames[hash] = propNames[i];
156: propValues[hash] = propValues[i];
157: propFlags[hash] = propFlags[i];
158: propNames[i] = null;
159: propValues[i] = null;
160: propFlags[i] = 0;
161: break;
162: }
163: hash = (hash + 1) & mask;
164: }
165: }
166:
167: fill = size;
168: }
169:
170: /**
171: * Gets a property value.
172: */
173: public ESBase getProperty(ESString name) throws Throwable {
174: int hash = name.hashCode() & mask;
175:
176: while (true) {
177: ESString propName = propNames[hash];
178:
179: if (propName == name || name.equals(propName)) {
180: ESBase value = propValues[hash];
181: return value == null ? prototype.getProperty(name)
182: : value;
183: } else if (propName == null) {
184: ESBase value = prototype.getProperty(name);
185: if (snapPrototype)
186: setProperty(name, value);
187: return value;
188: }
189:
190: hash = (hash + 1) & mask;
191: }
192: }
193:
194: protected boolean canPut(ESString name) {
195: int hash = name.hashCode() & mask;
196:
197: while (true) {
198: ESString propName = propNames[hash];
199:
200: if (name.equals(propName) && propValues[hash] != null)
201: return (propFlags[hash] & READ_ONLY) == 0;
202: else if (propName == null) {
203: if (prototype instanceof ESObject)
204: return ((ESObject) prototype).canPut(name);
205: else
206: return true;
207: }
208:
209: hash = (hash + 1) & mask;
210: }
211: }
212:
213: /*
214: public ESBase callWatch(ESString name, int hash, ESBase value)
215: throws Exception
216: {
217: Global resin = Global.getGlobalProto();
218: Call call = resin.getCall();
219: call.top = 1;
220:
221: if (propWatch[hash] instanceof ESClosure)
222: call.setArg(-1, ((ESClosure) propWatch[hash]).scope[0]);
223: else
224: call.setArg(-1, null);
225: call.setArg(0, name);
226: call.setArg(1, propValues[hash]);
227: call.setArg(2, value);
228: value = propWatch[hash].call(call, 3);
229: resin.freeCall(call);
230:
231: return value;
232: }
233: */
234:
235: /**
236: * Puts a new value in the property table with the appropriate flags
237: */
238: public void setProperty(ESString name, ESBase value)
239: throws Throwable {
240: if (copyState != DIRTY) {
241: if (copyState == COW)
242: copyAll();
243: copyState = DIRTY;
244: }
245:
246: if (value == esEmpty)
247: value = esUndefined;
248:
249: int hash = name.hashCode() & mask;
250:
251: while (true) {
252: ESString propName = propNames[hash];
253:
254: if (propValues[hash] == null) {
255: if (!prototype.canPut(name))
256: return;
257:
258: if (propName == null)
259: fill++;
260:
261: propNames[hash] = name;
262: /*
263: if ((propFlags[hash] & WATCH) != 0)
264: value = callWatch(name, hash, value);
265: */
266: propValues[hash] = value;
267: propFlags[hash] = 0;
268:
269: size++;
270:
271: if (propNames.length <= 4 * size) {
272: resize(4 * propNames.length);
273: } else if (propNames.length <= 2 * fill)
274: refill();
275:
276: return;
277: } else if (propName != name && !propName.equals(name)) {
278: hash = (hash + 1) & mask;
279: continue;
280: } else if ((propFlags[hash] & READ_ONLY) != 0)
281: return;
282: else {
283: /*
284: if ((propFlags[hash] & WATCH) != 0)
285: value = callWatch(name, hash, value);
286: */
287: propValues[hash] = value;
288: return;
289: }
290: }
291: }
292:
293: public void put(ESString name, ESBase value, int flags) {
294: int hash = name.hashCode() & mask;
295:
296: while (true) {
297: ESString propName = propNames[hash];
298:
299: if (propName == null || propValues[hash] == null
300: || propName.equals(name)) {
301: if (propName == null)
302: fill++;
303: if (propValues[hash] == null)
304: size++;
305:
306: propNames[hash] = name;
307: propValues[hash] = value;
308: propFlags[hash] = flags;
309:
310: if (propNames.length <= 4 * size) {
311: resize(4 * propNames.length);
312: } else if (propNames.length <= 2 * fill)
313: refill();
314:
315: return;
316: }
317:
318: hash = (hash + 1) & mask;
319: }
320: }
321:
322: public void put(String name, ESBase value, int flags) {
323: ESId id = ESId.intern(name);
324:
325: put(id, value, flags);
326: }
327:
328: /**
329: * Deletes the entry. Returns true if successful.
330: */
331: public ESBase delete(ESString name) throws Throwable {
332: if (copyState != DIRTY) {
333: if (copyState == COW)
334: copyAll();
335: copyState = DIRTY;
336: }
337:
338: int hash = name.hashCode() & mask;
339:
340: while (true) {
341: ESString hashName = propNames[hash];
342:
343: if (hashName == null)
344: return ESBoolean.FALSE;
345: else if (propValues[hash] != null && hashName.equals(name)) {
346: if ((propFlags[hash] & DONT_DELETE) != 0)
347: return ESBoolean.FALSE;
348: else {
349: propValues[hash] = null;
350: size--;
351: return ESBoolean.TRUE;
352: }
353: }
354:
355: hash = (hash + 1) & mask;
356: }
357: }
358:
359: public void watch(ESString name, ESBase fun) {
360: if (copyState != DIRTY) {
361: if (copyState == COW)
362: copyAll();
363: copyState = DIRTY;
364: }
365:
366: int hash = name.hashCode() & mask;
367:
368: while (true) {
369: ESString propName = propNames[hash];
370:
371: if (propValues[hash] == null) {
372: if (!prototype.canPut(name))
373: return;
374:
375: if (propName == null)
376: fill++;
377:
378: propNames[hash] = name;
379: propValues[hash] = esEmpty;
380: propFlags[hash] = WATCH;
381: if (propWatch == null)
382: propWatch = new ESBase[propFlags.length];
383: propWatch[hash] = fun;
384:
385: size++;
386:
387: if (propNames.length <= 4 * size)
388: resize(4 * propNames.length);
389: else if (propNames.length <= 2 * fill)
390: refill();
391:
392: return;
393: } else if (propName != name && !propName.equals(name)) {
394: hash = (hash + 1) & mask;
395: continue;
396: } else if ((propFlags[hash] & READ_ONLY) != 0)
397: return;
398: else {
399: propFlags[hash] |= WATCH;
400: if (propWatch == null)
401: propWatch = new ESBase[propFlags.length];
402:
403: propWatch[hash] = fun;
404: return;
405: }
406: }
407: }
408:
409: public void unwatch(ESString name) {
410: if (copyState != DIRTY) {
411: if (copyState == COW)
412: copyAll();
413: copyState = DIRTY;
414: }
415:
416: int hash = name.hashCode() & mask;
417:
418: while (true) {
419: ESString propName = propNames[hash];
420:
421: if (propName == null)
422: return;
423: else if (propName.equals(name)) {
424: propFlags[hash] &= ~WATCH;
425:
426: return;
427: }
428: }
429: }
430:
431: /**
432: * Sets the named property
433: */
434: public void put(int i, ESBase value, int flags) {
435: put(ESString.create(i), value, flags);
436: }
437:
438: public Iterator keys() throws ESException {
439: return new PropertyEnumeration(this );
440: }
441:
442: public ESBase typeof() throws ESException {
443: return ESString.create("object");
444: }
445:
446: /**
447: * XXX: not right
448: */
449: public ESBase toPrimitive(int hint) throws Throwable {
450: Global resin = Global.getGlobalProto();
451: Call eval = resin.getCall();
452: eval.global = resin.getGlobal();
453:
454: try {
455: ESBase fun = hasProperty(hint == STRING ? TO_STRING
456: : VALUE_OF);
457:
458: if (fun instanceof ESClosure || fun instanceof Native) {
459: eval.stack[0] = this ;
460: eval.top = 1;
461: ESBase value = fun.call(eval, 0);
462:
463: if (value instanceof ESBase
464: && !(value instanceof ESObject))
465: return value;
466: }
467:
468: fun = hasProperty(hint == STRING ? VALUE_OF : TO_STRING);
469:
470: if (fun instanceof ESClosure || fun instanceof Native) {
471: eval.stack[0] = this ;
472: eval.top = 1;
473: ESBase value = fun.call(eval, 0);
474:
475: if (value instanceof ESBase
476: && !(value instanceof ESObject))
477: return value;
478: }
479:
480: throw new ESException(
481: "cannot convert object to primitive type");
482: } finally {
483: resin.freeCall(eval);
484: }
485: }
486:
487: public ESObject toObject() {
488: return this ;
489: }
490:
491: public Object toJavaObject() throws ESException {
492: return this ;
493: }
494:
495: /**
496: * Returns a string rep of the object
497: */
498: public double toNum() throws Throwable {
499: ESBase value = toPrimitive(NUMBER);
500:
501: if (value instanceof ESObject)
502: throw new ESException("toPrimitive must return primitive");
503:
504: return value.toNum();
505: }
506:
507: /**
508: * Returns a string rep of the object
509: */
510: public ESString toStr() throws Throwable {
511: ESBase prim = toPrimitive(STRING);
512:
513: if (prim instanceof ESObject)
514: throw new ESException("toPrimitive must return primitive");
515:
516: return prim.toStr();
517: }
518:
519: public ESString toSource(IntMap map, boolean isLoopPass)
520: throws Throwable {
521: CharBuffer cb = new CharBuffer();
522: Global resin = Global.getGlobalProto();
523:
524: int mark = map.get(this );
525:
526: if (mark > 0 && isLoopPass)
527: return null;
528: else if (mark > 0) {
529: cb.append("#" + mark + "=");
530: map.put(this , -mark);
531: } else if (mark == 0 && isLoopPass) {
532: map.put(this , resin.addMark());
533: return null;
534: } else if (mark < 0 && !isLoopPass) {
535: return ESString.create("#" + -mark + "#");
536: }
537:
538: cb.append("{");
539:
540: if (isLoopPass)
541: map.put(this , 0);
542:
543: Iterator e = keys();
544:
545: boolean isFirst = true;
546: while (e.hasNext()) {
547: if (!isFirst)
548: cb.append(", ");
549: isFirst = false;
550:
551: ESString key = (ESString) e.next();
552:
553: cb.append(key);
554: cb.append(":");
555: ESBase value = getProperty(key);
556: if (isLoopPass)
557: value.toSource(map, isLoopPass);
558: else
559: cb.append(value.toSource(map, isLoopPass));
560: }
561:
562: cb.append("}");
563:
564: return new ESString(cb.toString());
565: }
566:
567: public boolean toBoolean() {
568: return true;
569: }
570:
571: ESObject dup() {
572: return new ESObject();
573: }
574:
575: public Object copy(HashMap refs) {
576: Object ref = refs.get(this );
577: if (ref != null)
578: return ref;
579:
580: ESObject copy = dup();
581: refs.put(this , copy);
582:
583: copy(refs, copy);
584:
585: return copy;
586: }
587:
588: private void copyAll() {
589: copyState = DIRTY;
590:
591: int len = propValues.length;
592:
593: ESString[] newPropNames = new ESString[len];
594: int[] newPropFlags = new int[len];
595: ESBase[] newPropValues = new ESBase[len];
596:
597: System.arraycopy(propNames, 0, newPropNames, 0, len);
598: System.arraycopy(propFlags, 0, newPropFlags, 0, len);
599: System.arraycopy(propValues, 0, newPropValues, 0, len);
600:
601: propNames = newPropNames;
602: propFlags = newPropFlags;
603: propValues = newPropValues;
604:
605: if (propWatch != null) {
606: ESBase[] newPropWatch = new ESBase[len];
607: System.arraycopy(propWatch, 0, newPropWatch, 0, len);
608: propWatch = newPropWatch;
609: }
610: }
611:
612: ESObject resinCopy() {
613: ESObject obj = dup();
614:
615: copy(obj);
616:
617: return obj;
618: }
619:
620: protected void copy(Object newObj) {
621: ESObject obj = (ESObject) newObj;
622:
623: obj.prototype = prototype;
624: obj.className = className;
625:
626: obj.propNames = propNames;
627: obj.propValues = propValues;
628: obj.propFlags = propFlags;
629: obj.propWatch = propWatch;
630: obj.size = size;
631: obj.fill = fill;
632: obj.mask = mask;
633: obj.copyState = copyState;
634:
635: if (obj.copyState == DIRTY) {
636: throw new RuntimeException();
637: } else if (copyState == CLEAN) {
638: copyState = COW;
639: obj.copyState = COW;
640: }
641: }
642:
643: protected void copy(HashMap refs, Object newObj) {
644: ESObject obj = (ESObject) newObj;
645:
646: obj.prototype = (ESBase) prototype.copy(refs);
647: obj.className = className;
648:
649: obj.propNames = propNames;
650: obj.propValues = propValues;
651: obj.propFlags = propFlags;
652: obj.propWatch = propWatch;
653: obj.size = size;
654: obj.fill = fill;
655: obj.mask = mask;
656: obj.copyState = copyState;
657:
658: if (obj.copyState == DIRTY) {
659: obj.copyAll();
660: } else if (copyState == CLEAN) {
661: copyState = COW;
662: obj.copyState = COW;
663: }
664: }
665:
666: ESObject shallowCopy() {
667: ESObject obj = dup();
668:
669: shallowCopy(obj);
670:
671: return obj;
672: }
673:
674: protected void shallowCopy(Object newObj) {
675: ESObject obj = (ESObject) newObj;
676:
677: obj.prototype = prototype;
678: obj.className = className;
679:
680: int len = propValues.length;
681:
682: if (propWatch != null) {
683: obj.propWatch = new ESBase[len];
684: System.arraycopy(propWatch, 0, obj.propWatch, 0, len);
685: }
686:
687: obj.propNames = new ESString[len];
688: obj.propFlags = new int[len];
689: obj.propValues = new ESBase[len];
690:
691: ESString[] newNames = obj.propNames;
692: ESString[] oldNames = propNames;
693: ESBase[] newValues = obj.propValues;
694: ESBase[] oldValues = propValues;
695: int[] newFlags = obj.propFlags;
696: int[] oldFlags = propFlags;
697: for (int i = 0; i < len; i++) {
698: newNames[i] = oldNames[i];
699: newValues[i] = oldValues[i];
700: newFlags[i] = oldFlags[i];
701: }
702:
703: obj.size = size;
704: obj.mask = mask;
705: obj.fill = fill;
706: obj.copyState = DIRTY;
707: }
708:
709: public boolean ecmaEquals(ESBase b) throws Throwable {
710: if (b instanceof ESObject || b instanceof ESThunk)
711: return this == b;
712: else
713: return toPrimitive(NONE).ecmaEquals(b);
714: }
715:
716: public ESBase call(Call call, int length) throws Throwable {
717: ESBase callFun = hasProperty(CALL);
718:
719: if (callFun != null) {
720: call.setThis(this );
721: return callFun.call(call, length);
722: }
723:
724: throw new ESNullException(toStr() + " is not a function");
725: }
726:
727: public ESBase construct(Call call, int length) throws Throwable {
728: ESBase callFun = hasProperty(CONSTRUCT);
729:
730: if (callFun != null) {
731: call.setThis(this );
732: return callFun.construct(call, length);
733: }
734:
735: throw new ESNullException(toStr() + " is not a constructor");
736: }
737: }
|