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