org.mozilla.javascript.NativeSymbol Maven / Gradle / Ivy
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, sealed);
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);
}
}
private NativeSymbol(String desc) {
this.key = new SymbolKey(desc);
this.symbolData = this;
}
private 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);
}
// #string_id_map#
@Override
protected int findPrototypeId(String s) {
int id = 0;
// #generated# Last update: 2016-01-26 16:39:41 PST
L0: { id = 0; String X = null;
int s_length = s.length();
if (s_length==7) { X="valueOf";id=Id_valueOf; }
else if (s_length==8) { X="toString";id=Id_toString; }
else if (s_length==11) { X="constructor";id=Id_constructor; }
if (X!=null && X!=s && !X.equals(s)) id = 0;
break L0;
}
// #/generated#
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;
// #/string_id_map#
@Override
protected void initPrototypeId(int id)
{
String s = null;
int arity = -1;
switch (id) {
case Id_constructor:
initPrototypeMethod(CLASS_NAME, id, "constructor", 1);
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.typeError0("msg.no.symbol.new");
} else {
// Unless we are being called by our own internal "new"
return js_constructor(args);
}
} else {
return construct(cx, scope, args);
}
case Id_toString:
return getSelf(thisObj).toString();
case Id_valueOf:
case SymbolId_toPrimitive:
return getSelf(thisObj).js_valueOf();
default:
return super.execIdCall(f, cx, scope, thisObj, args);
}
}
private NativeSymbol getSelf(Object thisObj) {
try {
return (NativeSymbol)thisObj;
} catch (ClassCastException cce) {
throw ScriptRuntime.typeError1("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(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.
@Override
public void put(String name, Scriptable start, Object value)
{
if (!isSymbol()) {
super.put(name, start, value);
} else if (Context.getCurrentContext().isStrictMode()) {
throw ScriptRuntime.typeError0("msg.no.assign.symbol.strict");
}
}
@Override
public void put(int index, Scriptable start, Object value)
{
if (!isSymbol()) {
super.put(index, start, value);
} else if (Context.getCurrentContext().isStrictMode()) {
throw ScriptRuntime.typeError0("msg.no.assign.symbol.strict");
}
}
@Override
public void put(Symbol key, Scriptable start, Object value)
{
if (!isSymbol()) {
super.put(key, start, value);
} else if (Context.getCurrentContext().isStrictMode()) {
throw ScriptRuntime.typeError0("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;
}
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;
}
}