org.mozilla.javascript.NativeSymbol Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino Show documentation
Show all versions of rhino Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically
embedded into Java applications to provide scripting to end users.
The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import java.util.HashMap;
import java.util.Map;
/**
* This is an implementation of the standard "Symbol" type that implements all of its weird
* properties. One of them is that some objects can have an "internal data slot" that makes them a
* Symbol and others cannot.
*/
public class NativeSymbol extends IdScriptableObject implements Symbol {
private static final long serialVersionUID = -589539749749830003L;
public static final String CLASS_NAME = "Symbol";
public static final String TYPE_NAME = "symbol";
private static final Object GLOBAL_TABLE_KEY = new Object();
private static final Object CONSTRUCTOR_SLOT = new Object();
private final SymbolKey key;
private final NativeSymbol symbolData;
public static void init(Context cx, Scriptable scope, boolean sealed) {
NativeSymbol obj = new NativeSymbol("");
ScriptableObject ctor = obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, false);
cx.putThreadLocal(CONSTRUCTOR_SLOT, Boolean.TRUE);
try {
createStandardSymbol(cx, scope, ctor, "iterator", SymbolKey.ITERATOR);
createStandardSymbol(cx, scope, ctor, "species", SymbolKey.SPECIES);
createStandardSymbol(cx, scope, ctor, "toStringTag", SymbolKey.TO_STRING_TAG);
createStandardSymbol(cx, scope, ctor, "hasInstance", SymbolKey.HAS_INSTANCE);
createStandardSymbol(
cx, scope, ctor, "isConcatSpreadable", SymbolKey.IS_CONCAT_SPREADABLE);
createStandardSymbol(cx, scope, ctor, "isRegExp", SymbolKey.IS_REGEXP);
createStandardSymbol(cx, scope, ctor, "toPrimitive", SymbolKey.TO_PRIMITIVE);
createStandardSymbol(cx, scope, ctor, "match", SymbolKey.MATCH);
createStandardSymbol(cx, scope, ctor, "replace", SymbolKey.REPLACE);
createStandardSymbol(cx, scope, ctor, "search", SymbolKey.SEARCH);
createStandardSymbol(cx, scope, ctor, "split", SymbolKey.SPLIT);
createStandardSymbol(cx, scope, ctor, "unscopables", SymbolKey.UNSCOPABLES);
} finally {
cx.removeThreadLocal(CONSTRUCTOR_SLOT);
}
if (sealed) {
// Can't seal until we have created all the stuff above!
ctor.sealObject();
}
}
/**
* This has to be used only for constructing the prototype instance. This sets symbolData to
* null (see isSymbol() for more).
*
* @param desc the description
*/
private NativeSymbol(String desc) {
this.key = new SymbolKey(desc);
this.symbolData = null;
}
NativeSymbol(SymbolKey key) {
this.key = key;
this.symbolData = this;
}
public NativeSymbol(NativeSymbol s) {
this.key = s.key;
this.symbolData = s.symbolData;
}
/**
* Use this when we need to create symbols internally because of the convoluted way we have to
* construct them.
*/
public static NativeSymbol construct(Context cx, Scriptable scope, Object[] args) {
cx.putThreadLocal(CONSTRUCTOR_SLOT, Boolean.TRUE);
try {
return (NativeSymbol) cx.newObject(scope, CLASS_NAME, args);
} finally {
cx.removeThreadLocal(CONSTRUCTOR_SLOT);
}
}
@Override
public String getClassName() {
return CLASS_NAME;
}
@Override
protected void fillConstructorProperties(IdFunctionObject ctor) {
super.fillConstructorProperties(ctor);
addIdFunctionProperty(ctor, CLASS_NAME, ConstructorId_for, "for", 1);
addIdFunctionProperty(ctor, CLASS_NAME, ConstructorId_keyFor, "keyFor", 1);
}
private static void createStandardSymbol(
Context cx, Scriptable scope, ScriptableObject ctor, String name, SymbolKey key) {
Scriptable sym = cx.newObject(scope, CLASS_NAME, new Object[] {name, key});
ctor.defineProperty(name, sym, DONTENUM | READONLY | PERMANENT);
}
@Override
protected int findPrototypeId(String s) {
int id = 0;
switch (s) {
case "constructor":
id = Id_constructor;
break;
case "toString":
id = Id_toString;
break;
case "valueOf":
id = Id_valueOf;
break;
default:
id = 0;
break;
}
return id;
}
@Override
protected int findPrototypeId(Symbol key) {
if (SymbolKey.TO_STRING_TAG.equals(key)) {
return SymbolId_toStringTag;
} else if (SymbolKey.TO_PRIMITIVE.equals(key)) {
return SymbolId_toPrimitive;
}
return 0;
}
private static final int ConstructorId_keyFor = -2,
ConstructorId_for = -1,
Id_constructor = 1,
Id_toString = 2,
Id_valueOf = 4,
SymbolId_toStringTag = 3,
SymbolId_toPrimitive = 5,
MAX_PROTOTYPE_ID = SymbolId_toPrimitive;
@Override
protected void initPrototypeId(int id) {
switch (id) {
case Id_constructor:
initPrototypeMethod(CLASS_NAME, id, "constructor", 0);
break;
case Id_toString:
initPrototypeMethod(CLASS_NAME, id, "toString", 0);
break;
case Id_valueOf:
initPrototypeMethod(CLASS_NAME, id, "valueOf", 0);
break;
case SymbolId_toStringTag:
initPrototypeValue(id, SymbolKey.TO_STRING_TAG, CLASS_NAME, DONTENUM | READONLY);
break;
case SymbolId_toPrimitive:
initPrototypeMethod(
CLASS_NAME, id, SymbolKey.TO_PRIMITIVE, "Symbol.toPrimitive", 1);
break;
default:
super.initPrototypeId(id);
break;
}
}
@Override
public Object execIdCall(
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (!f.hasTag(CLASS_NAME)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int id = f.methodId();
switch (id) {
case ConstructorId_for:
return js_for(cx, scope, args);
case ConstructorId_keyFor:
return js_keyFor(cx, scope, args);
case Id_constructor:
if (thisObj == null) {
if (cx.getThreadLocal(CONSTRUCTOR_SLOT) == null) {
// We should never get to this via "new".
throw ScriptRuntime.typeErrorById("msg.no.symbol.new");
}
// Unless we are being called by our own internal "new"
return js_constructor(args);
}
return construct(cx, scope, args);
case Id_toString:
return getSelf(cx, scope, thisObj).toString();
case Id_valueOf:
case SymbolId_toPrimitive:
return getSelf(cx, scope, thisObj).js_valueOf();
default:
return super.execIdCall(f, cx, scope, thisObj, args);
}
}
private static NativeSymbol getSelf(Context cx, Scriptable scope, Object thisObj) {
try {
return (NativeSymbol) ScriptRuntime.toObject(cx, scope, thisObj);
} catch (ClassCastException cce) {
throw ScriptRuntime.typeErrorById("msg.invalid.type", thisObj.getClass().getName());
}
}
private static NativeSymbol js_constructor(Object[] args) {
String desc;
if (args.length > 0) {
if (Undefined.instance.equals(args[0])) {
desc = "";
} else {
desc = ScriptRuntime.toString(args[0]);
}
} else {
desc = "";
}
if (args.length > 1) {
return new NativeSymbol((SymbolKey) args[1]);
}
return new NativeSymbol(new SymbolKey(desc));
}
private Object js_valueOf() {
// In the case that "Object()" was called we actually have a different "internal slot"
return symbolData;
}
private Object js_for(Context cx, Scriptable scope, Object[] args) {
String name =
(args.length > 0
? ScriptRuntime.toString(args[0])
: ScriptRuntime.toString(Undefined.instance));
Map table = getGlobalMap();
NativeSymbol ret = table.get(name);
if (ret == null) {
ret = construct(cx, scope, new Object[] {name});
table.put(name, ret);
}
return ret;
}
private Object js_keyFor(Context cx, Scriptable scope, Object[] args) {
Object s = (args.length > 0 ? args[0] : Undefined.instance);
if (!(s instanceof NativeSymbol)) {
throw ScriptRuntime.throwCustomError(cx, scope, "TypeError", "Not a Symbol");
}
NativeSymbol sym = (NativeSymbol) s;
Map table = getGlobalMap();
for (Map.Entry e : table.entrySet()) {
if (e.getValue().key == sym.key) {
return e.getKey();
}
}
return Undefined.instance;
}
@Override
public String toString() {
return key.toString();
}
// Symbol objects have a special property that one cannot add properties.
private static boolean isStrictMode() {
final Context cx = Context.getCurrentContext();
return (cx != null) && cx.isStrictMode();
}
@Override
public void put(String name, Scriptable start, Object value) {
if (!isSymbol()) {
super.put(name, start, value);
} else if (isStrictMode()) {
throw ScriptRuntime.typeErrorById("msg.no.assign.symbol.strict");
}
}
@Override
public void put(int index, Scriptable start, Object value) {
if (!isSymbol()) {
super.put(index, start, value);
} else if (isStrictMode()) {
throw ScriptRuntime.typeErrorById("msg.no.assign.symbol.strict");
}
}
@Override
public void put(Symbol key, Scriptable start, Object value) {
if (!isSymbol()) {
super.put(key, start, value);
} else if (isStrictMode()) {
throw ScriptRuntime.typeErrorById("msg.no.assign.symbol.strict");
}
}
/**
* Object() on a Symbol constructs an object which is NOT a symbol, but which has an "internal
* data slot" that is. Furthermore, such an object has the Symbol prototype so this particular
* object is still used. Account for that here: an "Object" that was created from a Symbol has a
* different value of the slot.
*/
public boolean isSymbol() {
return (symbolData == this);
}
@Override
public String getTypeOf() {
return (isSymbol() ? TYPE_NAME : super.getTypeOf());
}
@Override
public int hashCode() {
return key.hashCode();
}
@Override
public boolean equals(Object x) {
return key.equals(x);
}
SymbolKey getKey() {
return key;
}
@SuppressWarnings("unchecked")
private Map getGlobalMap() {
ScriptableObject top = (ScriptableObject) getTopLevelScope(this);
Map map =
(Map) top.getAssociatedValue(GLOBAL_TABLE_KEY);
if (map == null) {
map = new HashMap<>();
top.associateValue(GLOBAL_TABLE_KEY, map);
}
return map;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy