/*
 * Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.objects;

import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.JSType.isString;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.SwitchPoint;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.lookup.Lookup;
import jdk.nashorn.internal.objects.annotations.Attribute;
import jdk.nashorn.internal.objects.annotations.Getter;
import jdk.nashorn.internal.objects.annotations.Property;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.objects.annotations.Setter;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ECMAErrors;
import jdk.nashorn.internal.runtime.FindProperty;
import jdk.nashorn.internal.runtime.GlobalConstants;
import jdk.nashorn.internal.runtime.GlobalFunctions;
import jdk.nashorn.internal.runtime.JSType;
import jdk.nashorn.internal.runtime.NativeJavaPackage;
import jdk.nashorn.internal.runtime.PropertyDescriptor;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.Scope;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.ScriptRuntime;
import jdk.nashorn.internal.runtime.ScriptingFunctions;
import jdk.nashorn.internal.runtime.Specialization;
import jdk.nashorn.internal.runtime.arrays.ArrayData;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.linker.InvokeByName;
import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
import jdk.nashorn.internal.runtime.regexp.RegExpResult;
import jdk.nashorn.internal.scripts.JD;
import jdk.nashorn.internal.scripts.JO;
import jdk.nashorn.tools.ShellFunctions;

/**
 * Representation of global scope.
 */
@ScriptClass("Global")
public final class Global extends Scope {
    // This special value is used to flag a lazily initialized global property.
    private static final Object LAZY_SENTINEL = new Object();
    // This serves as placeholder value used in place of a location property (__FILE__, __DIR__, __LINE__)
    private static final Object LOCATION_PLACEHOLDER = new Object();

    private final InvokeByName TO_STRING = new InvokeByName("toString", ScriptObject.class);
    private final InvokeByName VALUE_OF  = new InvokeByName("valueOf",  ScriptObject.class);

    /**
     * Optimistic builtin names that require switchpoint invalidation
     * upon assignment. Overly conservative, but works for now, to avoid
     * any complicated scope checks and especially heavy weight guards
     * like
     *
     * <pre>
     *     public boolean setterGuard(final Object receiver) {
     *         final Global          global = Global.instance();
     *         final ScriptObject    sobj   = global.getFunctionPrototype();
     *         final Object          apply  = sobj.get("apply");
     *         return apply == receiver;
     *     }
     * </pre>
     *
     * Naturally, checking for builtin classes like NativeFunction is cheaper,
     * it's when you start adding property checks for said builtins you have
     * problems with guard speed.
     */

    /** Nashorn extension: arguments array */
    @Property(attributes = Attribute.NOT_ENUMERABLE | Attribute.NOT_CONFIGURABLE)
    public Object arguments;

    /** ECMA 15.1.2.2 parseInt (string , radix) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object parseInt;

    /** ECMA 15.1.2.3 parseFloat (string) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object parseFloat;

    /** ECMA 15.1.2.4 isNaN (number) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object isNaN;

    /** ECMA 15.1.2.5 isFinite (number) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object isFinite;

    /** ECMA 15.1.3.3 encodeURI */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object encodeURI;

    /** ECMA 15.1.3.4 encodeURIComponent */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object encodeURIComponent;

    /** ECMA 15.1.3.1 decodeURI */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object decodeURI;

    /** ECMA 15.1.3.2 decodeURIComponent */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object decodeURIComponent;

    /** ECMA B.2.1 escape (string) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object escape;

    /** ECMA B.2.2 unescape (string) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object unescape;

    /** Nashorn extension: global.print */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object print;

    /** Nashorn extension: global.load */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object load;

    /** Nashorn extension: global.loadWithNewGlobal */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object loadWithNewGlobal;

    /** Nashorn extension: global.exit */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object exit;

    /** Nashorn extension: global.quit */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object quit;

    /** Value property NaN of the Global Object - ECMA 15.1.1.1 NaN */
    @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
    public static final double NaN = Double.NaN;

    /** Value property Infinity of the Global Object - ECMA 15.1.1.2 Infinity */
    @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
    public static final double Infinity = Double.POSITIVE_INFINITY;

    /** Value property Undefined of the Global Object - ECMA 15.1.1.3 Undefined */
    @Property(attributes = Attribute.NON_ENUMERABLE_CONSTANT)
    public static final Object undefined = UNDEFINED;

    /** ECMA 15.1.2.1 eval(x) */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public Object eval;

    /** ECMA 15.1.4.1 Object constructor. */
    @Property(name = "Object", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object object;

    /** ECMA 15.1.4.2 Function constructor. */
    @Property(name = "Function", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object function;

    /** ECMA 15.1.4.3 Array constructor. */
    @Property(name = "Array", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object array;

    /** ECMA 15.1.4.4 String constructor */
    @Property(name = "String", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object string;

    /** ECMA 15.1.4.5 Boolean constructor */
    @Property(name = "Boolean", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object _boolean;

    /** ECMA 15.1.4.6 - Number constructor */
    @Property(name = "Number", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object number;

    /**
     * Getter for ECMA 15.1.4.7 Date property
     *
     * @param self self reference
     * @return Date property value
     */
    @Getter(name = "Date", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getDate(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.date == LAZY_SENTINEL) {
            global.date = global.getBuiltinDate();
        }
        return global.date;
    }

    /**
     * Setter for ECMA 15.1.4.7 Date property
     *
     * @param self self reference
     * @param value value for the Date property
     */
    @Setter(name = "Date", attributes = Attribute.NOT_ENUMERABLE)
    public static void setDate(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.date = value;
    }

    private volatile Object date = LAZY_SENTINEL;

    /**
     * Getter for ECMA 15.1.4.8 RegExp property
     *
     * @param self self reference
     * @return RegExp property value
     */
    @Getter(name = "RegExp", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getRegExp(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.regexp == LAZY_SENTINEL) {
            global.regexp = global.getBuiltinRegExp();
        }
        return global.regexp;
    }

    /**
     * Setter for ECMA 15.1.4.8 RegExp property
     *
     * @param self self reference
     * @param value value for the RegExp property
     */
    @Setter(name = "RegExp", attributes = Attribute.NOT_ENUMERABLE)
    public static void setRegExp(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.regexp = value;
    }

    private volatile Object regexp = LAZY_SENTINEL;

    /**
     * Getter for ECMA 15.12 - The JSON property
     * @param self self reference
     * @return the value of JSON property
     */
    @Getter(name = "JSON", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getJSON(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.json == LAZY_SENTINEL) {
            global.json = global.getBuiltinJSON();
        }
        return global.json;
    }

    /**
     * Setter for ECMA 15.12 - The JSON property
     * @param self self reference
     * @param value value for the JSON property
     */
    @Setter(name = "JSON", attributes = Attribute.NOT_ENUMERABLE)
    public static void setJSON(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.json = value;
    }

    private volatile Object json = LAZY_SENTINEL;

    /**
     * Getter for Nashorn extension: global.JSAdapter
     * @param self self reference
     * @return value of the JSAdapter property
     */
    @Getter(name = "JSAdapter", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getJSAdapter(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.jsadapter == LAZY_SENTINEL) {
            global.jsadapter = global.getBuiltinJSAdapter();
        }
        return global.jsadapter;
    }

    /**
     * Setter for Nashorn extension: global.JSAdapter
     * @param self self reference
     * @param value value for the JSAdapter property
     */
    @Setter(name = "JSAdapter", attributes = Attribute.NOT_ENUMERABLE)
    public static void setJSAdapter(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.jsadapter = value;
    }

    private volatile Object jsadapter = LAZY_SENTINEL;

    /** ECMA 15.8 - The Math object */
    @Property(name = "Math", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object math;

    /** Error object */
    @Property(name = "Error", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object error;

    /**
     * Getter for the EvalError property
     * @param self self reference
     * @return the value of EvalError property
     */
    @Getter(name = "EvalError", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getEvalError(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.evalError == LAZY_SENTINEL) {
            global.evalError = global.getBuiltinEvalError();
        }
        return global.evalError;
    }

    /**
     * Setter for the EvalError property
     * @param self self reference
     * @param value value of the EvalError property
     */
    @Setter(name = "EvalError", attributes = Attribute.NOT_ENUMERABLE)
    public static void setEvalError(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.evalError = value;
    }

    private volatile Object evalError = LAZY_SENTINEL;

    /**
     * Getter for the RangeError property.
     * @param self self reference
     * @return the value of RangeError property
     */
    @Getter(name = "RangeError", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getRangeError(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.rangeError == LAZY_SENTINEL) {
            global.rangeError = global.getBuiltinRangeError();
        }
        return global.rangeError;
    }


    /**
     * Setter for the RangeError property.
     * @param self self reference
     * @param value value for the RangeError property
     */
    @Setter(name = "RangeError", attributes = Attribute.NOT_ENUMERABLE)
    public static void setRangeError(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.rangeError = value;
    }

    private volatile Object rangeError = LAZY_SENTINEL;

    /** ReferenceError object */
    @Property(name = "ReferenceError", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object referenceError;

    /** SyntaxError object */
    @Property(name = "SyntaxError", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object syntaxError;

    /** TypeError object */
    @Property(name = "TypeError", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object typeError;

    /**
     * Getter for the URIError property.
     * @param self self reference
     * @return the value of URIError property
     */
    @Getter(name = "URIError", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getURIError(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.uriError == LAZY_SENTINEL) {
            global.uriError = global.getBuiltinURIError();
        }
        return global.uriError;
    }

    /**
     * Setter for the URIError property.
     * @param self self reference
     * @param value value for the URIError property
     */
    @Setter(name = "URIError", attributes = Attribute.NOT_ENUMERABLE)
    public static void setURIError(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.uriError = value;
    }

    private volatile Object uriError = LAZY_SENTINEL;

    /**
     * Getter for the ArrayBuffer property.
     * @param self self reference
     * @return the value of the ArrayBuffer property
     */
    @Getter(name = "ArrayBuffer", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getArrayBuffer(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.arrayBuffer == LAZY_SENTINEL) {
            global.arrayBuffer = global.getBuiltinArrayBuffer();
        }
        return global.arrayBuffer;
    }

    /**
     * Setter for the ArrayBuffer property.
     * @param self self reference
     * @param value value of the ArrayBuffer property
     */
    @Setter(name = "ArrayBuffer", attributes = Attribute.NOT_ENUMERABLE)
    public static void setArrayBuffer(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.arrayBuffer = value;
    }

    private volatile Object arrayBuffer;

    /**
     * Getter for the DataView property.
     * @param self self reference
     * @return the value of the DataView property
     */
    @Getter(name = "DataView", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getDataView(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.dataView == LAZY_SENTINEL) {
            global.dataView = global.getBuiltinDataView();
        }
        return global.dataView;
    }


    /**
     * Setter for the DataView property.
     * @param self self reference
     * @param value value of the DataView property
     */
    @Setter(name = "DataView", attributes = Attribute.NOT_ENUMERABLE)
    public static void setDataView(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.dataView = value;
    }

    private volatile Object dataView;

    /**
     * Getter for the Int8Array property.
     * @param self self reference
     * @return the value of the Int8Array property.
     */
    @Getter(name = "Int8Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getInt8Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.int8Array == LAZY_SENTINEL) {
            global.int8Array = global.getBuiltinInt8Array();
        }
        return global.int8Array;
    }

    /**
     * Setter for the Int8Array property.
     * @param self self reference
     * @param value value of the Int8Array property
     */
    @Setter(name = "Int8Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setInt8Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.int8Array = value;
    }

    private volatile Object int8Array;

    /**
     * Getter for the Uin8Array property.
     * @param self self reference
     * @return the value of the Uint8Array property
     */
    @Getter(name = "Uint8Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getUint8Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.uint8Array == LAZY_SENTINEL) {
            global.uint8Array = global.getBuiltinUint8Array();
        }
        return global.uint8Array;
    }

    /**
     * Setter for the Uin8Array property.
     * @param self self reference
     * @param value value of the Uin8Array property
     */
    @Setter(name = "Uint8Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setUint8Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.uint8Array = value;
    }

    private volatile Object uint8Array;

    /**
     * Getter for the Uint8ClampedArray property.
     * @param self self reference
     * @return the value of the Uint8ClampedArray property
     */
    @Getter(name = "Uint8ClampedArray", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getUint8ClampedArray(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.uint8ClampedArray == LAZY_SENTINEL) {
            global.uint8ClampedArray = global.getBuiltinUint8ClampedArray();
        }
        return global.uint8ClampedArray;
    }

    /**
     * Setter for the Uint8ClampedArray property.
     * @param self self reference
     * @param value value of the Uint8ClampedArray property
     */
    @Setter(name = "Uint8ClampedArray", attributes = Attribute.NOT_ENUMERABLE)
    public static void setUint8ClampedArray(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.uint8ClampedArray = value;
    }

    private volatile Object uint8ClampedArray;

    /**
     * Getter for the Int16Array property.
     * @param self self reference
     * @return the value of the Int16Array property
     */
    @Getter(name = "Int16Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getInt16Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.int16Array == LAZY_SENTINEL) {
            global.int16Array = global.getBuiltinInt16Array();
        }
        return global.int16Array;
    }

    /**
     * Setter for the Int16Array property.
     * @param self self reference
     * @param value value of the Int16Array property
     */
    @Setter(name = "Int16Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setInt16Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.int16Array = value;
    }

    private volatile Object int16Array;

    /**
     * Getter for the Uint16Array property.
     * @param self self reference
     * @return the value of the Uint16Array property
     */
    @Getter(name = "Uint16Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getUint16Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.uint16Array == LAZY_SENTINEL) {
            global.uint16Array = global.getBuiltinUint16Array();
        }
        return global.uint16Array;
    }

    /**
     * Setter for the Uint16Array property.
     * @param self self reference
     * @param value value of the Uint16Array property
     */
    @Setter(name = "Uint16Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setUint16Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.uint16Array = value;
    }

    private volatile Object uint16Array;

    /**
     * Getter for the Int32Array property.
     *
     * @param self self reference
     * @return the value of the Int32Array property
     */
    @Getter(name = "Int32Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getInt32Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.int32Array == LAZY_SENTINEL) {
            global.int32Array = global.getBuiltinInt32Array();
        }
        return global.int32Array;
    }


    /**
     * Setter for the Int32Array property.
     *
     * @param self self reference
     * @param value value of the Int32Array property
     */
    @Setter(name = "Int32Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setInt32Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.int32Array = value;
    }

    private volatile Object int32Array;

    /**
     * Getter of the Uint32Array property.
     *
     * @param self self reference
     * @return the value of the Uint32Array property
     */
    @Getter(name = "Uint32Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getUint32Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.uint32Array == LAZY_SENTINEL) {
            global.uint32Array = global.getBuiltinUint32Array();
        }
        return global.uint32Array;
    }


    /**
     * Setter of the Uint32Array property.
     *
     * @param self self reference
     * @param value value of the Uint32Array property
     */
    @Setter(name = "Uint32Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setUint32Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.uint32Array = value;
    }

    private volatile Object uint32Array;

    /**
     * Getter for the Float32Array property.
     *
     * @param self self reference
     * @return the value of the Float32Array property
     */
    @Getter(name = "Float32Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getFloat32Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.float32Array == LAZY_SENTINEL) {
            global.float32Array = global.getBuiltinFloat32Array();
        }
        return global.float32Array;
    }

    /**
     * Setter for the Float32Array property.
     *
     * @param self self reference
     * @param value value of the Float32Array property
     */
    @Setter(name = "Float32Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setFloat32Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.float32Array = value;
    }

    private volatile Object float32Array;

    /**
     * Getter for the Float64Array property.
     *
     * @param self self reference
     * @return the value of the Float64Array property
     */
    @Getter(name = "Float64Array", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getFloat64Array(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.float64Array == LAZY_SENTINEL) {
            global.float64Array = global.getBuiltinFloat64Array();
        }
        return global.float64Array;
    }

    /**
     * Setter for the Float64Array property.
     *
     * @param self self reference
     * @param value value of the Float64Array property
     */
    @Setter(name = "Float64Array", attributes = Attribute.NOT_ENUMERABLE)
    public static void setFloat64Array(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.float64Array = value;
    }

    private volatile Object float64Array;

    /** Nashorn extension: Java access - global.Packages */
    @Property(name = "Packages", attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object packages;

    /** Nashorn extension: Java access - global.com */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object com;

    /** Nashorn extension: Java access - global.edu */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object edu;

    /** Nashorn extension: Java access - global.java */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object java;

    /** Nashorn extension: Java access - global.javafx */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object javafx;

    /** Nashorn extension: Java access - global.javax */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object javax;

    /** Nashorn extension: Java access - global.org */
    @Property(attributes = Attribute.NOT_ENUMERABLE)
    public volatile Object org;

    /**
     * Getter for the Nashorn extension: Java access - global.JavaImporter.
     *
     * @param self self reference
     * @return the value of the JavaImporter property
     */
    @Getter(name = "JavaImporter", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getJavaImporter(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.javaImporter == LAZY_SENTINEL) {
            global.javaImporter = global.getBuiltinJavaImporter();
        }
        return global.javaImporter;
    }

    /**
     * Setter for the Nashorn extension: Java access - global.JavaImporter.
     *
     * @param self self reference
     * @param value value of the JavaImporter property
     */
    @Setter(name = "JavaImporter", attributes = Attribute.NOT_ENUMERABLE)
    public static void setJavaImporter(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.javaImporter = value;
    }

    private volatile Object javaImporter;

    /**
     * Getter for the Nashorn extension: global.Java property.
     *
     * @param self self reference
     * @return the value of the Java property
     */
    @Getter(name = "Java", attributes = Attribute.NOT_ENUMERABLE)
    public static Object getJavaApi(final Object self) {
        final Global global = Global.instanceFrom(self);
        if (global.javaApi == LAZY_SENTINEL) {
            global.javaApi = global.getBuiltinJavaApi();
        }
        return global.javaApi;
    }

    /**
     * Setter for the Nashorn extension: global.Java property.
     *
     * @param self self reference
     * @param value value of the Java property
     */
    @Setter(name = "Java", attributes = Attribute.NOT_ENUMERABLE)
    public static void setJavaApi(final Object self, final Object value) {
        final Global global = Global.instanceFrom(self);
        global.javaApi = value;
    }

    private volatile Object javaApi;

    /** Nashorn extension: current script's file name */
    @Property(name = "__FILE__", attributes = Attribute.NON_ENUMERABLE_CONSTANT)
    public static final Object __FILE__ = LOCATION_PLACEHOLDER;

    /** Nashorn extension: current script's directory */
    @Property(name = "__DIR__", attributes = Attribute.NON_ENUMERABLE_CONSTANT)
    public static final Object __DIR__ = LOCATION_PLACEHOLDER;

    /** Nashorn extension: current source line number being executed */
    @Property(name = "__LINE__", attributes = Attribute.NON_ENUMERABLE_CONSTANT)
    public static final Object __LINE__ = LOCATION_PLACEHOLDER;

    private volatile NativeDate DEFAULT_DATE;

    /** Used as Date.prototype's default value */
    NativeDate getDefaultDate() {
        return DEFAULT_DATE;
    }

    private volatile NativeRegExp DEFAULT_REGEXP;

    /** Used as RegExp.prototype's default value */
    NativeRegExp getDefaultRegExp() {
        return DEFAULT_REGEXP;
    }

    /*
     * Built-in constructor objects: Even if user changes dynamic values of
     * "Object", "Array" etc., we still want to keep original values of these
     * constructors here. For example, we need to be able to create array,
     * regexp literals even after user overwrites global "Array" or "RegExp"
     * constructor - see also ECMA 262 spec. Annex D.
     */
    private ScriptFunction builtinFunction;
    private ScriptFunction builtinObject;
    private ScriptFunction builtinArray;
    private ScriptFunction builtinBoolean;
    private ScriptFunction builtinDate;
    private ScriptObject   builtinJSON;
    private ScriptFunction builtinJSAdapter;
    private ScriptObject   builtinMath;
    private ScriptFunction builtinNumber;
    private ScriptFunction builtinRegExp;
    private ScriptFunction builtinString;
    private ScriptFunction builtinError;
    private ScriptFunction builtinEval;
    private ScriptFunction builtinEvalError;
    private ScriptFunction builtinRangeError;
    private ScriptFunction builtinReferenceError;
    private ScriptFunction builtinSyntaxError;
    private ScriptFunction builtinTypeError;
    private ScriptFunction builtinURIError;
    private ScriptObject   builtinPackages;
    private ScriptObject   builtinCom;
    private ScriptObject   builtinEdu;
    private ScriptObject   builtinJava;
    private ScriptObject   builtinJavafx;
    private ScriptObject   builtinJavax;
    private ScriptObject   builtinOrg;
    private ScriptFunction builtinJavaImporter;
    private ScriptObject   builtinJavaApi;
    private ScriptFunction builtinArrayBuffer;
    private ScriptFunction builtinDataView;
    private ScriptFunction builtinInt8Array;
    private ScriptFunction builtinUint8Array;
    private ScriptFunction builtinUint8ClampedArray;
    private ScriptFunction builtinInt16Array;
    private ScriptFunction builtinUint16Array;
    private ScriptFunction builtinInt32Array;
    private ScriptFunction builtinUint32Array;
    private ScriptFunction builtinFloat32Array;
    private ScriptFunction builtinFloat64Array;

    /*
     * ECMA section 13.2.3 The [[ThrowTypeError]] Function Object
     */
    private ScriptFunction typeErrorThrower;

    // Used to store the last RegExp result to support deprecated RegExp constructor properties
    private RegExpResult lastRegExpResult;

    private static final MethodHandle EVAL                 = findOwnMH_S("eval",                Object.class, Object.class, Object.class);
    private static final MethodHandle NO_SUCH_PROPERTY     = findOwnMH_S(NO_SUCH_PROPERTY_NAME, Object.class, Object.class, Object.class);
    private static final MethodHandle PRINT                = findOwnMH_S("print",               Object.class, Object.class, Object[].class);
    private static final MethodHandle PRINTLN              = findOwnMH_S("println",             Object.class, Object.class, Object[].class);
    private static final MethodHandle LOAD                 = findOwnMH_S("load",                Object.class, Object.class, Object.class);
    private static final MethodHandle LOAD_WITH_NEW_GLOBAL = findOwnMH_S("loadWithNewGlobal",   Object.class, Object.class, Object[].class);
    private static final MethodHandle EXIT                 = findOwnMH_S("exit",                Object.class, Object.class, Object.class);
    private static final MethodHandle LEXICAL_SCOPE_FILTER = findOwnMH_S("lexicalScopeFilter",  Object.class, Object.class);

    // initialized by nasgen
    private static PropertyMap $nasgenmap$;

    // context to which this global belongs to
    private final Context context;

    // current ScriptContext to use - can be null.
    private ThreadLocal<ScriptContext> scontext;
    // current ScriptEngine associated - can be null.
    private ScriptEngine engine;
    // initial ScriptContext - usually null and only used for special case
    private volatile ScriptContext initscontext;

    // ES6 global lexical scope.
    private final LexicalScope lexicalScope;

    // Switchpoint for non-constant global callsites in the presence of ES6 lexical scope.
    private SwitchPoint lexicalScopeSwitchPoint;

    /**
     * Set the current script context
     * @param ctxt script context
     */
    public void setScriptContext(final ScriptContext ctxt) {
        assert scontext != null;
        scontext.set(ctxt);
    }

    /**
     * Get the current script context
     * @return current script context
     */
    public ScriptContext getScriptContext() {
        assert scontext != null;
        return scontext.get();
    }

    /**
     * Set the initial script context
     * @param ctxt initial script context
     */
    public void setInitScriptContext(final ScriptContext ctxt) {
        this.initscontext = ctxt;
    }

    private ScriptContext currentContext() {
        final ScriptContext sc = scontext != null? scontext.get() : null;
        if (sc != null) {
            return sc;
        } else if (initscontext != null) {
            return initscontext;
        }
        return engine != null? engine.getContext() : null;
    }

    @Override
    protected Context getContext() {
        return context;
    }

    @Override
    protected boolean useDualFields() {
        return context.useDualFields();
    }

    // performs initialization checks for Global constructor and returns the
    // PropertyMap, if everything is fine.
    private static PropertyMap checkAndGetMap(final Context context) {
        // security check first
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission(Context.NASHORN_CREATE_GLOBAL));
        }

        Objects.requireNonNull(context);

        return $nasgenmap$;
    }

    /**
     * Constructor
     *
     * @param context the context
     */
    public Global(final Context context) {
        super(checkAndGetMap(context));
        this.context = context;
        this.lexicalScope = context.getEnv()._es6 ? new LexicalScope(this) : null;
    }

    /**
     * Script access to "current" Global instance
     *
     * @return the global singleton
     */
    public static Global instance() {
        return Objects.requireNonNull(Context.getGlobal());
    }

    private static Global instanceFrom(final Object self) {
        return self instanceof Global? (Global)self : instance();
    }

    /**
     * Check if we have a Global instance
     * @return true if one exists
     */
    public static boolean hasInstance() {
        return Context.getGlobal() != null;
    }

    /**
     * Script access to {@link ScriptEnvironment}
     *
     * @return the script environment
     */
    static ScriptEnvironment getEnv() {
        return instance().getContext().getEnv();
    }

    /**
     * Script access to {@link Context}
     *
     * @return the context
     */
    static Context getThisContext() {
        return instance().getContext();
    }

    // Runtime interface to Global

    /**
     * Is there a class filter in the current Context?
     * @return class filter
     */
    public ClassFilter getClassFilter() {
        return context.getClassFilter();
    }

    /**
     * Is this global of the given Context?
     * @param ctxt the context
     * @return true if this global belongs to the given Context
     */
    public boolean isOfContext(final Context ctxt) {
        return this.context == ctxt;
    }

    /**
     * Does this global belong to a strict Context?
     * @return true if this global belongs to a strict Context
     */
    public boolean isStrictContext() {
        return context.getEnv()._strict;
    }

    /**
     * Initialize standard builtin objects like "Object", "Array", "Function" etc.
     * as well as our extension builtin objects like "Java", "JSAdapter" as properties
     * of the global scope object.
     *
     * @param eng ScriptEngine to initialize
     */
    public void initBuiltinObjects(final ScriptEngine eng) {
        if (this.builtinObject != null) {
            // already initialized, just return
            return;
        }

        this.engine = eng;
        if (this.engine != null) {
            this.scontext = new ThreadLocal<>();
        }
        init(eng);
    }

    /**
     * Wrap a Java object as corresponding script object
     *
     * @param obj object to wrap
     * @return    wrapped object
     */
    public Object wrapAsObject(final Object obj) {
        if (obj instanceof Boolean) {
            return new NativeBoolean((Boolean)obj, this);
        } else if (obj instanceof Number) {
            return new NativeNumber(((Number)obj).doubleValue(), this);
        } else if (isString(obj)) {
            return new NativeString((CharSequence)obj, this);
        } else if (obj instanceof Object[]) { // extension
            return new NativeArray(ArrayData.allocate((Object[])obj), this);
        } else if (obj instanceof double[]) { // extension
            return new NativeArray(ArrayData.allocate((double[])obj), this);
        } else if (obj instanceof int[]) {
            return new NativeArray(ArrayData.allocate((int[]) obj), this);
        } else if (obj instanceof ArrayData) {
            return new NativeArray((ArrayData) obj, this);
        } else {
            // FIXME: more special cases? Map? List?
            return obj;
        }
    }

    /**
     * Lookup helper for JS primitive types
     *
     * @param request the link request for the dynamic call site.
     * @param self     self reference
     *
     * @return guarded invocation
     */
    public static GuardedInvocation primitiveLookup(final LinkRequest request, final Object self) {
        if (isString(self)) {
            return NativeString.lookupPrimitive(request, self);
        } else if (self instanceof Number) {
            return NativeNumber.lookupPrimitive(request, self);
        } else if (self instanceof Boolean) {
            return NativeBoolean.lookupPrimitive(request, self);
        }
        throw new IllegalArgumentException("Unsupported primitive: " + self);
    }

    /**
     * Returns a method handle that creates a wrapper object for a JS primitive value.
     *
     * @param self receiver object
     * @return method handle to create wrapper objects for primitive receiver
     */
    public static MethodHandle getPrimitiveWrapFilter(final Object self) {
        if (isString(self)) {
            return NativeString.WRAPFILTER;
        } else if (self instanceof Number) {
            return NativeNumber.WRAPFILTER;
        } else if (self instanceof Boolean) {
            return NativeBoolean.WRAPFILTER;
        }
        throw new IllegalArgumentException("Unsupported primitive: " + self);
    }


    /**
     * Create a new empty script object
     *
     * @return the new ScriptObject
     */
    public ScriptObject newObject() {
        return useDualFields() ? new JD(getObjectPrototype()) : new JO(getObjectPrototype());
    }

    /**
     * Default value of given type
     *
     * @param sobj     script object
     * @param typeHint type hint
     *
     * @return default value
     */
    public Object getDefaultValue(final ScriptObject sobj, final Class<?> typeHint) {
        // When the [[DefaultValue]] internal method of O is called with no hint,
        // then it behaves as if the hint were Number, unless O is a Date object
        // in which case it behaves as if the hint were String.
        Class<?> hint = typeHint;
        if (hint == null) {
            hint = Number.class;
        }

        try {
            if (hint == String.class) {

                final Object toString = TO_STRING.getGetter().invokeExact(sobj);

                if (Bootstrap.isCallable(toString)) {
                    final Object value = TO_STRING.getInvoker().invokeExact(toString, sobj);
                    if (JSType.isPrimitive(value)) {
                        return value;
                    }
                }

                final Object valueOf = VALUE_OF.getGetter().invokeExact(sobj);
                if (Bootstrap.isCallable(valueOf)) {
                    final Object value = VALUE_OF.getInvoker().invokeExact(valueOf, sobj);
                    if (JSType.isPrimitive(value)) {
                        return value;
                    }
                }
                throw typeError(this, "cannot.get.default.string");
            }

            if (hint == Number.class) {
                final Object valueOf = VALUE_OF.getGetter().invokeExact(sobj);
                if (Bootstrap.isCallable(valueOf)) {
                    final Object value = VALUE_OF.getInvoker().invokeExact(valueOf, sobj);
                    if (JSType.isPrimitive(value)) {
                        return value;
                    }
                }

                final Object toString = TO_STRING.getGetter().invokeExact(sobj);
                if (Bootstrap.isCallable(toString)) {
                    final Object value = TO_STRING.getInvoker().invokeExact(toString, sobj);
                    if (JSType.isPrimitive(value)) {
                        return value;
                    }
                }

                throw typeError(this, "cannot.get.default.number");
            }
        } catch (final RuntimeException | Error e) {
            throw e;
        } catch (final Throwable t) {
            throw new RuntimeException(t);
        }

        return UNDEFINED;
    }

    /**
     * Is the given ScriptObject an ECMAScript Error object?
     *
     * @param sobj the object being checked
     * @return true if sobj is an Error object
     */
    public boolean isError(final ScriptObject sobj) {
        final ScriptObject errorProto = getErrorPrototype();
        ScriptObject proto = sobj.getProto();
        while (proto != null) {
            if (proto == errorProto) {
                return true;
            }
            proto = proto.getProto();
        }
        return false;
    }

    /**
     * Create a new ECMAScript Error object.
     *
     * @param msg error message
     * @return newly created Error object
     */
    public ScriptObject newError(final String msg) {
        return new NativeError(msg, this);
    }

    /**
     * Create a new ECMAScript EvalError object.
     *
     * @param msg error message
     * @return newly created EvalError object
     */
    public ScriptObject newEvalError(final String msg) {
        return new NativeEvalError(msg, this);
    }

    /**
     * Create a new ECMAScript RangeError object.
     *
     * @param msg error message
     * @return newly created RangeError object
     */
    public ScriptObject newRangeError(final String msg) {
        return new NativeRangeError(msg, this);
    }

    /**
     * Create a new ECMAScript ReferenceError object.
     *
     * @param msg error message
     * @return newly created ReferenceError object
     */
    public ScriptObject newReferenceError(final String msg) {
        return new NativeReferenceError(msg, this);
    }

    /**
     * Create a new ECMAScript SyntaxError object.
     *
     * @param msg error message
     * @return newly created SyntaxError object
     */
    public ScriptObject newSyntaxError(final String msg) {
        return new NativeSyntaxError(msg, this);
    }

    /**
     * Create a new ECMAScript TypeError object.
     *
     * @param msg error message
     * @return newly created TypeError object
     */
    public ScriptObject newTypeError(final String msg) {
        return new NativeTypeError(msg, this);
    }

    /**
     * Create a new ECMAScript URIError object.
     *
     * @param msg error message
     * @return newly created URIError object
     */
    public ScriptObject newURIError(final String msg) {
        return new NativeURIError(msg, this);
    }

    /**
     * Create a new ECMAScript GenericDescriptor object.
     *
     * @param configurable is the property configurable?
     * @param enumerable is the property enumerable?
     * @return newly created GenericDescriptor object
     */
    public PropertyDescriptor newGenericDescriptor(final boolean configurable, final boolean enumerable) {
        return new GenericPropertyDescriptor(configurable, enumerable, this);
    }

    /**
     * Create a new ECMAScript DatePropertyDescriptor object.
     *
     * @param value of the data property
     * @param configurable is the property configurable?
     * @param enumerable is the property enumerable?
     * @param writable is the property writable?
     * @return newly created DataPropertyDescriptor object
     */
    public PropertyDescriptor newDataDescriptor(final Object value, final boolean configurable, final boolean enumerable, final boolean writable) {
        return new DataPropertyDescriptor(configurable, enumerable, writable, value, this);
    }

    /**
     * Create a new ECMAScript AccessorPropertyDescriptor object.
     *
     * @param get getter function of the user accessor property
     * @param set setter function of the user accessor property
     * @param configurable is the property configurable?
     * @param enumerable is the property enumerable?
     * @return newly created AccessorPropertyDescriptor object
     */
    public PropertyDescriptor newAccessorDescriptor(final Object get, final Object set, final boolean configurable, final boolean enumerable) {
        final AccessorPropertyDescriptor desc = new AccessorPropertyDescriptor(configurable, enumerable, get == null ? UNDEFINED : get, set == null ? UNDEFINED : set, this);

        if (get == null) {
            desc.delete(PropertyDescriptor.GET, false);
        }

        if (set == null) {
            desc.delete(PropertyDescriptor.SET, false);
        }

        return desc;
    }

    private static <T> T getLazilyCreatedValue(final Object key, final Callable<T> creator, final Map<Object, T> map) {
        final T obj = map.get(key);
        if (obj != null) {
            return obj;
        }

        try {
            final T newObj = creator.call();
            final T existingObj = map.putIfAbsent(key, newObj);
            return existingObj != null ? existingObj : newObj;
        } catch (final Exception exp) {
            throw new RuntimeException(exp);
        }
    }

    private final Map<Object, InvokeByName> namedInvokers = new ConcurrentHashMap<>();


    /**
     * Get cached InvokeByName object for the given key
     * @param key key to be associated with InvokeByName object
     * @param creator if InvokeByName is absent 'creator' is called to make one (lazy init)
     * @return InvokeByName object associated with the key.
     */
    public InvokeByName getInvokeByName(final Object key, final Callable<InvokeByName> creator) {
        return getLazilyCreatedValue(key, creator, namedInvokers);
    }

    private final Map<Object, MethodHandle> dynamicInvokers = new ConcurrentHashMap<>();

    /**
     * Get cached dynamic method handle for the given key
     * @param key key to be associated with dynamic method handle
     * @param creator if method handle is absent 'creator' is called to make one (lazy init)
     * @return dynamic method handle associated with the key.
     */
    public MethodHandle getDynamicInvoker(final Object key, final Callable<MethodHandle> creator) {
        return getLazilyCreatedValue(key, creator, dynamicInvokers);
    }

    /**
     * Hook to search missing variables in ScriptContext if available
     * @param self used to detect if scope call or not (this function is 'strict')
     * @param name name of the variable missing
     * @return value of the missing variable or undefined (or TypeError for scope search)
     */
    public static Object __noSuchProperty__(final Object self, final Object name) {
        final Global global = Global.instance();
        final ScriptContext sctxt = global.currentContext();
        final String nameStr = name.toString();

        if (sctxt != null) {
            final int scope = sctxt.getAttributesScope(nameStr);
            if (scope != -1) {
                return ScriptObjectMirror.unwrap(sctxt.getAttribute(nameStr, scope), global);
            }
        }

        if ("context".equals(nameStr)) {
            return sctxt;
        } else if ("engine".equals(nameStr)) {
            // expose "engine" variable only when there is no security manager
            // or when no class filter is set.
            if (System.getSecurityManager() == null || global.getClassFilter() == null) {
                return global.engine;
            }
        }

        if (self == UNDEFINED) {
            // scope access and so throw ReferenceError
            throw referenceError(global, "not.defined", nameStr);
        }

        return UNDEFINED;
    }

    /**
     * This is the eval used when 'indirect' eval call is made.
     *
     * var global = this;
     * global.eval("print('hello')");
     *
     * @param self  eval scope
     * @param str   eval string
     *
     * @return the result of eval
     */
    public static Object eval(final Object self, final Object str) {
        return directEval(self, str, Global.instanceFrom(self), UNDEFINED, false);
    }

    /**
     * Direct eval
     *
     * @param self     The scope of eval passed as 'self'
     * @param str      Evaluated code
     * @param callThis "this" to be passed to the evaluated code
     * @param location location of the eval call
     * @param strict   is eval called from a strict mode code?
     *
     * @return the return value of the eval
     *
     * This is directly invoked from generated when eval(code) is called in user code
     */
    public static Object directEval(final Object self, final Object str, final Object callThis, final Object location, final boolean strict) {
        if (!isString(str)) {
            return str;
        }
        final Global global = Global.instanceFrom(self);
        final ScriptObject scope = self instanceof ScriptObject && ((ScriptObject)self).isScope() ? (ScriptObject)self : global;

        return global.getContext().eval(scope, str.toString(), callThis, location, strict, true);
    }

    /**
     * Global print implementation - Nashorn extension
     *
     * @param self    scope
     * @param objects arguments to print
     *
     * @return result of print (undefined)
     */
    public static Object print(final Object self, final Object... objects) {
        return Global.instanceFrom(self).printImpl(false, objects);
    }

    /**
     * Global println implementation - Nashorn extension
     *
     * @param self    scope
     * @param objects arguments to print
     *
     * @return result of println (undefined)
     */
    public static Object println(final Object self, final Object... objects) {
        return Global.instanceFrom(self).printImpl(true, objects);
    }

    /**
     * Global load implementation - Nashorn extension.
     *
     * <p>
     * load builtin loads the given script. Script source can be a URL or a File
     * or a script object with name and script properties. Evaluated code gets
     * global object "this" and uses global object as scope for evaluation.
     * </p>
     * <p>
     * If self is undefined or null or global, then global object is used
     * as scope as well as "this" for the evaluated code. If self is any other
     * object, then it is indirect load call. With indirect load call, the
     * properties of scope are available to evaluated script as variables. Also,
     * global scope properties are accessible. Any var, function definition in
     * evaluated script goes into an object that is not accessible to user scripts.
     * </p>
     * Thus the indirect load call is equivalent to the following:
     * <pre>
     * <code>
     * (function (scope, source) {
     *    with(scope) {
     *        eval(&lt;script_from_source&gt;);
     *    }
     * })(self, source);
     * </code>
     * </pre>
     *
     * @param self    scope to use for the script evaluation
     * @param source  script source
     *
     * @return result of load (may be undefined)
     *
     * @throws IOException if source could not be read
     */
    public static Object load(final Object self, final Object source) throws IOException {
        final Global global = Global.instanceFrom(self);
        return global.getContext().load(self, source);
    }

    /**
     * Global loadWithNewGlobal implementation - Nashorn extension.
     *
     * loadWithNewGlobal builtin loads the given script from a URL or a File
     * or a script object with name and script properties. Evaluated code gets
     * new global object "this" and uses that new global object as scope for evaluation.
     *
     * @param self self This value is ignored by this function
     * @param args optional arguments to be passed to the loaded script
     *
     * @return result of load (may be undefined)
     *
     * @throws IOException if source could not be read
     */
    public static Object loadWithNewGlobal(final Object self, final Object...args) throws IOException {
        final Global global = Global.instanceFrom(self);
        final int length = args.length;
        final boolean hasArgs = 0 < length;
        final Object from = hasArgs ? args[0] : UNDEFINED;
        final Object[] arguments = hasArgs ? Arrays.copyOfRange(args, 1, length) : args;

        return global.getContext().loadWithNewGlobal(from, arguments);
    }

    /**
     * Global exit and quit implementation - Nashorn extension: perform a {@code System.exit} call from the script
     *
     * @param self  self reference
     * @param code  exit code
     *
     * @return undefined (will never be reached)
     */
    public static Object exit(final Object self, final Object code) {
        System.exit(JSType.toInt32(code));
        return UNDEFINED;
    }

    // builtin prototype accessors

    /**
     * Get the builtin Object prototype.
     * @return the object prototype.
     */
    public ScriptObject getObjectPrototype() {
        return ScriptFunction.getPrototype(builtinObject);
    }

    /**
     * Get the builtin Function prototype.
     * @return the Function.prototype.
     */
    public ScriptObject getFunctionPrototype() {
        return ScriptFunction.getPrototype(builtinFunction);
    }

    ScriptObject getArrayPrototype() {
        return ScriptFunction.getPrototype(builtinArray);
    }

    ScriptObject getBooleanPrototype() {
        return ScriptFunction.getPrototype(builtinBoolean);
    }

    ScriptObject getNumberPrototype() {
        return ScriptFunction.getPrototype(builtinNumber);
    }

    ScriptObject getDatePrototype() {
        return ScriptFunction.getPrototype(getBuiltinDate());
    }

    ScriptObject getRegExpPrototype() {
        return ScriptFunction.getPrototype(getBuiltinRegExp());
    }

    ScriptObject getStringPrototype() {
        return ScriptFunction.getPrototype(builtinString);
    }

    ScriptObject getErrorPrototype() {
        return ScriptFunction.getPrototype(builtinError);
    }

    ScriptObject getEvalErrorPrototype() {
        return ScriptFunction.getPrototype(getBuiltinEvalError());
    }

    ScriptObject getRangeErrorPrototype() {
        return ScriptFunction.getPrototype(getBuiltinRangeError());
    }

    ScriptObject getReferenceErrorPrototype() {
        return ScriptFunction.getPrototype(builtinReferenceError);
    }

    ScriptObject getSyntaxErrorPrototype() {
        return ScriptFunction.getPrototype(builtinSyntaxError);
    }

    ScriptObject getTypeErrorPrototype() {
        return ScriptFunction.getPrototype(builtinTypeError);
    }

    ScriptObject getURIErrorPrototype() {
        return ScriptFunction.getPrototype(getBuiltinURIError());
    }

    ScriptObject getJavaImporterPrototype() {
        return ScriptFunction.getPrototype(getBuiltinJavaImporter());
    }

    ScriptObject getJSAdapterPrototype() {
        return ScriptFunction.getPrototype(getBuiltinJSAdapter());
    }

    private synchronized ScriptFunction getBuiltinArrayBuffer() {
        if (this.builtinArrayBuffer == null) {
            this.builtinArrayBuffer = initConstructorAndSwitchPoint("ArrayBuffer", ScriptFunction.class);
        }
        return this.builtinArrayBuffer;
    }

    ScriptObject getArrayBufferPrototype() {
        return ScriptFunction.getPrototype(getBuiltinArrayBuffer());
    }

    private synchronized ScriptFunction getBuiltinDataView() {
        if (this.builtinDataView == null) {
            this.builtinDataView = initConstructorAndSwitchPoint("DataView", ScriptFunction.class);
        }
        return this.builtinDataView;
    }

    ScriptObject getDataViewPrototype() {
        return ScriptFunction.getPrototype(getBuiltinDataView());
    }

    private synchronized ScriptFunction getBuiltinInt8Array() {
        if (this.builtinInt8Array == null) {
            this.builtinInt8Array = initConstructorAndSwitchPoint("Int8Array", ScriptFunction.class);
        }
        return this.builtinInt8Array;
    }

    ScriptObject getInt8ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinInt8Array());
    }

    private synchronized ScriptFunction getBuiltinUint8Array() {
        if (this.builtinUint8Array == null) {
            this.builtinUint8Array = initConstructorAndSwitchPoint("Uint8Array", ScriptFunction.class);
        }
        return this.builtinUint8Array;
    }

    ScriptObject getUint8ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinUint8Array());
    }

    private synchronized ScriptFunction getBuiltinUint8ClampedArray() {
        if (this.builtinUint8ClampedArray == null) {
            this.builtinUint8ClampedArray = initConstructorAndSwitchPoint("Uint8ClampedArray", ScriptFunction.class);
        }
        return this.builtinUint8ClampedArray;
    }

    ScriptObject getUint8ClampedArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinUint8ClampedArray());
    }

    private synchronized ScriptFunction getBuiltinInt16Array() {
        if (this.builtinInt16Array == null) {
            this.builtinInt16Array = initConstructorAndSwitchPoint("Int16Array", ScriptFunction.class);
        }
        return this.builtinInt16Array;
    }

    ScriptObject getInt16ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinInt16Array());
    }

    private synchronized ScriptFunction getBuiltinUint16Array() {
        if (this.builtinUint16Array == null) {
            this.builtinUint16Array = initConstructorAndSwitchPoint("Uint16Array", ScriptFunction.class);
        }
        return this.builtinUint16Array;
    }

    ScriptObject getUint16ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinUint16Array());
    }

    private synchronized ScriptFunction getBuiltinInt32Array() {
        if (this.builtinInt32Array == null) {
            this.builtinInt32Array = initConstructorAndSwitchPoint("Int32Array", ScriptFunction.class);
        }
        return this.builtinInt32Array;
    }

    ScriptObject getInt32ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinInt32Array());
    }

    private synchronized ScriptFunction getBuiltinUint32Array() {
        if (this.builtinUint32Array == null) {
            this.builtinUint32Array = initConstructorAndSwitchPoint("Uint32Array", ScriptFunction.class);
        }
        return this.builtinUint32Array;
    }

    ScriptObject getUint32ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinUint32Array());
    }

    private synchronized ScriptFunction getBuiltinFloat32Array() {
        if (this.builtinFloat32Array == null) {
            this.builtinFloat32Array = initConstructorAndSwitchPoint("Float32Array", ScriptFunction.class);
        }
        return this.builtinFloat32Array;
    }

    ScriptObject getFloat32ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinFloat32Array());
    }

    private synchronized ScriptFunction getBuiltinFloat64Array() {
        if (this.builtinFloat64Array == null) {
            this.builtinFloat64Array = initConstructorAndSwitchPoint("Float64Array", ScriptFunction.class);
        }
        return this.builtinFloat64Array;
    }

    ScriptObject getFloat64ArrayPrototype() {
        return ScriptFunction.getPrototype(getBuiltinFloat64Array());
    }

    /**
     * Return the function that throws TypeError unconditionally. Used as "poison" methods for certain Function properties.
     *
     * @return the TypeError throwing function
     */
    public ScriptFunction getTypeErrorThrower() {
        return typeErrorThrower;
    }

    private synchronized ScriptFunction getBuiltinDate() {
        if (this.builtinDate == null) {
            this.builtinDate = initConstructorAndSwitchPoint("Date", ScriptFunction.class);
            final ScriptObject dateProto = ScriptFunction.getPrototype(builtinDate);
            // initialize default date
            this.DEFAULT_DATE = new NativeDate(NaN, dateProto);
        }
        return this.builtinDate;
    }

    private synchronized ScriptFunction getBuiltinEvalError() {
        if (this.builtinEvalError == null) {
            this.builtinEvalError = initErrorSubtype("EvalError", getErrorPrototype());
        }
        return this.builtinEvalError;
    }

    private ScriptFunction getBuiltinFunction() {
        return builtinFunction;
    }

    /**
     * Get the switchpoint used to check property changes for Function.prototype.apply
     * @return the switchpoint guarding apply (same as guarding call, and everything else in function)
     */
    public static SwitchPoint getBuiltinFunctionApplySwitchPoint() {
        return ScriptFunction.getPrototype(Global.instance().getBuiltinFunction()).getProperty("apply").getBuiltinSwitchPoint();
    }

    private static boolean isBuiltinFunctionProperty(final String name) {
        final Global instance = Global.instance();
        final ScriptFunction builtinFunction = instance.getBuiltinFunction();
        if (builtinFunction == null) {
            return false; //conservative for compile-only mode
        }
        final boolean isBuiltinFunction = instance.function == builtinFunction;
        return isBuiltinFunction && ScriptFunction.getPrototype(builtinFunction).getProperty(name).isBuiltin();
    }

    /**
     * Check if the Function.prototype.apply has not been replaced
     * @return true if Function.prototype.apply has been replaced
     */
    public static boolean isBuiltinFunctionPrototypeApply() {
        return isBuiltinFunctionProperty("apply");
    }

    /**
     * Check if the Function.prototype.apply has not been replaced
     * @return true if Function.prototype.call has been replaced
     */
    public static boolean isBuiltinFunctionPrototypeCall() {
        return isBuiltinFunctionProperty("call");
    }

    private synchronized ScriptFunction getBuiltinJSAdapter() {
        if (this.builtinJSAdapter == null) {
            this.builtinJSAdapter = initConstructorAndSwitchPoint("JSAdapter", ScriptFunction.class);
        }
        return builtinJSAdapter;
    }

    private synchronized ScriptObject getBuiltinJSON() {
        if (this.builtinJSON == null) {
            this.builtinJSON = initConstructorAndSwitchPoint("JSON", ScriptObject.class);
        }
        return this.builtinJSON;
    }

    private synchronized ScriptFunction getBuiltinJavaImporter() {
        if (getContext().getEnv()._no_java) {
            throw new IllegalStateException();
        }
        if (this.builtinJavaImporter == null) {
            this.builtinJavaImporter = initConstructor("JavaImporter", ScriptFunction.class);
        }
        return this.builtinJavaImporter;
    }

    private synchronized ScriptObject getBuiltinJavaApi() {
        if (getContext().getEnv()._no_java) {
            throw new IllegalStateException();
        }
        if (this.builtinJavaApi == null) {
            this.builtinJavaApi = initConstructor("Java", ScriptObject.class);
        }
        return this.builtinJavaApi;
    }

    private synchronized ScriptFunction getBuiltinRangeError() {
        if (this.builtinRangeError == null) {
            this.builtinRangeError = initErrorSubtype("RangeError", getErrorPrototype());
        }
        return builtinRangeError;
    }

    private synchronized ScriptFunction getBuiltinRegExp() {
        if (this.builtinRegExp == null) {
            this.builtinRegExp = initConstructorAndSwitchPoint("RegExp", ScriptFunction.class);
            final ScriptObject regExpProto = ScriptFunction.getPrototype(builtinRegExp);
            // initialize default regexp object
            this.DEFAULT_REGEXP = new NativeRegExp("(?:)", "", this, regExpProto);
            // RegExp.prototype should behave like a RegExp object. So copy the
            // properties.
            regExpProto.addBoundProperties(DEFAULT_REGEXP);
        }
        return builtinRegExp;
    }

    private synchronized ScriptFunction getBuiltinURIError() {
        if (this.builtinURIError == null) {
            this.builtinURIError = initErrorSubtype("URIError", getErrorPrototype());
        }
        return this.builtinURIError;
    }

    @Override
    public String getClassName() {
        return "global";
    }

    /**
     * Copy function used to clone NativeRegExp objects.
     *
     * @param regexp a NativeRegExp to clone
     *
     * @return copy of the given regexp object
     */
    public static Object regExpCopy(final Object regexp) {
        return new NativeRegExp((NativeRegExp)regexp);
    }

    /**
     * Convert given object to NativeRegExp type.
     *
     * @param obj object to be converted
     * @return NativeRegExp instance
     */
    public static NativeRegExp toRegExp(final Object obj) {
        if (obj instanceof NativeRegExp) {
            return (NativeRegExp)obj;
        }
        return new NativeRegExp(JSType.toString(obj));
    }

    /**
     * ECMA 9.9 ToObject implementation
     *
     * @param obj  an item for which to run ToObject
     * @return ToObject version of given item
     */
    public static Object toObject(final Object obj) {
        if (obj == null || obj == UNDEFINED) {
            throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
        }

        if (obj instanceof ScriptObject) {
            return obj;
        }

        return instance().wrapAsObject(obj);
    }

    /**
     * Allocate a new object array.
     *
     * @param initial object values.
     * @return the new array
     */
    public static NativeArray allocate(final Object[] initial) {
        ArrayData arrayData = ArrayData.allocate(initial);

        for (int index = 0; index < initial.length; index++) {
            final Object value = initial[index];

            if (value == ScriptRuntime.EMPTY) {
                arrayData = arrayData.delete(index);
            }
        }

        return new NativeArray(arrayData);
    }

    /**
     * Allocate a new number array.
     *
     * @param initial number values.
     * @return the new array
     */
    public static NativeArray allocate(final double[] initial) {
        return new NativeArray(ArrayData.allocate(initial));
    }

    /**
     * Allocate a new integer array.
     *
     * @param initial number values.
     * @return the new array
     */
    public static NativeArray allocate(final int[] initial) {
        return new NativeArray(ArrayData.allocate(initial));
    }

    /**
     * Allocate a new object array for arguments.
     *
     * @param arguments initial arguments passed.
     * @param callee reference to the function that uses arguments object
     * @param numParams actual number of declared parameters
     *
     * @return the new array
     */
    public static ScriptObject allocateArguments(final Object[] arguments, final Object callee, final int numParams) {
        return NativeArguments.allocate(arguments, (ScriptFunction)callee, numParams);
    }

    /**
     * Called from generated to check if given function is the builtin 'eval'. If
     * eval is used in a script, a lot of optimizations and assumptions cannot be done.
     *
     * @param  fn function object that is checked
     * @return true if fn is the builtin eval
     */
    public static boolean isEval(final Object fn) {
        return fn == Global.instance().builtinEval;
    }

    /**
     * Called from generated to replace a location property placeholder with the actual location property value.
     *
     * @param  placeholder the value tested for being a placeholder for a location property
     * @param  locationProperty the actual value for the location property
     * @return locationProperty if placeholder is indeed a placeholder for a location property, the placeholder otherwise
     */
    public static Object replaceLocationPropertyPlaceholder(final Object placeholder, final Object locationProperty) {
        return isLocationPropertyPlaceholder(placeholder) ? locationProperty : placeholder;
    }

    /**
     * Called from runtime internals to check if the passed value is a location property placeholder.
     * @param  placeholder the value tested for being a placeholder for a location property
     * @return true if the value is a placeholder, false otherwise.
     */
    public static boolean isLocationPropertyPlaceholder(final Object placeholder) {
        return placeholder == LOCATION_PLACEHOLDER;
    }

    /**
     * Create a new RegExp object.
     *
     * @param expression Regular expression.
     * @param options    Search options.
     *
     * @return New RegExp object.
     */
    public static Object newRegExp(final String expression, final String options) {
        if (options == null) {
            return new NativeRegExp(expression);
        }
        return new NativeRegExp(expression, options);
    }

    /**
     * Get the object prototype
     *
     * @return the object prototype
     */
    public static ScriptObject objectPrototype() {
        return Global.instance().getObjectPrototype();
    }

    /**
     * Create a new empty object instance.
     *
     * @return New empty object.
     */
    public static ScriptObject newEmptyInstance() {
        return Global.instance().newObject();
    }

    /**
     * Check if a given object is a ScriptObject, raises an exception if this is
     * not the case
     *
     * @param obj and object to check
     * @return the script object
     */
    public static ScriptObject checkObject(final Object obj) {
        if (!(obj instanceof ScriptObject)) {
            throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
        }
        return (ScriptObject)obj;
    }

    /**
     * ECMA 9.10 - implementation of CheckObjectCoercible, i.e. raise an exception
     * if this object is null or undefined.
     *
     * @param obj an object to check
     */
    public static void checkObjectCoercible(final Object obj) {
        if (obj == null || obj == UNDEFINED) {
            throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
        }
    }

    /**
     * Return the ES6 global scope for lexically declared bindings.
     * @return the ES6 lexical global scope.
     */
    public final ScriptObject getLexicalScope() {
        assert context.getEnv()._es6;
        return lexicalScope;
    }

    @Override
    public void addBoundProperties(final ScriptObject source, final jdk.nashorn.internal.runtime.Property[] properties) {
        PropertyMap ownMap = getMap();
        LexicalScope lexScope = null;
        PropertyMap lexicalMap = null;
        boolean hasLexicalDefinitions = false;

        if (context.getEnv()._es6) {
            lexScope = (LexicalScope) getLexicalScope();
            lexicalMap = lexScope.getMap();

            for (final jdk.nashorn.internal.runtime.Property property : properties) {
                if (property.isLexicalBinding()) {
                    hasLexicalDefinitions = true;
                }
                // ES6 15.1.8 steps 6. and 7.
                final jdk.nashorn.internal.runtime.Property globalProperty = ownMap.findProperty(property.getKey());
                if (globalProperty != null && !globalProperty.isConfigurable() && property.isLexicalBinding()) {
                    throw ECMAErrors.syntaxError("redeclare.variable", property.getKey());
                }
                final jdk.nashorn.internal.runtime.Property lexicalProperty = lexicalMap.findProperty(property.getKey());
                if (lexicalProperty != null && !property.isConfigurable()) {
                    throw ECMAErrors.syntaxError("redeclare.variable", property.getKey());
                }
            }
        }

        final boolean extensible = isExtensible();
        for (final jdk.nashorn.internal.runtime.Property property : properties) {
            if (property.isLexicalBinding()) {
                assert lexScope != null;
                lexicalMap = lexScope.addBoundProperty(lexicalMap, source, property, true);

                if (ownMap.findProperty(property.getKey()) != null) {
                    // If property exists in the global object invalidate any global constant call sites.
                    invalidateGlobalConstant(property.getKey());
                }
            } else {
                ownMap = addBoundProperty(ownMap, source, property, extensible);
            }
        }

        setMap(ownMap);

        if (hasLexicalDefinitions) {
            assert lexScope != null;
            lexScope.setMap(lexicalMap);
            invalidateLexicalSwitchPoint();
        }
    }

    @Override
    public GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) {
        final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
        final boolean isScope = NashornCallSiteDescriptor.isScope(desc);

        if (lexicalScope != null && isScope && !NashornCallSiteDescriptor.isApplyToCall(desc)) {
            if (lexicalScope.hasOwnProperty(name)) {
                return lexicalScope.findGetMethod(desc, request, operator);
            }
        }

        final GuardedInvocation invocation =  super.findGetMethod(desc, request, operator);

        // We want to avoid adding our generic lexical scope switchpoint to global constant invocations,
        // because those are invalidated per-key in the addBoundProperties method above.
        // We therefore check if the invocation does already have a switchpoint and the property is non-inherited,
        // assuming this only applies to global constants. If other non-inherited properties will
        // start using switchpoints some time in the future we'll have to revisit this.
        if (isScope && context.getEnv()._es6 && (invocation.getSwitchPoints() == null || !hasOwnProperty(name))) {
            return invocation.addSwitchPoint(getLexicalScopeSwitchPoint());
        }

        return invocation;
    }

    @Override
    protected FindProperty findProperty(final String key, final boolean deep, final ScriptObject start) {
        if (lexicalScope != null && start != this && start.isScope()) {
            final FindProperty find = lexicalScope.findProperty(key, false);
            if (find != null) {
                return find;
            }
        }
        return super.findProperty(key, deep, start);
    }

    @Override
    public GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) {
        final boolean isScope = NashornCallSiteDescriptor.isScope(desc);

        if (lexicalScope != null && isScope) {
            final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
            if (lexicalScope.hasOwnProperty(name)) {
                return lexicalScope.findSetMethod(desc, request);
            }
        }

        final GuardedInvocation invocation = super.findSetMethod(desc, request);

        if (isScope && context.getEnv()._es6) {
            return invocation.addSwitchPoint(getLexicalScopeSwitchPoint());
        }

        return invocation;
    }

    /**
     * Adds jjs shell interactive mode builtin functions to global scope.
     */
    public void addShellBuiltins() {
        Object value = ScriptFunction.createBuiltin("input", ShellFunctions.INPUT);
        addOwnProperty("input", Attribute.NOT_ENUMERABLE, value);

        value = ScriptFunction.createBuiltin("evalinput", ShellFunctions.EVALINPUT);
        addOwnProperty("evalinput", Attribute.NOT_ENUMERABLE, value);
    }

    private synchronized SwitchPoint getLexicalScopeSwitchPoint() {
        SwitchPoint switchPoint = lexicalScopeSwitchPoint;
        if (switchPoint == null || switchPoint.hasBeenInvalidated()) {
            switchPoint = lexicalScopeSwitchPoint = new SwitchPoint();
        }
        return switchPoint;
    }

    private synchronized void invalidateLexicalSwitchPoint() {
        if (lexicalScopeSwitchPoint != null) {
            context.getLogger(GlobalConstants.class).info("Invalidating non-constant globals on lexical scope update");
            SwitchPoint.invalidateAll(new SwitchPoint[]{ lexicalScopeSwitchPoint });
        }
    }


    @SuppressWarnings("unused")
    private static Object lexicalScopeFilter(final Object self) {
        if (self instanceof Global) {
            return ((Global) self).getLexicalScope();
        }
        return self;
    }

    private <T extends ScriptObject> T initConstructorAndSwitchPoint(final String name, final Class<T> clazz) {
        final T func = initConstructor(name, clazz);
        tagBuiltinProperties(name, func);
        return func;
    }

    @SuppressWarnings("hiding")
    private void init(final ScriptEngine eng) {
        assert Context.getGlobal() == this : "this global is not set as current";

        final ScriptEnvironment env = getContext().getEnv();

        // initialize Function and Object constructor
        initFunctionAndObject();

        // Now fix Global's own proto.
        this.setInitialProto(getObjectPrototype());

        // initialize global function properties
        this.eval = this.builtinEval = ScriptFunction.createBuiltin("eval", EVAL);

        this.parseInt = ScriptFunction.createBuiltin("parseInt",   GlobalFunctions.PARSEINT,
                    new Specialization[] {
                    new Specialization(GlobalFunctions.PARSEINT_Z),
                    new Specialization(GlobalFunctions.PARSEINT_I),
                    new Specialization(GlobalFunctions.PARSEINT_OI),
                    new Specialization(GlobalFunctions.PARSEINT_O) });
        this.parseFloat = ScriptFunction.createBuiltin("parseFloat", GlobalFunctions.PARSEFLOAT);
        this.isNaN = ScriptFunction.createBuiltin("isNaN",   GlobalFunctions.IS_NAN,
                   new Specialization[] {
                        new Specialization(GlobalFunctions.IS_NAN_I),
                        new Specialization(GlobalFunctions.IS_NAN_J),
                        new Specialization(GlobalFunctions.IS_NAN_D) });
        this.parseFloat         = ScriptFunction.createBuiltin("parseFloat", GlobalFunctions.PARSEFLOAT);
        this.isNaN              = ScriptFunction.createBuiltin("isNaN",      GlobalFunctions.IS_NAN);
        this.isFinite           = ScriptFunction.createBuiltin("isFinite",   GlobalFunctions.IS_FINITE);
        this.encodeURI          = ScriptFunction.createBuiltin("encodeURI",  GlobalFunctions.ENCODE_URI);
        this.encodeURIComponent = ScriptFunction.createBuiltin("encodeURIComponent", GlobalFunctions.ENCODE_URICOMPONENT);
        this.decodeURI          = ScriptFunction.createBuiltin("decodeURI",  GlobalFunctions.DECODE_URI);
        this.decodeURIComponent = ScriptFunction.createBuiltin("decodeURIComponent", GlobalFunctions.DECODE_URICOMPONENT);
        this.escape             = ScriptFunction.createBuiltin("escape",     GlobalFunctions.ESCAPE);
        this.unescape           = ScriptFunction.createBuiltin("unescape",   GlobalFunctions.UNESCAPE);
        this.print              = ScriptFunction.createBuiltin("print",      env._print_no_newline ? PRINT : PRINTLN);
        this.load               = ScriptFunction.createBuiltin("load",       LOAD);
        this.loadWithNewGlobal  = ScriptFunction.createBuiltin("loadWithNewGlobal", LOAD_WITH_NEW_GLOBAL);
        this.exit               = ScriptFunction.createBuiltin("exit",       EXIT);
        this.quit               = ScriptFunction.createBuiltin("quit",       EXIT);

        // built-in constructors
        this.builtinArray     = initConstructorAndSwitchPoint("Array", ScriptFunction.class);
        this.builtinBoolean   = initConstructorAndSwitchPoint("Boolean", ScriptFunction.class);
        this.builtinNumber    = initConstructorAndSwitchPoint("Number", ScriptFunction.class);
        this.builtinString    = initConstructorAndSwitchPoint("String", ScriptFunction.class);
        this.builtinMath      = initConstructorAndSwitchPoint("Math", ScriptObject.class);

        // initialize String.prototype.length to 0
        // add String.prototype.length
        final ScriptObject stringPrototype = getStringPrototype();
        stringPrototype.addOwnProperty("length", Attribute.NON_ENUMERABLE_CONSTANT, 0.0);

        // set isArray flag on Array.prototype
        final ScriptObject arrayPrototype = getArrayPrototype();
        arrayPrototype.setIsArray();

        // Error stuff
        initErrorObjects();

        // java access
        if (! env._no_java) {
            this.javaApi = LAZY_SENTINEL;
            this.javaImporter = LAZY_SENTINEL;
            initJavaAccess();
        } else {
            // delete nasgen-created global properties related to java access
            this.delete("Java", false);
            this.delete("JavaImporter", false);
            this.delete("Packages", false);
            this.delete("com", false);
            this.delete("edu", false);
            this.delete("java", false);
            this.delete("javafx", false);
            this.delete("javax", false);
            this.delete("org", false);
        }

        if (! env._no_typed_arrays) {
            this.arrayBuffer       = LAZY_SENTINEL;
            this.dataView          = LAZY_SENTINEL;
            this.int8Array         = LAZY_SENTINEL;
            this.uint8Array        = LAZY_SENTINEL;
            this.uint8ClampedArray = LAZY_SENTINEL;
            this.int16Array        = LAZY_SENTINEL;
            this.uint16Array       = LAZY_SENTINEL;
            this.int32Array        = LAZY_SENTINEL;
            this.uint32Array       = LAZY_SENTINEL;
            this.float32Array      = LAZY_SENTINEL;
            this.float64Array      = LAZY_SENTINEL;
        }

        if (env._scripting) {
            initScripting(env);
        }

        if (Context.DEBUG) {
            boolean debugOkay;
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                try {
                    sm.checkPermission(new RuntimePermission(Context.NASHORN_DEBUG_MODE));
                    debugOkay = true;
                } catch (final SecurityException ignored) {
                    // if no permission, don't initialize Debug object
                    debugOkay = false;
                }

            } else {
                debugOkay = true;
            }

            if (debugOkay) {
                initDebug();
            }
        }

        copyBuiltins();

        // expose script (command line) arguments as "arguments" property of global
        arguments = wrapAsObject(env.getArguments().toArray());
        if (env._scripting) {
            // synonym for "arguments" in scripting mode
            addOwnProperty("$ARG", Attribute.NOT_ENUMERABLE, arguments);
        }

        if (eng != null) {
            // default file name
            addOwnProperty(ScriptEngine.FILENAME, Attribute.NOT_ENUMERABLE, null);
            // __noSuchProperty__ hook for ScriptContext search of missing variables
            final ScriptFunction noSuchProp = ScriptFunction.createStrictBuiltin(NO_SUCH_PROPERTY_NAME, NO_SUCH_PROPERTY);
            addOwnProperty(NO_SUCH_PROPERTY_NAME, Attribute.NOT_ENUMERABLE, noSuchProp);
        }
    }

    private void initErrorObjects() {
        // Error objects
        this.builtinError = initConstructor("Error", ScriptFunction.class);
        final ScriptObject errorProto = getErrorPrototype();

        // Nashorn specific accessors on Error.prototype - stack, lineNumber, columnNumber and fileName
        final ScriptFunction getStack = ScriptFunction.createBuiltin("getStack", NativeError.GET_STACK);
        final ScriptFunction setStack = ScriptFunction.createBuiltin("setStack", NativeError.SET_STACK);
        errorProto.addOwnProperty("stack", Attribute.NOT_ENUMERABLE, getStack, setStack);
        final ScriptFunction getLineNumber = ScriptFunction.createBuiltin("getLineNumber", NativeError.GET_LINENUMBER);
        final ScriptFunction setLineNumber = ScriptFunction.createBuiltin("setLineNumber", NativeError.SET_LINENUMBER);
        errorProto.addOwnProperty("lineNumber", Attribute.NOT_ENUMERABLE, getLineNumber, setLineNumber);
        final ScriptFunction getColumnNumber = ScriptFunction.createBuiltin("getColumnNumber", NativeError.GET_COLUMNNUMBER);
        final ScriptFunction setColumnNumber = ScriptFunction.createBuiltin("setColumnNumber", NativeError.SET_COLUMNNUMBER);
        errorProto.addOwnProperty("columnNumber", Attribute.NOT_ENUMERABLE, getColumnNumber, setColumnNumber);
        final ScriptFunction getFileName = ScriptFunction.createBuiltin("getFileName", NativeError.GET_FILENAME);
        final ScriptFunction setFileName = ScriptFunction.createBuiltin("setFileName", NativeError.SET_FILENAME);
        errorProto.addOwnProperty("fileName", Attribute.NOT_ENUMERABLE, getFileName, setFileName);

        // ECMA 15.11.4.2 Error.prototype.name
        // Error.prototype.name = "Error";
        errorProto.set(NativeError.NAME, "Error", 0);
        // ECMA 15.11.4.3 Error.prototype.message
        // Error.prototype.message = "";
        errorProto.set(NativeError.MESSAGE, "", 0);

        tagBuiltinProperties("Error", builtinError);

        this.builtinReferenceError = initErrorSubtype("ReferenceError", errorProto);
        this.builtinSyntaxError = initErrorSubtype("SyntaxError", errorProto);
        this.builtinTypeError = initErrorSubtype("TypeError", errorProto);
    }

    private ScriptFunction initErrorSubtype(final String name, final ScriptObject errorProto) {
        final ScriptFunction cons = initConstructor(name, ScriptFunction.class);
        final ScriptObject prototype = ScriptFunction.getPrototype(cons);
        prototype.set(NativeError.NAME, name, 0);
        prototype.set(NativeError.MESSAGE, "", 0);
        prototype.setInitialProto(errorProto);
        tagBuiltinProperties(name, cons);
        return cons;
    }

    private void initJavaAccess() {
        final ScriptObject objectProto = getObjectPrototype();
        this.builtinPackages = new NativeJavaPackage("", objectProto);
        this.builtinCom = new NativeJavaPackage("com", objectProto);
        this.builtinEdu = new NativeJavaPackage("edu", objectProto);
        this.builtinJava = new NativeJavaPackage("java", objectProto);
        this.builtinJavafx = new NativeJavaPackage("javafx", objectProto);
        this.builtinJavax = new NativeJavaPackage("javax", objectProto);
        this.builtinOrg = new NativeJavaPackage("org", objectProto);
    }

    private void initScripting(final ScriptEnvironment scriptEnv) {
        ScriptObject value;
        value = ScriptFunction.createBuiltin("readLine", ScriptingFunctions.READLINE);
        addOwnProperty("readLine", Attribute.NOT_ENUMERABLE, value);

        value = ScriptFunction.createBuiltin("readFully", ScriptingFunctions.READFULLY);
        addOwnProperty("readFully", Attribute.NOT_ENUMERABLE, value);

        final String execName = ScriptingFunctions.EXEC_NAME;
        value = ScriptFunction.createBuiltin(execName, ScriptingFunctions.EXEC);
        value.addOwnProperty(ScriptingFunctions.THROW_ON_ERROR_NAME, Attribute.NOT_ENUMERABLE, false);

        addOwnProperty(execName, Attribute.NOT_ENUMERABLE, value);

        // Nashorn extension: global.echo (scripting-mode-only)
        // alias for "print"
        value = (ScriptObject)get("print");
        addOwnProperty("echo", Attribute.NOT_ENUMERABLE, value);

        // Nashorn extension: global.$OPTIONS (scripting-mode-only)
        final ScriptObject options = newObject();
        copyOptions(options, scriptEnv);
        addOwnProperty("$OPTIONS", Attribute.NOT_ENUMERABLE, options);

        // Nashorn extension: global.$ENV (scripting-mode-only)
        if (System.getSecurityManager() == null) {
            // do not fill $ENV if we have a security manager around
            // Retrieve current state of ENV variables.
            final ScriptObject env = newObject();
            env.putAll(System.getenv(), scriptEnv._strict);

            // Some platforms, e.g., Windows, do not define the PWD environment
            // variable, so that the $ENV.PWD property needs to be explicitly
            // set.
            if (!env.containsKey(ScriptingFunctions.PWD_NAME)) {
                env.put(ScriptingFunctions.PWD_NAME, System.getProperty("user.dir"), scriptEnv._strict);
            }

            addOwnProperty(ScriptingFunctions.ENV_NAME, Attribute.NOT_ENUMERABLE, env);
        } else {
            addOwnProperty(ScriptingFunctions.ENV_NAME, Attribute.NOT_ENUMERABLE, UNDEFINED);
        }

        // add other special properties for exec support
        addOwnProperty(ScriptingFunctions.OUT_NAME, Attribute.NOT_ENUMERABLE, UNDEFINED);
        addOwnProperty(ScriptingFunctions.ERR_NAME, Attribute.NOT_ENUMERABLE, UNDEFINED);
        addOwnProperty(ScriptingFunctions.EXIT_NAME, Attribute.NOT_ENUMERABLE, UNDEFINED);
    }

    private static void copyOptions(final ScriptObject options, final ScriptEnvironment scriptEnv) {
        for (final Field f : scriptEnv.getClass().getFields()) {
            try {
                options.set(f.getName(), f.get(scriptEnv), 0);
            } catch (final IllegalArgumentException | IllegalAccessException exp) {
                throw new RuntimeException(exp);
            }
        }
    }

    private void copyBuiltins() {
        this.array             = this.builtinArray;
        this._boolean          = this.builtinBoolean;
        this.error             = this.builtinError;
        this.function          = this.builtinFunction;
        this.com               = this.builtinCom;
        this.edu               = this.builtinEdu;
        this.java              = this.builtinJava;
        this.javafx            = this.builtinJavafx;
        this.javax             = this.builtinJavax;
        this.org               = this.builtinOrg;
        this.math              = this.builtinMath;
        this.number            = this.builtinNumber;
        this.object            = this.builtinObject;
        this.packages          = this.builtinPackages;
        this.referenceError    = this.builtinReferenceError;
        this.string            = this.builtinString;
        this.syntaxError       = this.builtinSyntaxError;
        this.typeError         = this.builtinTypeError;
    }

    private void initDebug() {
        this.addOwnProperty("Debug", Attribute.NOT_ENUMERABLE, initConstructor("Debug", ScriptObject.class));
    }

    private Object printImpl(final boolean newLine, final Object... objects) {
        final ScriptContext sc = currentContext();
        @SuppressWarnings("resource")
        final PrintWriter out = sc != null? new PrintWriter(sc.getWriter()) : getContext().getEnv().getOut();
        final StringBuilder sb = new StringBuilder();

        for (final Object obj : objects) {
            if (sb.length() != 0) {
                sb.append(' ');
            }

            sb.append(JSType.toString(obj));
        }

        // Print all at once to ensure thread friendly result.
        if (newLine) {
            out.println(sb.toString());
        } else {
            out.print(sb.toString());
        }

        out.flush();

        return UNDEFINED;
    }

    private <T extends ScriptObject> T initConstructor(final String name, final Class<T> clazz) {
        try {
            // Assuming class name pattern for built-in JS constructors.
            final StringBuilder sb = new StringBuilder("jdk.nashorn.internal.objects.");

            sb.append("Native");
            sb.append(name);
            sb.append("$Constructor");

            final Class<?> funcClass = Class.forName(sb.toString());
            final T res = clazz.cast(funcClass.newInstance());

            if (res instanceof ScriptFunction) {
                // All global constructor prototypes are not-writable,
                // not-enumerable and not-configurable.
                final ScriptFunction func = (ScriptFunction)res;
                func.modifyOwnProperty(func.getProperty("prototype"), Attribute.NON_ENUMERABLE_CONSTANT);
            }

            if (res.getProto() == null) {
                res.setInitialProto(getObjectPrototype());
            }

            res.setIsBuiltin();

            return res;
        } catch (final ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private List<jdk.nashorn.internal.runtime.Property> extractBuiltinProperties(final String name, final ScriptObject func) {
        final List<jdk.nashorn.internal.runtime.Property> list = new ArrayList<>();

        list.addAll(Arrays.asList(func.getMap().getProperties()));

        if (func instanceof ScriptFunction) {
            final ScriptObject proto = ScriptFunction.getPrototype((ScriptFunction)func);
            if (proto != null) {
                list.addAll(Arrays.asList(proto.getMap().getProperties()));
            }
        }

        final jdk.nashorn.internal.runtime.Property prop = getProperty(name);
        if (prop != null) {
            list.add(prop);
        }

        return list;
    }

    /**
     * Given a builtin object, traverse its properties recursively and associate them with a name that
     * will be a key to their invalidation switchpoint.
     * @param name name for key
     * @param func builtin script object
     */
    private void tagBuiltinProperties(final String name, final ScriptObject func) {
        SwitchPoint sp = context.getBuiltinSwitchPoint(name);
        if (sp == null) {
            sp = context.newBuiltinSwitchPoint(name);
        }

        //get all builtin properties in this builtin object and register switchpoints keyed on the propery name,
        //one overwrite destroys all for now, e.g. Function.prototype.apply = 17; also destroys Function.prototype.call etc
        for (final jdk.nashorn.internal.runtime.Property prop : extractBuiltinProperties(name, func)) {
            prop.setBuiltinSwitchPoint(sp);
        }
    }

    // Function and Object constructors are inter-dependent. Also,
    // Function.prototype
    // functions are not properly initialized. We fix the references here.
    // NOTE: be careful if you want to re-order the operations here. You may
    // have
    // to play with object references carefully!!
    private void initFunctionAndObject() {
        // First-n-foremost is Function

        this.builtinFunction = initConstructor("Function", ScriptFunction.class);

        // create global anonymous function
        final ScriptFunction anon = ScriptFunction.createAnonymous();
        // need to copy over members of Function.prototype to anon function
        anon.addBoundProperties(getFunctionPrototype());

        // Function.prototype === Object.getPrototypeOf(Function) ===
        // <anon-function>
        builtinFunction.setInitialProto(anon);
        builtinFunction.setPrototype(anon);
        anon.set("constructor", builtinFunction, 0);
        anon.deleteOwnProperty(anon.getMap().findProperty("prototype"));

        // use "getter" so that [[ThrowTypeError]] function's arity is 0 - as specified in step 10 of section 13.2.3
        this.typeErrorThrower = ScriptFunction.createBuiltin("TypeErrorThrower", Lookup.TYPE_ERROR_THROWER_GETTER);
        typeErrorThrower.preventExtensions();

        // now initialize Object
        this.builtinObject = initConstructor("Object", ScriptFunction.class);
        final ScriptObject ObjectPrototype = getObjectPrototype();
        // Object.getPrototypeOf(Function.prototype) === Object.prototype
        anon.setInitialProto(ObjectPrototype);

        // ES6 draft compliant __proto__ property of Object.prototype
        // accessors on Object.prototype for "__proto__"
        final ScriptFunction getProto = ScriptFunction.createBuiltin("getProto", NativeObject.GET__PROTO__);
        final ScriptFunction setProto = ScriptFunction.createBuiltin("setProto", NativeObject.SET__PROTO__);
        ObjectPrototype.addOwnProperty("__proto__", Attribute.NOT_ENUMERABLE, getProto, setProto);

        // Function valued properties of Function.prototype were not properly
        // initialized. Because, these were created before global.function and
        // global.object were not initialized.
        jdk.nashorn.internal.runtime.Property[] properties = getFunctionPrototype().getMap().getProperties();
        for (final jdk.nashorn.internal.runtime.Property property : properties) {
            final Object key = property.getKey();
            final Object value = builtinFunction.get(key);

            if (value instanceof ScriptFunction && value != anon) {
                final ScriptFunction func = (ScriptFunction)value;
                func.setInitialProto(getFunctionPrototype());
                final ScriptObject prototype = ScriptFunction.getPrototype(func);
                if (prototype != null) {
                    prototype.setInitialProto(ObjectPrototype);
                }
            }
        }

        // For function valued properties of Object and Object.prototype, make
        // sure prototype's proto chain ends with Object.prototype
        for (final jdk.nashorn.internal.runtime.Property property : builtinObject.getMap().getProperties()) {
            final Object key = property.getKey();
            final Object value = builtinObject.get(key);

            if (value instanceof ScriptFunction) {
                final ScriptFunction func = (ScriptFunction)value;
                final ScriptObject prototype = ScriptFunction.getPrototype(func);
                if (prototype != null) {
                    prototype.setInitialProto(ObjectPrototype);
                }
            }
        }

        properties = getObjectPrototype().getMap().getProperties();

        for (final jdk.nashorn.internal.runtime.Property property : properties) {
            final Object key   = property.getKey();
            if (key.equals("constructor")) {
                continue;
            }

            final Object value = ObjectPrototype.get(key);
            if (value instanceof ScriptFunction) {
                final ScriptFunction func = (ScriptFunction)value;
                final ScriptObject prototype = ScriptFunction.getPrototype(func);
                if (prototype != null) {
                    prototype.setInitialProto(ObjectPrototype);
                }
            }
        }

        tagBuiltinProperties("Object", builtinObject);
        tagBuiltinProperties("Function", builtinFunction);
        tagBuiltinProperties("Function", anon);
    }

    private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
        return MH.findStatic(MethodHandles.lookup(), Global.class, name, MH.type(rtype, types));
    }

    RegExpResult getLastRegExpResult() {
        return lastRegExpResult;
    }

    void setLastRegExpResult(final RegExpResult regExpResult) {
        this.lastRegExpResult = regExpResult;
    }

    @Override
    protected boolean isGlobal() {
        return true;
    }

    /**
     * A class representing the ES6 global lexical scope.
     */
    private static class LexicalScope extends ScriptObject {

        LexicalScope(final Global global) {
            super(global, PropertyMap.newMap());
        }

        @Override
        protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) {
            return filterInvocation(super.findGetMethod(desc, request, operator));
        }

        @Override
        protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) {
            return filterInvocation(super.findSetMethod(desc, request));
        }

        @Override
        protected PropertyMap addBoundProperty(final PropertyMap propMap, final ScriptObject source, final jdk.nashorn.internal.runtime.Property property, final boolean extensible) {
            // We override this method just to make it callable by Global
            return super.addBoundProperty(propMap, source, property, extensible);
        }

        private static GuardedInvocation filterInvocation(final GuardedInvocation invocation) {
            final MethodType type = invocation.getInvocation().type();
            return invocation.asType(type.changeParameterType(0, Object.class)).filterArguments(0, LEXICAL_SCOPE_FILTER);
        }
    }

}
