1
/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; c-basic-offset: 4 -*-
3
* The contents of this file are subject to the Netscape Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/NPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is Rhino code, released
16
* The Initial Developer of the Original Code is Netscape
17
* Communications Corporation. Portions created by Netscape are
18
* Copyright (C) 1997-1999 Netscape Communications Corporation. All
24
* Alternatively, the contents of this file may be used under the
25
* terms of the GNU Public License (the "GPL"), in which case the
26
* provisions of the GPL are applicable instead of those above.
27
* If you wish to allow use of your version of this file only
28
* under the terms of the GPL and not to allow others to use your
29
* version of this file under the NPL, indicate your decision by
30
* deleting the provisions above and replace them with the notice
31
* and other provisions required by the GPL. If you do not delete
32
* the provisions above, a recipient may use your version of this
33
* file under either the NPL or the GPL.
36
package org.mozilla.javascript;
41
Base class for native object implementation that uses IdFunctionObject to export its methods to script via <class-name>.prototype object.
43
Any descendant should implement at least the following methods:
49
To define non-function properties, the descendant should override
52
to get/set property value and provide its default attributes.
55
To customize initializition of constructor and protype objects, descendant
56
may override scopeInit or fillConstructorProperties methods.
59
public abstract class IdScriptableObject extends ScriptableObject
60
implements IdFunctionCall
62
private transient volatile PrototypeValues prototypeValues;
64
private static final class PrototypeValues implements Serializable
66
private static final int VALUE_SLOT = 0;
67
private static final int NAME_SLOT = 1;
68
private static final int SLOT_SPAN = 2;
70
private IdScriptableObject obj;
73
private volatile Object[] valueArray;
74
private volatile short[] attributeArray;
75
private volatile int lastFoundId = 1;
77
// The following helps to avoid creation of valueArray during runtime
78
// initialization for common case of "constructor" property
80
private IdFunctionObject constructor;
81
private short constructorAttrs;
83
PrototypeValues(IdScriptableObject obj, int maxId)
85
if (obj == null) throw new IllegalArgumentException();
86
if (maxId < 1) throw new IllegalArgumentException();
96
final void initValue(int id, String name, Object value, int attributes)
98
if (!(1 <= id && id <= maxId))
99
throw new IllegalArgumentException();
101
throw new IllegalArgumentException();
102
if (value == NOT_FOUND)
103
throw new IllegalArgumentException();
104
ScriptableObject.checkValidAttributes(attributes);
105
if (obj.findPrototypeId(name) != id)
106
throw new IllegalArgumentException(name);
108
if (id == constructorId) {
109
if (!(value instanceof IdFunctionObject)) {
110
throw new IllegalArgumentException("consructor should be initialized with IdFunctionObject");
112
constructor = (IdFunctionObject)value;
113
constructorAttrs = (short)attributes;
117
initSlot(id, name, value, attributes);
120
private void initSlot(int id, String name, Object value,
123
Object[] array = valueArray;
125
throw new IllegalStateException();
128
value = UniqueTag.NULL_VALUE;
130
int index = (id - 1) * SLOT_SPAN;
131
synchronized (this) {
132
Object value2 = array[index + VALUE_SLOT];
133
if (value2 == null) {
134
array[index + VALUE_SLOT] = value;
135
array[index + NAME_SLOT] = name;
136
attributeArray[id - 1] = (short)attributes;
138
if (!name.equals(array[index + NAME_SLOT]))
139
throw new IllegalStateException();
144
final IdFunctionObject createPrecachedConstructor()
146
if (constructorId != 0) throw new IllegalStateException();
147
constructorId = obj.findPrototypeId("constructor");
148
if (constructorId == 0) {
149
throw new IllegalStateException(
150
"No id for constructor property");
152
obj.initPrototypeId(constructorId);
153
if (constructor == null) {
154
throw new IllegalStateException(
155
obj.getClass().getName()+".initPrototypeId() did not "
156
+"initialize id="+constructorId);
158
constructor.initFunction(obj.getClassName(),
159
ScriptableObject.getTopLevelScope(obj));
160
constructor.markAsConstructor(obj);
164
final int findId(String name)
166
Object[] array = valueArray;
168
return obj.findPrototypeId(name);
170
int id = lastFoundId;
171
if (name == array[(id - 1) * SLOT_SPAN + NAME_SLOT]) {
174
id = obj.findPrototypeId(name);
176
int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
177
// Make cache to work!
178
array[nameSlot] = name;
184
final boolean has(int id)
186
Object[] array = valueArray;
188
// Not yet initialized, assume all exists
191
int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
192
Object value = array[valueSlot];
194
// The particular entry has not been yet initialized
197
return value != NOT_FOUND;
200
final Object get(int id)
202
Object value = ensureId(id);
203
if (value == UniqueTag.NULL_VALUE) {
209
final void set(int id, Scriptable start, Object value)
211
if (value == NOT_FOUND) throw new IllegalArgumentException();
213
int attr = attributeArray[id - 1];
214
if ((attr & READONLY) == 0) {
217
value = UniqueTag.NULL_VALUE;
219
int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
220
synchronized (this) {
221
valueArray[valueSlot] = value;
225
int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
226
String name = (String)valueArray[nameSlot];
227
start.put(name, start, value);
232
final void delete(int id)
235
int attr = attributeArray[id - 1];
236
if ((attr & PERMANENT) == 0) {
237
int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
238
synchronized (this) {
239
valueArray[valueSlot] = NOT_FOUND;
240
attributeArray[id - 1] = EMPTY;
245
final int getAttributes(int id)
248
return attributeArray[id - 1];
251
final void setAttributes(int id, int attributes)
253
ScriptableObject.checkValidAttributes(attributes);
255
synchronized (this) {
256
attributeArray[id - 1] = (short)attributes;
260
final Object[] getNames(boolean getAll, Object[] extraEntries)
262
Object[] names = null;
264
for (int id = 1; id <= maxId; ++id) {
265
Object value = ensureId(id);
266
if (getAll || (attributeArray[id - 1] & DONTENUM) == 0) {
267
if (value != NOT_FOUND) {
268
int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT;
269
String name = (String)valueArray[nameSlot];
271
names = new Object[maxId];
273
names[count++] = name;
279
} else if (extraEntries == null || extraEntries.length == 0) {
280
if (count != names.length) {
281
Object[] tmp = new Object[count];
282
System.arraycopy(names, 0, tmp, 0, count);
287
int extra = extraEntries.length;
288
Object[] tmp = new Object[extra + count];
289
System.arraycopy(extraEntries, 0, tmp, 0, extra);
290
System.arraycopy(names, 0, tmp, extra, count);
295
private Object ensureId(int id)
297
Object[] array = valueArray;
299
synchronized (this) {
302
array = new Object[maxId * SLOT_SPAN];
304
attributeArray = new short[maxId];
308
int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT;
309
Object value = array[valueSlot];
311
if (id == constructorId) {
312
initSlot(constructorId, "constructor",
313
constructor, constructorAttrs);
314
constructor = null; // no need to refer it any longer
316
obj.initPrototypeId(id);
318
value = array[valueSlot];
320
throw new IllegalStateException(
321
obj.getClass().getName()+".initPrototypeId(int id) "
322
+"did not initialize id="+id);
329
public IdScriptableObject()
333
public IdScriptableObject(Scriptable scope, Scriptable prototype)
335
super(scope, prototype);
338
protected final Object defaultGet(String name)
340
return super.get(name, this);
343
protected final void defaultPut(String name, Object value)
345
super.put(name, this, value);
348
public boolean has(String name, Scriptable start)
350
int info = findInstanceIdInfo(name);
352
int attr = (info >>> 16);
353
if ((attr & PERMANENT) != 0) {
356
int id = (info & 0xFFFF);
357
return NOT_FOUND != getInstanceIdValue(id);
359
if (prototypeValues != null) {
360
int id = prototypeValues.findId(name);
362
return prototypeValues.has(id);
365
return super.has(name, start);
368
public Object get(String name, Scriptable start)
370
int info = findInstanceIdInfo(name);
372
int id = (info & 0xFFFF);
373
return getInstanceIdValue(id);
375
if (prototypeValues != null) {
376
int id = prototypeValues.findId(name);
378
return prototypeValues.get(id);
381
return super.get(name, start);
384
public void put(String name, Scriptable start, Object value)
386
int info = findInstanceIdInfo(name);
388
if (start == this && isSealed()) {
389
throw Context.reportRuntimeError1("msg.modify.sealed",
392
int attr = (info >>> 16);
393
if ((attr & READONLY) == 0) {
395
int id = (info & 0xFFFF);
396
setInstanceIdValue(id, value);
399
start.put(name, start, value);
404
if (prototypeValues != null) {
405
int id = prototypeValues.findId(name);
407
if (start == this && isSealed()) {
408
throw Context.reportRuntimeError1("msg.modify.sealed",
411
prototypeValues.set(id, start, value);
415
super.put(name, start, value);
418
public void delete(String name)
420
int info = findInstanceIdInfo(name);
422
// Let the super class to throw exceptions for sealed objects
424
int attr = (info >>> 16);
425
if ((attr & PERMANENT) == 0) {
426
int id = (info & 0xFFFF);
427
setInstanceIdValue(id, NOT_FOUND);
432
if (prototypeValues != null) {
433
int id = prototypeValues.findId(name);
436
prototypeValues.delete(id);
444
public int getAttributes(String name)
446
int info = findInstanceIdInfo(name);
448
int attr = (info >>> 16);
451
if (prototypeValues != null) {
452
int id = prototypeValues.findId(name);
454
return prototypeValues.getAttributes(id);
457
return super.getAttributes(name);
460
public void setAttributes(String name, int attributes)
462
ScriptableObject.checkValidAttributes(attributes);
463
int info = findInstanceIdInfo(name);
465
int currentAttributes = (info >>> 16);
466
if (attributes != currentAttributes) {
467
throw new RuntimeException(
468
"Change of attributes for this id is not supported");
472
if (prototypeValues != null) {
473
int id = prototypeValues.findId(name);
475
prototypeValues.setAttributes(id, attributes);
479
super.setAttributes(name, attributes);
482
Object[] getIds(boolean getAll)
484
Object[] result = super.getIds(getAll);
486
if (prototypeValues != null) {
487
result = prototypeValues.getNames(getAll, result);
490
int maxInstanceId = getMaxInstanceId();
491
if (maxInstanceId != 0) {
495
for (int id = maxInstanceId; id != 0; --id) {
496
String name = getInstanceIdName(id);
497
int info = findInstanceIdInfo(name);
499
int attr = (info >>> 16);
500
if ((attr & PERMANENT) == 0) {
501
if (NOT_FOUND == getInstanceIdValue(id)) {
505
if (getAll || (attr & DONTENUM) == 0) {
507
// Need extra room for no more then [1..id] names
508
ids = new Object[id];
515
if (result.length == 0 && ids.length == count) {
519
Object[] tmp = new Object[result.length + count];
520
System.arraycopy(result, 0, tmp, 0, result.length);
521
System.arraycopy(ids, 0, tmp, result.length, count);
530
* Get maximum id findInstanceIdInfo can generate.
532
protected int getMaxInstanceId()
537
protected static int instanceIdInfo(int attributes, int id)
539
return (attributes << 16) | id;
543
* Map name to id of instance property.
544
* Should return 0 if not found or the result of
545
* {@link #instanceIdInfo(int, int)}.
547
protected int findInstanceIdInfo(String name)
552
/** Map id back to property name it defines.
554
protected String getInstanceIdName(int id)
556
throw new IllegalArgumentException(String.valueOf(id));
560
** If id value is constant, descendant can call cacheIdValue to store
561
** value in the permanent cache.
562
** Default implementation creates IdFunctionObject instance for given id
563
** and cache its value
565
protected Object getInstanceIdValue(int id)
567
throw new IllegalStateException(String.valueOf(id));
571
* Set or delete id value. If value == NOT_FOUND , the implementation
572
* should make sure that the following getInstanceIdValue return NOT_FOUND.
574
protected void setInstanceIdValue(int id, Object value)
576
throw new IllegalStateException(String.valueOf(id));
579
/** 'thisObj' will be null if invoked as constructor, in which case
580
** instance of Scriptable should be returned. */
581
public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
582
Scriptable thisObj, Object[] args)
587
public final IdFunctionObject exportAsJSClass(int maxPrototypeId,
591
// Set scope and prototype unless this is top level scope itself
592
if (scope != this && scope != null) {
593
setParentScope(scope);
594
setPrototype(getObjectPrototype(scope));
597
activatePrototypeMap(maxPrototypeId);
598
IdFunctionObject ctor = prototypeValues.createPrecachedConstructor();
602
fillConstructorProperties(ctor);
606
ctor.exportAsScopeProperty();
610
public final boolean hasPrototypeMap()
612
return prototypeValues != null;
615
public final void activatePrototypeMap(int maxPrototypeId)
617
PrototypeValues values = new PrototypeValues(this, maxPrototypeId);
618
synchronized (this) {
619
if (prototypeValues != null)
620
throw new IllegalStateException();
621
prototypeValues = values;
625
public final void initPrototypeMethod(Object tag, int id, String name,
628
Scriptable scope = ScriptableObject.getTopLevelScope(this);
629
IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
630
prototypeValues.initValue(id, name, f, DONTENUM);
633
public final void initPrototypeConstructor(IdFunctionObject f)
635
int id = prototypeValues.constructorId;
637
throw new IllegalStateException();
638
if (f.methodId() != id)
639
throw new IllegalArgumentException();
640
if (isSealed()) { f.sealObject(); }
641
prototypeValues.initValue(id, "constructor", f, DONTENUM);
644
public final void initPrototypeValue(int id, String name, Object value,
647
prototypeValues.initValue(id, name, value, attributes);
650
protected void initPrototypeId(int id)
652
throw new IllegalStateException(String.valueOf(id));
655
protected int findPrototypeId(String name)
657
throw new IllegalStateException(name);
660
protected void fillConstructorProperties(IdFunctionObject ctor)
664
protected void addIdFunctionProperty(Scriptable obj, Object tag, int id,
665
String name, int arity)
667
Scriptable scope = ScriptableObject.getTopLevelScope(obj);
668
IdFunctionObject f = newIdFunction(tag, id, name, arity, scope);
669
f.addAsProperty(obj);
673
* Utility method to construct type error to indicate incompatible call
674
* when converting script thisObj to a particular type is not possible.
675
* Possible usage would be to have a private function like realThis:
677
* private static NativeSomething realThis(Scriptable thisObj,
678
* IdFunctionObject f)
680
* if (!(thisObj instanceof NativeSomething))
681
* throw incompatibleCallError(f);
682
* return (NativeSomething)thisObj;
685
* Note that although such function can be implemented universally via
686
* java.lang.Class.isInstance(), it would be much more slower.
687
* @param readOnly specify if the function f does not change state of
689
* @return Scriptable object suitable for a check by the instanceof
691
* @throws RuntimeException if no more instanceof target can be found
693
protected static EcmaError incompatibleCallError(IdFunctionObject f)
695
throw ScriptRuntime.typeError1("msg.incompat.call",
696
f.getFunctionName());
699
private IdFunctionObject newIdFunction(Object tag, int id, String name,
700
int arity, Scriptable scope)
702
IdFunctionObject f = new IdFunctionObject(this, tag, id, name, arity,
704
if (isSealed()) { f.sealObject(); }
708
private void readObject(ObjectInputStream stream)
709
throws IOException, ClassNotFoundException
711
stream.defaultReadObject();
712
int maxPrototypeId = stream.readInt();
713
if (maxPrototypeId != 0) {
714
activatePrototypeMap(maxPrototypeId);
718
private void writeObject(ObjectOutputStream stream)
721
stream.defaultWriteObject();
722
int maxPrototypeId = 0;
723
if (prototypeValues != null) {
724
maxPrototypeId = prototypeValues.getMaxId();
726
stream.writeInt(maxPrototypeId);