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 oqr
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;
39
Base class for native object implementation that uses IdFunction to export its methods to script via <class-name>.prototype object.
41
Any descendant should implement at least the following methods:
47
To define non-function properties, the descendant should customize
50
getIdDefaultAttributes
52
to get/set property value and provide its default attributes.
54
To customize initializition of constructor and protype objects, descendant
55
may override scopeInit or fillConstructorProperties methods.
58
public abstract class IdScriptable extends ScriptableObject
59
implements IdFunctionMaster
61
/** NULL_TAG can be used to distinguish between uninitialized and null
64
protected static final Object NULL_TAG = new Object();
66
public IdScriptable() {
67
activateIdMap(maxInstanceId());
70
public boolean has(String name, Scriptable start) {
72
int id = mapNameToId(name);
77
return super.has(name, start);
80
public Object get(String name, Scriptable start) {
82
int maxId = this.maxId;
84
Object[] data = idMapData;
86
int id = mapNameToId(name);
88
return getIdValue(id);
93
if (data[id - 1 + maxId] != name) {
94
id = mapNameToId(name);
95
if (id == 0) { break L; }
96
data[id - 1 + maxId] = name;
99
Object value = data[id - 1];
101
value = getIdValue(id);
103
else if (value == NULL_TAG) {
112
int id = mapNameToId(name);
114
Object[] data = idMapData;
116
return getIdValue(id);
119
Object value = data[id - 1];
121
value = getIdValue(id);
123
else if (value == NULL_TAG) {
131
return super.get(name, start);
134
public void put(String name, Scriptable start, Object value) {
136
int id = mapNameToId(name);
138
int attr = getAttributes(id);
139
if ((attr & READONLY) == 0) {
141
setIdValue(id, value);
144
start.put(name, start, value);
150
super.put(name, start, value);
153
public void delete(String name) {
155
int id = mapNameToId(name);
157
// Let the super class to throw exceptions for sealed objects
159
int attr = getAttributes(id);
160
if ((attr & PERMANENT) == 0) {
170
public int getAttributes(String name, Scriptable start)
171
throws PropertyException
174
int id = mapNameToId(name);
177
return getAttributes(id);
179
// For ids with deleted values super will throw exceptions
182
return super.getAttributes(name, start);
185
public void setAttributes(String name, Scriptable start,
187
throws PropertyException
190
int id = mapNameToId(name);
193
synchronized (this) {
194
setAttributes(id, attributes);
198
// For ids with deleted values super will throw exceptions
201
super.setAttributes(name, start, attributes);
204
synchronized void addPropertyAttribute(int attribute) {
205
extraIdAttributes |= (byte)attribute;
206
super.addPropertyAttribute(attribute);
210
* Redefine ScriptableObject.defineProperty to allow changing
211
* values/attributes of id-based properties unless
212
* getIdDefaultAttributes contains the READONLY attribute.
213
* @see #getIdDefaultAttributes
214
* @see org.mozilla.javascript.ScriptableObject#defineProperty
216
public void defineProperty(String propertyName, Object value,
220
int id = mapNameToId(propertyName);
222
int default_attributes = getIdDefaultAttributes(id);
223
if ((default_attributes & READONLY) != 0) {
224
// It is a bug to redefine id with readonly attributes
225
throw new RuntimeException
226
("Attempt to redefine read-only id " + propertyName);
228
setAttributes(id, attributes);
229
setIdValue(id, value);
233
super.defineProperty(propertyName, value, attributes);
236
Object[] getIds(boolean getAll) {
237
Object[] result = super.getIds(getAll);
243
for (int id = maxId; id != 0; --id) {
245
if (getAll || (getAttributes(id) & DONTENUM) == 0) {
247
// Need extra room for nor more then [1..id] names
248
ids = new Object[id];
250
ids[count++] = getIdName(id);
255
if (result.length == 0 && ids.length == count) {
259
Object[] tmp = new Object[result.length + count];
260
System.arraycopy(result, 0, tmp, 0, result.length);
261
System.arraycopy(ids, 0, tmp, result.length, count);
269
/** Return maximum id number that should be present in each instance. */
270
protected int maxInstanceId() { return 0; }
273
* Map name to id of prototype or instance property.
274
* Should return 0 if not found
276
protected abstract int mapNameToId(String name);
278
/** Map id back to property name it defines.
280
protected abstract String getIdName(int id);
282
/** Get default attributes for id.
283
** Default implementation return DONTENUM that is the standard attribute
284
** for core EcmaScript function. Typically descendants need to overwrite
285
** this for non-function attributes like length to return
286
** DONTENUM | READONLY | PERMANENT or DONTENUM | PERMANENT
288
protected int getIdDefaultAttributes(int id) {
292
/** Check if id value exists.
293
** Default implementation always returns true */
294
protected boolean hasIdValue(int id) {
299
** If id value is constant, descendant can call cacheIdValue to store
300
** value in the permanent cache.
301
** Default implementation creates IdFunction instance for given id
302
** and cache its value
304
protected Object getIdValue(int id) {
305
IdFunction f = newIdFunction(id);
306
f.setParentScope(getParentScope());
307
return cacheIdValue(id, f);
312
* IdScriptable never calls this method if result of
313
* <code>getIdDefaultAttributes(id)</code> contains READONLY attribute.
314
* Descendants can overwrite this method to provide custom handler for
315
* property assignments.
317
protected void setIdValue(int id, Object value) {
318
synchronized (this) {
319
ensureIdData()[id - 1] = (value != null) ? value : NULL_TAG;
324
* Store value in permanent cache unless value was already assigned to id.
325
* After this call IdScriptable never calls hasIdValue and getIdValue
328
protected Object cacheIdValue(int id, Object value) {
329
synchronized (this) {
330
Object[] data = ensureIdData();
331
Object curValue = data[id - 1];
332
if (curValue == null) {
333
data[id - 1] = (value != null) ? value : NULL_TAG;
343
* Delete value represented by id so hasIdValue return false.
344
* IdScriptable never calls this method if result of
345
* <code>getIdDefaultAttributes(id)</code> contains PERMANENT attribute.
346
* Descendants can overwrite this method to provide custom handler for
349
protected void deleteIdValue(int id) {
350
synchronized (this) {
351
ensureIdData()[id - 1] = NOT_FOUND;
355
/** 'thisObj' will be null if invoked as constructor, in which case
356
** instance of Scriptable should be returned. */
357
public Object execMethod(int methodId, IdFunction function,
358
Context cx, Scriptable scope,
359
Scriptable thisObj, Object[] args)
360
throws JavaScriptException
362
throw IdFunction.onBadMethodId(this, methodId);
365
/** Get arity or defined argument count for method with given id.
366
** Should return -1 if methodId is not known or can not be used
367
** with execMethod call. */
368
public int methodArity(int methodId) {
372
/** Activate id support with the given maximum id */
373
protected void activateIdMap(int maxId) {
377
/** Sets whether newly constructed function objects should be sealed */
378
protected void setSealFunctionsFlag(boolean sealed) {
379
setSetupFlag(SEAL_FUNCTIONS_FLAG, sealed);
383
* Set parameters of function properties.
384
* Currently only determines whether functions should use dynamic scope.
385
* @param cx context to read function parameters.
387
* @see org.mozilla.javascript.Context#hasCompileFunctionsWithDynamicScope
389
protected void setFunctionParametrs(Context cx) {
390
setSetupFlag(USE_DYNAMIC_SCOPE_FLAG,
391
cx.hasCompileFunctionsWithDynamicScope());
394
private void setSetupFlag(int flag, boolean value) {
395
setupFlags = (byte)(value ? setupFlags | flag : setupFlags & ~flag);
399
* Prepare this object to serve as the prototype property of constructor
400
* object with name <code>getClassName()<code> defined in
401
* <code>scope</code>.
402
* @param maxId maximum id available in prototype object
403
* @param cx current context
404
* @param scope object to define constructor in.
405
* @param sealed indicates whether object and all its properties should
408
public void addAsPrototype(int maxId, Context cx, Scriptable scope,
411
activateIdMap(maxId);
413
setSealFunctionsFlag(sealed);
414
setFunctionParametrs(cx);
416
int constructorId = mapNameToId("constructor");
417
if (constructorId == 0) {
418
// It is a bug to call this function without id for constructor
419
throw new RuntimeException("No id for constructor property");
422
IdFunction ctor = newIdFunction(constructorId);
423
ctor.initAsConstructor(scope, this);
424
fillConstructorProperties(cx, ctor, sealed);
427
ctor.addPropertyAttribute(READONLY);
430
setParentScope(ctor);
431
setPrototype(getObjectPrototype(scope));
432
cacheIdValue(constructorId, ctor);
438
defineProperty(scope, getClassName(), ctor, ScriptableObject.DONTENUM);
441
protected void fillConstructorProperties
442
(Context cx, IdFunction ctor, boolean sealed)
446
protected void addIdFunctionProperty
447
(Scriptable obj, int id, boolean sealed)
449
IdFunction f = newIdFunction(id);
450
if (sealed) { f.sealObject(); }
451
defineProperty(obj, getIdName(id), f, DONTENUM);
455
* Utility method for converting target object into native this.
456
* Possible usage would be to have a private function like realThis:
458
private NativeSomething realThis(Scriptable thisObj,
459
IdFunction f, boolean readOnly)
461
while (!(thisObj instanceof NativeSomething)) {
462
thisObj = nextInstanceCheck(thisObj, f, readOnly);
464
return (NativeSomething)thisObj;
467
* Note that although such function can be implemented universally via
468
* java.lang.Class.isInstance(), it would be much more slower.
469
* @param readOnly specify if the function f does not change state of object.
470
* @return Scriptable object suitable for a check by the instanceof operator.
471
* @throws RuntimeException if no more instanceof target can be found
473
protected Scriptable nextInstanceCheck(Scriptable thisObj,
477
if (readOnly && 0 != (setupFlags & USE_DYNAMIC_SCOPE_FLAG)) {
478
// for read only functions under dynamic scope look prototype chain
479
thisObj = thisObj.getPrototype();
480
if (thisObj != null) { return thisObj; }
482
throw NativeGlobal.typeError1("msg.incompat.call",
483
f.getFunctionName(), f);
486
protected IdFunction newIdFunction(int id) {
487
IdFunction f = new IdFunction(this, getIdName(id), id);
488
if (0 != (setupFlags & SEAL_FUNCTIONS_FLAG)) { f.sealObject(); }
492
protected final Object wrap_double(double x) {
493
return (x == x) ? new Double(x) : ScriptRuntime.NaNobj;
496
protected final Object wrap_int(int x) {
498
if (b == x) { return new Byte(b); }
499
return new Integer(x);
502
protected final Object wrap_long(long x) {
504
if (i == x) { return wrap_int(i); }
508
protected final Object wrap_boolean(boolean x) {
509
return x ? Boolean.TRUE : Boolean.FALSE;
512
private boolean hasValue(int id) {
514
Object[] data = idMapData;
515
if (data == null || (value = data[id - 1]) == null) {
516
return hasIdValue(id);
519
return value != NOT_FOUND;
523
// Must be called only from synchronized (this)
524
private Object[] ensureIdData() {
525
Object[] data = idMapData;
527
idMapData = data = new Object[CACHE_NAMES ? maxId * 2 : maxId];
532
private int getAttributes(int id) {
533
int attributes = getIdDefaultAttributes(id) | extraIdAttributes;
534
byte[] array = attributesArray;
536
attributes |= 0xFF & array[id - 1];
541
private void setAttributes(int id, int attributes) {
542
int defaultAttrs = getIdDefaultAttributes(id);
543
if ((attributes & defaultAttrs) != defaultAttrs) {
544
// It is a bug to set attributes to less restrictive values
545
// then given by defaultAttrs
546
throw new RuntimeException("Attempt to unset default attributes");
548
// Store only additional bits
549
attributes &= ~defaultAttrs;
550
byte[] array = attributesArray;
551
if (array == null && attributes != 0) {
552
synchronized (this) {
553
array = attributesArray;
555
attributesArray = array = new byte[maxId];
560
array[id - 1] = (byte)attributes;
565
private Object[] idMapData;
566
private byte[] attributesArray;
568
private static final boolean CACHE_NAMES = true;
569
private int lastIdCache;
571
private static final int USE_DYNAMIC_SCOPE_FLAG = 1 << 0;
572
private static final int SEAL_FUNCTIONS_FLAG = 1 << 1;
574
private byte setupFlags;
575
private byte extraIdAttributes;