001: /* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Igor Bukanov
026: *
027: * Alternatively, the contents of this file may be used under the terms of
028: * the GNU General Public License Version 2 or later (the "GPL"), in which
029: * case the provisions of the GPL are applicable instead of those above. If
030: * you wish to allow use of your version of this file only under the terms of
031: * the GPL and not to allow others to use your version of this file under the
032: * MPL, indicate your decision by deleting the provisions above and replacing
033: * them with the notice and other provisions required by the GPL. If you do
034: * not delete the provisions above, a recipient may use your version of this
035: * file under either the MPL or the GPL.
036: *
037: * ***** END LICENSE BLOCK ***** */
038:
039: package org.mozilla.javascript;
040:
041: import java.io.*;
042:
043: /**
044: Base class for native object implementation that uses IdFunctionObject to export its methods to script via <class-name>.prototype object.
045:
046: Any descendant should implement at least the following methods:
047: findInstanceIdInfo
048: getInstanceIdName
049: execIdCall
050: methodArity
051:
052: To define non-function properties, the descendant should override
053: getInstanceIdValue
054: setInstanceIdValue
055: to get/set property value and provide its default attributes.
056:
057:
058: To customize initializition of constructor and protype objects, descendant
059: may override scopeInit or fillConstructorProperties methods.
060:
061: */
062: public abstract class IdScriptableObject extends ScriptableObject
063: implements IdFunctionCall {
064: private transient volatile PrototypeValues prototypeValues;
065:
066: private static final class PrototypeValues implements Serializable {
067: static final long serialVersionUID = 3038645279153854371L;
068:
069: private static final int VALUE_SLOT = 0;
070: private static final int NAME_SLOT = 1;
071: private static final int SLOT_SPAN = 2;
072:
073: private IdScriptableObject obj;
074: private int maxId;
075: private volatile Object[] valueArray;
076: private volatile short[] attributeArray;
077: private volatile int lastFoundId = 1;
078:
079: // The following helps to avoid creation of valueArray during runtime
080: // initialization for common case of "constructor" property
081: int constructorId;
082: private IdFunctionObject constructor;
083: private short constructorAttrs;
084:
085: PrototypeValues(IdScriptableObject obj, int maxId) {
086: if (obj == null)
087: throw new IllegalArgumentException();
088: if (maxId < 1)
089: throw new IllegalArgumentException();
090: this .obj = obj;
091: this .maxId = maxId;
092: }
093:
094: final int getMaxId() {
095: return maxId;
096: }
097:
098: final void initValue(int id, String name, Object value,
099: int attributes) {
100: if (!(1 <= id && id <= maxId))
101: throw new IllegalArgumentException();
102: if (name == null)
103: throw new IllegalArgumentException();
104: if (value == NOT_FOUND)
105: throw new IllegalArgumentException();
106: ScriptableObject.checkValidAttributes(attributes);
107: if (obj.findPrototypeId(name) != id)
108: throw new IllegalArgumentException(name);
109:
110: if (id == constructorId) {
111: if (!(value instanceof IdFunctionObject)) {
112: throw new IllegalArgumentException(
113: "consructor should be initialized with IdFunctionObject");
114: }
115: constructor = (IdFunctionObject) value;
116: constructorAttrs = (short) attributes;
117: return;
118: }
119:
120: initSlot(id, name, value, attributes);
121: }
122:
123: private void initSlot(int id, String name, Object value,
124: int attributes) {
125: Object[] array = valueArray;
126: if (array == null)
127: throw new IllegalStateException();
128:
129: if (value == null) {
130: value = UniqueTag.NULL_VALUE;
131: }
132: int index = (id - 1) * SLOT_SPAN;
133: synchronized (this ) {
134: Object value2 = array[index + VALUE_SLOT];
135: if (value2 == null) {
136: array[index + VALUE_SLOT] = value;
137: array[index + NAME_SLOT] = name;
138: attributeArray[id - 1] = (short) attributes;
139: } else {
140: if (!name.equals(array[index + NAME_SLOT]))
141: throw new IllegalStateException();
142: }
143: }
144: }
145:
146: final IdFunctionObject createPrecachedConstructor() {
147: if (constructorId != 0)
148: throw new IllegalStateException();
149: constructorId = obj.findPrototypeId("constructor");
150: if (constructorId == 0) {
151: throw new IllegalStateException(
152: "No id for constructor property");
153: }
154: obj.initPrototypeId(constructorId);
155: if (constructor == null) {
156: throw new IllegalStateException(obj.getClass()
157: .getName()
158: + ".initPrototypeId() did not "
159: + "initialize id=" + constructorId);
160: }
161: constructor.initFunction(obj.getClassName(),
162: ScriptableObject.getTopLevelScope(obj));
163: constructor.markAsConstructor(obj);
164: return constructor;
165: }
166:
167: final int findId(String name) {
168: Object[] array = valueArray;
169: if (array == null) {
170: return obj.findPrototypeId(name);
171: }
172: int id = lastFoundId;
173: if (name == array[(id - 1) * SLOT_SPAN + NAME_SLOT]) {
174: return id;
175: }
176: id = obj.findPrototypeId(name);
177: if (id != 0) {
178: int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
179: // Make cache to work!
180: array[nameSlot] = name;
181: lastFoundId = id;
182: }
183: return id;
184: }
185:
186: final boolean has(int id) {
187: Object[] array = valueArray;
188: if (array == null) {
189: // Not yet initialized, assume all exists
190: return true;
191: }
192: int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
193: Object value = array[valueSlot];
194: if (value == null) {
195: // The particular entry has not been yet initialized
196: return true;
197: }
198: return value != NOT_FOUND;
199: }
200:
201: final Object get(int id) {
202: Object value = ensureId(id);
203: if (value == UniqueTag.NULL_VALUE) {
204: value = null;
205: }
206: return value;
207: }
208:
209: final void set(int id, Scriptable start, Object value) {
210: if (value == NOT_FOUND)
211: throw new IllegalArgumentException();
212: ensureId(id);
213: int attr = attributeArray[id - 1];
214: if ((attr & READONLY) == 0) {
215: if (start == obj) {
216: if (value == null) {
217: value = UniqueTag.NULL_VALUE;
218: }
219: int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
220: synchronized (this ) {
221: valueArray[valueSlot] = value;
222: }
223: } else {
224: int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
225: String name = (String) valueArray[nameSlot];
226: start.put(name, start, value);
227: }
228: }
229: }
230:
231: final void delete(int id) {
232: ensureId(id);
233: int attr = attributeArray[id - 1];
234: if ((attr & PERMANENT) == 0) {
235: int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
236: synchronized (this ) {
237: valueArray[valueSlot] = NOT_FOUND;
238: attributeArray[id - 1] = EMPTY;
239: }
240: }
241: }
242:
243: final int getAttributes(int id) {
244: ensureId(id);
245: return attributeArray[id - 1];
246: }
247:
248: final void setAttributes(int id, int attributes) {
249: ScriptableObject.checkValidAttributes(attributes);
250: ensureId(id);
251: synchronized (this ) {
252: attributeArray[id - 1] = (short) attributes;
253: }
254: }
255:
256: final Object[] getNames(boolean getAll, Object[] extraEntries) {
257: Object[] names = null;
258: int count = 0;
259: for (int id = 1; id <= maxId; ++id) {
260: Object value = ensureId(id);
261: if (getAll || (attributeArray[id - 1] & DONTENUM) == 0) {
262: if (value != NOT_FOUND) {
263: int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
264: String name = (String) valueArray[nameSlot];
265: if (names == null) {
266: names = new Object[maxId];
267: }
268: names[count++] = name;
269: }
270: }
271: }
272: if (count == 0) {
273: return extraEntries;
274: } else if (extraEntries == null || extraEntries.length == 0) {
275: if (count != names.length) {
276: Object[] tmp = new Object[count];
277: System.arraycopy(names, 0, tmp, 0, count);
278: names = tmp;
279: }
280: return names;
281: } else {
282: int extra = extraEntries.length;
283: Object[] tmp = new Object[extra + count];
284: System.arraycopy(extraEntries, 0, tmp, 0, extra);
285: System.arraycopy(names, 0, tmp, extra, count);
286: return tmp;
287: }
288: }
289:
290: private Object ensureId(int id) {
291: Object[] array = valueArray;
292: if (array == null) {
293: synchronized (this ) {
294: array = valueArray;
295: if (array == null) {
296: array = new Object[maxId * SLOT_SPAN];
297: valueArray = array;
298: attributeArray = new short[maxId];
299: }
300: }
301: }
302: int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
303: Object value = array[valueSlot];
304: if (value == null) {
305: if (id == constructorId) {
306: initSlot(constructorId, "constructor", constructor,
307: constructorAttrs);
308: constructor = null; // no need to refer it any longer
309: } else {
310: obj.initPrototypeId(id);
311: }
312: value = array[valueSlot];
313: if (value == null) {
314: throw new IllegalStateException(obj.getClass()
315: .getName()
316: + ".initPrototypeId(int id) "
317: + "did not initialize id=" + id);
318: }
319: }
320: return value;
321: }
322: }
323:
324: public IdScriptableObject() {
325: }
326:
327: public IdScriptableObject(Scriptable scope, Scriptable prototype) {
328: super (scope, prototype);
329: }
330:
331: protected final Object defaultGet(String name) {
332: return super .get(name, this );
333: }
334:
335: protected final void defaultPut(String name, Object value) {
336: super .put(name, this , value);
337: }
338:
339: public boolean has(String name, Scriptable start) {
340: int info = findInstanceIdInfo(name);
341: if (info != 0) {
342: int attr = (info >>> 16);
343: if ((attr & PERMANENT) != 0) {
344: return true;
345: }
346: int id = (info & 0xFFFF);
347: return NOT_FOUND != getInstanceIdValue(id);
348: }
349: if (prototypeValues != null) {
350: int id = prototypeValues.findId(name);
351: if (id != 0) {
352: return prototypeValues.has(id);
353: }
354: }
355: return super .has(name, start);
356: }
357:
358: public Object get(String name, Scriptable start) {
359: int info = findInstanceIdInfo(name);
360: if (info != 0) {
361: int id = (info & 0xFFFF);
362: return getInstanceIdValue(id);
363: }
364: if (prototypeValues != null) {
365: int id = prototypeValues.findId(name);
366: if (id != 0) {
367: return prototypeValues.get(id);
368: }
369: }
370: return super .get(name, start);
371: }
372:
373: public void put(String name, Scriptable start, Object value) {
374: int info = findInstanceIdInfo(name);
375: if (info != 0) {
376: if (start == this && isSealed()) {
377: throw Context.reportRuntimeError1("msg.modify.sealed",
378: name);
379: }
380: int attr = (info >>> 16);
381: if ((attr & READONLY) == 0) {
382: if (start == this ) {
383: int id = (info & 0xFFFF);
384: setInstanceIdValue(id, value);
385: } else {
386: start.put(name, start, value);
387: }
388: }
389: return;
390: }
391: if (prototypeValues != null) {
392: int id = prototypeValues.findId(name);
393: if (id != 0) {
394: if (start == this && isSealed()) {
395: throw Context.reportRuntimeError1(
396: "msg.modify.sealed", name);
397: }
398: prototypeValues.set(id, start, value);
399: return;
400: }
401: }
402: super .put(name, start, value);
403: }
404:
405: public void delete(String name) {
406: int info = findInstanceIdInfo(name);
407: if (info != 0) {
408: // Let the super class to throw exceptions for sealed objects
409: if (!isSealed()) {
410: int attr = (info >>> 16);
411: if ((attr & PERMANENT) == 0) {
412: int id = (info & 0xFFFF);
413: setInstanceIdValue(id, NOT_FOUND);
414: }
415: return;
416: }
417: }
418: if (prototypeValues != null) {
419: int id = prototypeValues.findId(name);
420: if (id != 0) {
421: if (!isSealed()) {
422: prototypeValues.delete(id);
423: }
424: return;
425: }
426: }
427: super .delete(name);
428: }
429:
430: public int getAttributes(String name) {
431: int info = findInstanceIdInfo(name);
432: if (info != 0) {
433: int attr = (info >>> 16);
434: return attr;
435: }
436: if (prototypeValues != null) {
437: int id = prototypeValues.findId(name);
438: if (id != 0) {
439: return prototypeValues.getAttributes(id);
440: }
441: }
442: return super .getAttributes(name);
443: }
444:
445: public void setAttributes(String name, int attributes) {
446: ScriptableObject.checkValidAttributes(attributes);
447: int info = findInstanceIdInfo(name);
448: if (info != 0) {
449: int currentAttributes = (info >>> 16);
450: if (attributes != currentAttributes) {
451: throw new RuntimeException(
452: "Change of attributes for this id is not supported");
453: }
454: return;
455: }
456: if (prototypeValues != null) {
457: int id = prototypeValues.findId(name);
458: if (id != 0) {
459: prototypeValues.setAttributes(id, attributes);
460: return;
461: }
462: }
463: super .setAttributes(name, attributes);
464: }
465:
466: Object[] getIds(boolean getAll) {
467: Object[] result = super .getIds(getAll);
468:
469: if (prototypeValues != null) {
470: result = prototypeValues.getNames(getAll, result);
471: }
472:
473: int maxInstanceId = getMaxInstanceId();
474: if (maxInstanceId != 0) {
475: Object[] ids = null;
476: int count = 0;
477:
478: for (int id = maxInstanceId; id != 0; --id) {
479: String name = getInstanceIdName(id);
480: int info = findInstanceIdInfo(name);
481: if (info != 0) {
482: int attr = (info >>> 16);
483: if ((attr & PERMANENT) == 0) {
484: if (NOT_FOUND == getInstanceIdValue(id)) {
485: continue;
486: }
487: }
488: if (getAll || (attr & DONTENUM) == 0) {
489: if (count == 0) {
490: // Need extra room for no more then [1..id] names
491: ids = new Object[id];
492: }
493: ids[count++] = name;
494: }
495: }
496: }
497: if (count != 0) {
498: if (result.length == 0 && ids.length == count) {
499: result = ids;
500: } else {
501: Object[] tmp = new Object[result.length + count];
502: System.arraycopy(result, 0, tmp, 0, result.length);
503: System.arraycopy(ids, 0, tmp, result.length, count);
504: result = tmp;
505: }
506: }
507: }
508: return result;
509: }
510:
511: /**
512: * Get maximum id findInstanceIdInfo can generate.
513: */
514: protected int getMaxInstanceId() {
515: return 0;
516: }
517:
518: protected static int instanceIdInfo(int attributes, int id) {
519: return (attributes << 16) | id;
520: }
521:
522: /**
523: * Map name to id of instance property.
524: * Should return 0 if not found or the result of
525: * {@link #instanceIdInfo(int, int)}.
526: */
527: protected int findInstanceIdInfo(String name) {
528: return 0;
529: }
530:
531: /** Map id back to property name it defines.
532: */
533: protected String getInstanceIdName(int id) {
534: throw new IllegalArgumentException(String.valueOf(id));
535: }
536:
537: /** Get id value.
538: ** If id value is constant, descendant can call cacheIdValue to store
539: ** value in the permanent cache.
540: ** Default implementation creates IdFunctionObject instance for given id
541: ** and cache its value
542: */
543: protected Object getInstanceIdValue(int id) {
544: throw new IllegalStateException(String.valueOf(id));
545: }
546:
547: /**
548: * Set or delete id value. If value == NOT_FOUND , the implementation
549: * should make sure that the following getInstanceIdValue return NOT_FOUND.
550: */
551: protected void setInstanceIdValue(int id, Object value) {
552: throw new IllegalStateException(String.valueOf(id));
553: }
554:
555: /** 'thisObj' will be null if invoked as constructor, in which case
556: ** instance of Scriptable should be returned. */
557: public Object execIdCall(IdFunctionObject f, Context cx,
558: Scriptable scope, Scriptable this Obj, Object[] args) {
559: throw f.unknown();
560: }
561:
562: public final IdFunctionObject exportAsJSClass(int maxPrototypeId,
563: Scriptable scope, boolean sealed) {
564: // Set scope and prototype unless this is top level scope itself
565: if (scope != this && scope != null) {
566: setParentScope(scope);
567: setPrototype(getObjectPrototype(scope));
568: }
569:
570: activatePrototypeMap(maxPrototypeId);
571: IdFunctionObject ctor = prototypeValues
572: .createPrecachedConstructor();
573: if (sealed) {
574: sealObject();
575: }
576: fillConstructorProperties(ctor);
577: if (sealed) {
578: ctor.sealObject();
579: }
580: ctor.exportAsScopeProperty();
581: return ctor;
582: }
583:
584: public final boolean hasPrototypeMap() {
585: return prototypeValues != null;
586: }
587:
588: public final void activatePrototypeMap(int maxPrototypeId) {
589: PrototypeValues values = new PrototypeValues(this ,
590: maxPrototypeId);
591: synchronized (this ) {
592: if (prototypeValues != null)
593: throw new IllegalStateException();
594: prototypeValues = values;
595: }
596: }
597:
598: public final void initPrototypeMethod(Object tag, int id,
599: String name, int arity) {
600: Scriptable scope = ScriptableObject.getTopLevelScope(this );
601: IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
602: prototypeValues.initValue(id, name, f, DONTENUM);
603: }
604:
605: public final void initPrototypeConstructor(IdFunctionObject f) {
606: int id = prototypeValues.constructorId;
607: if (id == 0)
608: throw new IllegalStateException();
609: if (f.methodId() != id)
610: throw new IllegalArgumentException();
611: if (isSealed()) {
612: f.sealObject();
613: }
614: prototypeValues.initValue(id, "constructor", f, DONTENUM);
615: }
616:
617: public final void initPrototypeValue(int id, String name,
618: Object value, int attributes) {
619: prototypeValues.initValue(id, name, value, attributes);
620: }
621:
622: protected void initPrototypeId(int id) {
623: throw new IllegalStateException(String.valueOf(id));
624: }
625:
626: protected int findPrototypeId(String name) {
627: throw new IllegalStateException(name);
628: }
629:
630: protected void fillConstructorProperties(IdFunctionObject ctor) {
631: }
632:
633: protected void addIdFunctionProperty(Scriptable obj, Object tag,
634: int id, String name, int arity) {
635: Scriptable scope = ScriptableObject.getTopLevelScope(obj);
636: IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
637: f.addAsProperty(obj);
638: }
639:
640: /**
641: * Utility method to construct type error to indicate incompatible call
642: * when converting script thisObj to a particular type is not possible.
643: * Possible usage would be to have a private function like realThis:
644: * <pre>
645: * private static NativeSomething realThis(Scriptable thisObj,
646: * IdFunctionObject f)
647: * {
648: * if (!(thisObj instanceof NativeSomething))
649: * throw incompatibleCallError(f);
650: * return (NativeSomething)thisObj;
651: * }
652: * </pre>
653: * Note that although such function can be implemented universally via
654: * java.lang.Class.isInstance(), it would be much more slower.
655: * @param f function that is attempting to convert 'this'
656: * object.
657: * @return Scriptable object suitable for a check by the instanceof
658: * operator.
659: * @throws RuntimeException if no more instanceof target can be found
660: */
661: protected static EcmaError incompatibleCallError(IdFunctionObject f) {
662: throw ScriptRuntime.typeError1("msg.incompat.call", f
663: .getFunctionName());
664: }
665:
666: private IdFunctionObject newIdFunction(Object tag, int id,
667: String name, int arity, Scriptable scope) {
668: IdFunctionObject f = new IdFunctionObject(this , tag, id, name,
669: arity, scope);
670: if (isSealed()) {
671: f.sealObject();
672: }
673: return f;
674: }
675:
676: private void readObject(ObjectInputStream stream)
677: throws IOException, ClassNotFoundException {
678: stream.defaultReadObject();
679: int maxPrototypeId = stream.readInt();
680: if (maxPrototypeId != 0) {
681: activatePrototypeMap(maxPrototypeId);
682: }
683: }
684:
685: private void writeObject(ObjectOutputStream stream)
686: throws IOException {
687: stream.defaultWriteObject();
688: int maxPrototypeId = 0;
689: if (prototypeValues != null) {
690: maxPrototypeId = prototypeValues.getMaxId();
691: }
692: stream.writeInt(maxPrototypeId);
693: }
694:
695: }
|