/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.runtime.objects;
import static com.github.anba.es6draft.runtime.AbstractOperations.Get;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToFlatString;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToNumber;
import static com.github.anba.es6draft.runtime.internal.Errors.newURIError;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.Eval.indirectEval;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import java.io.IOException;
import java.net.URISyntaxException;
import com.github.anba.es6draft.compiler.CompilationException;
import com.github.anba.es6draft.parser.ParserException;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.Properties.Attributes;
import com.github.anba.es6draft.runtime.internal.Properties.CompatibilityExtension;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.internal.RuntimeContext;
import com.github.anba.es6draft.runtime.internal.Strings;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.Undefined;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;
/**
* <h1>18 The Global Object</h1>
* <ul>
* <li>18.1 Value Properties of the Global Object
* <li>18.2 Function Properties of the Global Object
* <li>18.3 Constructor Properties of the Global Object
* <li>18.4 Other Properties of the Global Object
* </ul>
*/
public class GlobalObject extends OrdinaryObject implements Initializable {
private final Realm realm;
/**
* Constructs a new Global object.
*
* @param realm
* the realm object
*/
public GlobalObject(Realm realm) {
super(realm);
this.realm = realm;
}
@Override
public final void initialize(Realm realm) {
createProperties(realm, this, ValueProperties.class);
createProperties(realm, this, FunctionProperties.class);
createProperties(realm, this, ConstructorProperties.class);
createProperties(realm, this, OtherProperties.class);
if (realm.isEnabled(CompatibilityOption.System) || realm.isEnabled(CompatibilityOption.SystemGlobal)) {
createProperties(realm, this, SystemProperty.class);
}
createProperties(realm, this, SIMDProperty.class);
createProperties(realm, this, ObservableProperty.class);
createProperties(realm, this, AtomicsProperties.class);
createProperties(realm, this, AdditionalProperties.class);
}
/**
* Executes any initialization scripts which should be run for this global instance.
*
* @throws IOException
* if there was any I/O error
* @throws URISyntaxException
* the URL is not a valid URI
* @throws ParserException
* if the source contains any syntax errors
* @throws CompilationException
* if the parsed source could not be compiled
*/
public void initializeScripted() throws IOException, URISyntaxException, ParserException, CompilationException {
/* empty */
}
/**
* Initializes implementation defined extensions.
*/
public void initializeExtensions() {
/* empty */
}
/**
* Creates user-defined native global functions.
*
* @param <T>
* the owner type
* @param object
* the owner object instance
* @param clazz
* the class which holds the properties
* @return the owner object
*/
public final <T> T createGlobalProperties(T object, Class<T> clazz) {
return getRealm().createGlobalProperties(object, clazz);
}
/**
* Returns the {@link Realm} of this global object.
*
* @return the realm instance
*/
public final Realm getRealm() {
return realm;
}
/**
* Returns the {@link RuntimeContext} of this global object.
*
* @return the runtime context instance
*/
public final RuntimeContext getRuntimeContext() {
return getRealm().getWorld().getContext();
}
/**
* 18.1 Value Properties of the Global Object
*/
public enum ValueProperties {
;
/**
* 18.1.2 NaN
*/
@Value(name = "NaN", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Double NaN = Double.NaN;
/**
* 18.1.1 Infinity
*/
@Value(name = "Infinity", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Double Infinity = Double.POSITIVE_INFINITY;
/**
* 18.1.3 undefined
*/
@Value(name = "undefined", attributes = @Attributes(writable = false, enumerable = false,
configurable = false))
public static final Undefined undefined = UNDEFINED;
}
/**
* 18.2 Function Properties of the Global Object
*/
public enum FunctionProperties {
;
/**
* 18.2.1 eval (x)
*
* @param cx
* the execution context
* @param caller
* the caller context
* @param thisValue
* the function this-value
* @param args
* the arguments
* @return the evaluation result
*/
@Function(name = "eval", arity = 1)
public static Object eval(ExecutionContext cx, ExecutionContext caller, Object thisValue,
Object... args) {
return indirectEval(cx, caller, args);
}
/**
* 18.2.2 isFinite (number)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param number
* the number value
* @return {@code true} if the argument is a finite number
*/
@Function(name = "isFinite", arity = 1)
public static Object isFinite(ExecutionContext cx, Object thisValue, Object number) {
/* steps 1-2 */
double num = ToNumber(cx, number);
/* step 3 */
if (Double.isNaN(num) || Double.isInfinite(num)) {
return false;
}
/* step 4 */
return true;
}
/**
* 18.2.3 isNaN (number)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param number
* the number value
* @return {@code true} if the number is the NaN value
*/
@Function(name = "isNaN", arity = 1)
public static Object isNaN(ExecutionContext cx, Object thisValue, Object number) {
/* steps 1-2 */
double num = ToNumber(cx, number);
/* step 3 */
if (Double.isNaN(num)) {
return true;
}
/* step 4 */
return false;
}
/**
* 18.2.4 parseFloat (string)
*
* @param cx
* the execution context
* @return the parsed number
*/
@Value(name = "parseFloat")
public static Object parseFloat(ExecutionContext cx) {
return Get(cx, cx.getIntrinsic(Intrinsics.Number), "parseFloat");
}
/**
* 18.2.5 parseInt (string , radix)
*
* @param cx
* the execution context
* @return the parsed integer
*/
@Value(name = "parseInt")
public static Object parseInt(ExecutionContext cx) {
return Get(cx, cx.getIntrinsic(Intrinsics.Number), "parseInt");
}
/**
* 18.2.6 URI Handling Functions<br>
* 18.2.6.2 decodeURI (encodedURI)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param encodedURI
* the encoded URI
* @return the decoded URI
*/
@Function(name = "decodeURI", arity = 1)
public static Object decodeURI(ExecutionContext cx, Object thisValue, Object encodedURI) {
/* steps 1-2 */
String uriString = ToFlatString(cx, encodedURI);
/* steps 3-4 */
String decoded = URIFunctions.decodeURI(uriString);
if (decoded == null) {
throw newURIError(cx, Messages.Key.MalformedURI);
}
return decoded;
}
/**
* 18.2.6 URI Handling Functions<br>
* 18.2.6.3 decodeURIComponent (encodedURIComponent)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param encodedURIComponent
* the encoded URI component
* @return the decoded URI component
*/
@Function(name = "decodeURIComponent", arity = 1)
public static Object decodeURIComponent(ExecutionContext cx, Object thisValue,
Object encodedURIComponent) {
/* steps 1-2 */
String componentString = ToFlatString(cx, encodedURIComponent);
/* steps 3-4 */
String decoded = URIFunctions.decodeURIComponent(componentString);
if (decoded == null) {
throw newURIError(cx, Messages.Key.MalformedURI);
}
return decoded;
}
/**
* 18.2.6 URI Handling Functions<br>
* 18.2.6.4 encodeURI (uri)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param uri
* the URI
* @return the encoded URI
*/
@Function(name = "encodeURI", arity = 1)
public static Object encodeURI(ExecutionContext cx, Object thisValue, Object uri) {
/* steps 1-2 */
String uriString = ToFlatString(cx, uri);
/* steps 3-4 */
String encoded = URIFunctions.encodeURI(uriString);
if (encoded == null) {
throw newURIError(cx, Messages.Key.MalformedURI);
}
return encoded;
}
/**
* 18.2.6 URI Handling Functions<br>
* 18.2.6.5 encodeURIComponent (uriComponent)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param uriComponent
* the URI component
* @return the encoded URI component
*/
@Function(name = "encodeURIComponent", arity = 1)
public static Object encodeURIComponent(ExecutionContext cx, Object thisValue,
Object uriComponent) {
/* steps 1-2 */
String componentString = ToFlatString(cx, uriComponent);
/* steps 3-4 */
String encoded = URIFunctions.encodeURIComponent(componentString);
if (encoded == null) {
throw newURIError(cx, Messages.Key.MalformedURI);
}
return encoded;
}
}
/**
* 18.3 Constructor Properties of the Global Object
*/
public enum ConstructorProperties {
;
/**
* 18.3.1 Array ( . . . )
*/
@Value(name = "Array")
public static final Intrinsics Array = Intrinsics.Array;
/**
* 18.3.2 ArrayBuffer ( . . . )
*/
@Value(name = "ArrayBuffer")
public static final Intrinsics ArrayBuffer = Intrinsics.ArrayBuffer;
/**
* 18.3.3 Boolean ( . . . )
*/
@Value(name = "Boolean")
public static final Intrinsics Boolean = Intrinsics.Boolean;
/**
* 18.3.4 DataView ( . . . )
*/
@Value(name = "DataView")
public static final Intrinsics DataView = Intrinsics.DataView;
/**
* 18.3.5 Date ( . . . )
*/
@Value(name = "Date")
public static final Intrinsics Date = Intrinsics.Date;
/**
* 18.3.6 Error ( . . . )
*/
@Value(name = "Error")
public static final Intrinsics Error = Intrinsics.Error;
/**
* 18.3.7 EvalError ( . . . )
*/
@Value(name = "EvalError")
public static final Intrinsics EvalError = Intrinsics.EvalError;
/**
* 18.3.8 Float32Array ( . . . )
*/
@Value(name = "Float32Array")
public static final Intrinsics Float32Array = Intrinsics.Float32Array;
/**
* 18.3.9 Float64Array ( . . . )
*/
@Value(name = "Float64Array")
public static final Intrinsics Float64Array = Intrinsics.Float64Array;
/**
* 18.3.10 Function ( . . . )
*/
@Value(name = "Function")
public static final Intrinsics Function = Intrinsics.Function;
/**
* 18.3.11 Int8Array ( . . . )
*/
@Value(name = "Int8Array")
public static final Intrinsics Int8Array = Intrinsics.Int8Array;
/**
* 18.3.12 Int16Array ( . . . )
*/
@Value(name = "Int16Array")
public static final Intrinsics Int16Array = Intrinsics.Int16Array;
/**
* 18.3.13 Int32Array ( . . . )
*/
@Value(name = "Int32Array")
public static final Intrinsics Int32Array = Intrinsics.Int32Array;
/**
* 18.3.14 Map ( . . . )
*/
@Value(name = "Map")
public static final Intrinsics Map = Intrinsics.Map;
/**
* 18.3.15 Number ( . . . )
*/
@Value(name = "Number")
public static final Intrinsics Number = Intrinsics.Number;
/**
* 18.3.16 Object ( . . . )
*/
@Value(name = "Object")
public static final Intrinsics Object = Intrinsics.Object;
/**
* 18.3.17 Proxy ( . . . )
*/
@Value(name = "Proxy")
public static final Intrinsics Proxy = Intrinsics.Proxy;
/**
* 18.3.18 Promise ( . . . )
*/
@Value(name = "Promise")
public static final Intrinsics Promise = Intrinsics.Promise;
/**
* 18.3.19 RangeError ( . . . )
*/
@Value(name = "RangeError")
public static final Intrinsics RangeError = Intrinsics.RangeError;
/**
* 18.3.20 ReferenceError ( . . . )
*/
@Value(name = "ReferenceError")
public static final Intrinsics ReferenceError = Intrinsics.ReferenceError;
/**
* 18.3.21 RegExp ( . . . )
*/
@Value(name = "RegExp")
public static final Intrinsics RegExp = Intrinsics.RegExp;
/**
* 18.3.22 Set ( . . . )
*/
@Value(name = "Set")
public static final Intrinsics Set = Intrinsics.Set;
/**
* 18.3.23 String ( . . . )
*/
@Value(name = "String")
public static final Intrinsics String = Intrinsics.String;
/**
* 18.3.24 Symbol ( . . . )
*/
@Value(name = "Symbol")
public static final Intrinsics Symbol = Intrinsics.Symbol;
/**
* 18.3.25 SyntaxError ( . . . )
*/
@Value(name = "SyntaxError")
public static final Intrinsics SyntaxError = Intrinsics.SyntaxError;
/**
* 18.3.26 TypeError ( . . . )
*/
@Value(name = "TypeError")
public static final Intrinsics TypeError = Intrinsics.TypeError;
/**
* 18.3.27 UInt8Array ( . . . )
*/
@Value(name = "Uint8Array")
public static final Intrinsics Uint8Array = Intrinsics.Uint8Array;
/**
* 18.3.28 UInt8ClampedArray ( . . . )
*/
@Value(name = "Uint8ClampedArray")
public static final Intrinsics Uint8ClampedArray = Intrinsics.Uint8ClampedArray;
/**
* 18.3.29 UInt16Array ( . . . )
*/
@Value(name = "Uint16Array")
public static final Intrinsics Uint16Array = Intrinsics.Uint16Array;
/**
* 18.3.30 UInt32Array ( . . . )
*/
@Value(name = "Uint32Array")
public static final Intrinsics Uint32Array = Intrinsics.Uint32Array;
/**
* 18.3.31 URIError ( . . . )
*/
@Value(name = "URIError")
public static final Intrinsics URIError = Intrinsics.URIError;
/**
* 18.3.32 WeakMap ( . . . )
*/
@Value(name = "WeakMap")
public static final Intrinsics WeakMap = Intrinsics.WeakMap;
/**
* 18.3.33 WeakSet ( . . . )
*/
@Value(name = "WeakSet")
public static final Intrinsics WeakSet = Intrinsics.WeakSet;
// InternalError
@Value(name = "InternalError")
public static final Intrinsics InternalError = Intrinsics.InternalError;
}
/**
* 18.4 Other Properties of the Global Object
*/
public enum OtherProperties {
;
/**
* 18.4.1 JSON
*/
@Value(name = "JSON")
public static final Intrinsics JSON = Intrinsics.JSON;
/**
* 18.4.2 Math
*/
@Value(name = "Math")
public static final Intrinsics Math = Intrinsics.Math;
/**
* 18.4.3 Reflect
*/
@Value(name = "Reflect")
public static final Intrinsics Reflect = Intrinsics.Reflect;
// Internationalization API
@Value(name = "Intl")
public static final Intrinsics Intl = Intrinsics.Intl;
}
public enum SystemProperty {
;
@Value(name = "System")
public static final Intrinsics System = Intrinsics.System;
}
@CompatibilityExtension(CompatibilityOption.SIMD)
public enum SIMDProperty {
;
@Value(name = "SIMD")
public static final Intrinsics SIMD = Intrinsics.SIMD;
}
@CompatibilityExtension(CompatibilityOption.Observable)
public enum ObservableProperty {
;
@Value(name = "Observable")
public static final Intrinsics Observable = Intrinsics.Observable;
}
@CompatibilityExtension(CompatibilityOption.Atomics)
public enum AtomicsProperties {
;
@Value(name = "Atomics")
public static final Intrinsics Atomics = Intrinsics.Atomics;
@Value(name = "SharedArrayBuffer")
public static final Intrinsics SharedArrayBuffer = Intrinsics.SharedArrayBuffer;
}
/**
* B.2.1 Additional Properties of the Global Object
*/
@CompatibilityExtension(CompatibilityOption.GlobalObject)
public enum AdditionalProperties {
;
private static int fromHexDigit(char c) {
if (c >= '0' && c <= '9')
return (c - '0');
if (c >= 'A' && c <= 'F')
return (c - 'A') + 10;
if (c >= 'a' && c <= 'f')
return (c - 'a') + 10;
return -1;
}
private static char toHexDigit(int i, int shift) {
i = (i >> shift) & 0b1111;
return (char) (i + (i < 0x0A ? '0' : ('A' - 10)));
}
/**
* B.2.1.1 escape (string)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param string
* the string
* @return the escaped string
*/
@Function(name = "escape", arity = 1)
public static Object escape(ExecutionContext cx, Object thisValue, Object string) {
/* steps 1-2 */
String s = ToFlatString(cx, string);
/* step 3 */
int length = s.length();
/* step 4 */
StringBuilder r = new StringBuilder(length);
/* steps 5-6 */
for (int k = 0; k < length; ++k) {
char c = s.charAt(k);
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')
|| c == '@' || c == '*' || c == '_' || c == '+' || c == '-' || c == '.'
|| c == '/') {
r.append(c);
} else if (c < 256) {
r.append('%').append(toHexDigit(c, 4)).append(toHexDigit(c, 0));
} else {
r.append("%u").append(toHexDigit(c, 12)).append(toHexDigit(c, 8))
.append(toHexDigit(c, 4)).append(toHexDigit(c, 0));
}
}
/* step 7 */
return r.toString();
}
/**
* B.2.1.2 unescape (string)
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param string
* the string
* @return the unescaped string
*/
@Function(name = "unescape", arity = 1)
public static Object unescape(ExecutionContext cx, Object thisValue, Object string) {
/* steps 1-2 */
String s = ToFlatString(cx, string);
/* step 3 */
int length = s.length();
/* step 4 */
StringBuilder r = new StringBuilder(length);
/* steps 5-6 */
for (int k = 0; k < length; ++k) {
char c = s.charAt(k);
if (c == '%') {
if (k <= length - 6 && s.charAt(k + 1) == 'u') {
char c2 = s.charAt(k + 2);
char c3 = s.charAt(k + 3);
char c4 = s.charAt(k + 4);
char c5 = s.charAt(k + 5);
int h = fromHexDigit(c2) << 12 | fromHexDigit(c3) << 8
| fromHexDigit(c4) << 4 | fromHexDigit(c5);
if (h >= 0) {
k += 5;
c = (char) h;
}
} else if (k <= length - 3) {
char c1 = s.charAt(k + 1);
char c2 = s.charAt(k + 2);
int h = fromHexDigit(c1) << 4 | fromHexDigit(c2);
if (h >= 0) {
k += 2;
c = (char) h;
}
}
}
r.append(c);
}
/* step 7 */
return r.toString();
}
}
/**
* 18.2.6 URI Handling Functions
*/
private static final class URIFunctions {
private URIFunctions() {
}
/**
* 18.2.6.2 decodeURI (encodedURI)
*
* @param encodedURI
* the encoded URI
* @return the decoded URI or {@code null} if invalid
*/
public static String decodeURI(String encodedURI) {
return decode(encodedURI, RESERVED_LO | HASH, RESERVED_HI);
}
/**
* 18.2.6.3 decodeURIComponent (encodedURIComponent)
*
* @param encodedURIComponent
* the encoded URI component
* @return the decoded URI component or {@code null} if invalid
*/
public static String decodeURIComponent(String encodedURIComponent) {
return decode(encodedURIComponent, 0, 0);
}
/**
* 18.2.6.4 encodeURI (uri)
*
* @param uri
* the URI
* @return the encoded URI or {@code null} if invalid
*/
public static String encodeURI(String uri) {
return encode(uri, RESERVED_LO | UNESCAPED_LO | HASH, RESERVED_HI | UNESCAPED_HI);
}
/**
* 18.2.6.5 encodeURIComponent (uriComponent)
*
* @param uriComponent
* the URI component
* @return the encoded URI component or {@code null} if invalid
*/
public static String encodeURIComponent(String uriComponent) {
return encode(uriComponent, UNESCAPED_LO, UNESCAPED_HI);
}
// reserved = ; / ? : @ & = + $ ,
private static final long RESERVED_LO = 0b10101100_00000000_10011000_01010000_00000000_00000000_00000000_00000000L;
private static final long RESERVED_HI = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000001L;
// alpha = a-z A-Z
private static final long ALPHA_LO = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L;
private static final long ALPHA_HI = 0b00000111_11111111_11111111_11111110_00000111_11111111_11111111_11111110L;
// digit = 0-9
private static final long DIGIT_LO = 0b00000011_11111111_00000000_00000000_00000000_00000000_00000000_00000000L;
private static final long DIGIT_HI = 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000L;
// mark = - _ . ! ~ * ' ( )
private static final long MARK_LO = 0b00000000_00000000_01100111_10000010_00000000_00000000_00000000_00000000L;
private static final long MARK_HI = 0b01000000_00000000_00000000_00000000_10000000_00000000_00000000_00000000L;
// unescaped = alpha | digit | mark
private static final long UNESCAPED_LO = ALPHA_LO | DIGIT_LO | MARK_LO;
private static final long UNESCAPED_HI = ALPHA_HI | DIGIT_HI | MARK_HI;
private static final long HASH = 0b00000000_00000000_00000000_00001000_00000000_00000000_00000000_00000000L;
static {
assert HASH == low('#');
assert RESERVED_LO == low(";/?:&=+$,");
assert RESERVED_HI == high("@");
assert ALPHA_LO == low("");
assert ALPHA_HI == high("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
assert DIGIT_LO == low("0123456789");
assert DIGIT_HI == high("");
assert MARK_LO == low("-.!*'()");
assert MARK_HI == high("_~");
}
private static int readNibble(char c) {
if (c >= 'a') {
return (c <= 'f') ? (c - ('a' - 10)) : -1;
}
if (c >= 'A') {
return (c <= 'F') ? (c - ('A' - 10)) : -1;
}
return (c >= '0' && c <= '9') ? (c - '0') : -1;
}
private static int readByte(String s, int i) {
if (s.charAt(i) != '%')
return -1;
return (readNibble(s.charAt(i + 1)) << 4) | readNibble(s.charAt(i + 2));
}
private static StringBuilder writeByte(StringBuilder sb, int b) {
int b0 = (b >> 4) & 0b1111;
int b1 = b & 0b1111;
b0 = b0 + (b0 < 0x0A ? '0' : ('A' - 10));
b1 = b1 + (b1 < 0x0A ? '0' : ('A' - 10));
return sb.append('%').append((char) b0).append((char) b1);
}
/**
* 18.2.6.1.1 Runtime Semantics: Encode ( string, unescapedSet )
* <p>
* Returns the encoded string or {@code null} on error.
*
* @param s
* the string
* @param low
* the low bit set
* @param high
* the high bit set
* @return the encoded string
*/
private static String encode(String s, long low, long high) {
final int length = s.length();
int j = 0;
StringBuilder sb = null;
for (int i = 0; i < length; ++i) {
char c = s.charAt(i);
if (masked(c, low, high)) {
continue;
}
if (sb == null) {
// 10 = 5 * encoded ASCII or 1 * encoded supplementary character
sb = new StringBuilder(length + 10);
}
if (j < i) {
sb.append(s, j, i);
}
if (c <= 0x7F) {
writeByte(sb, c);
} else if (c <= 0x7FF) {
writeByte(sb, 0b11000000 | ((c >> 6) & 0b11111));
writeByte(sb, 0b10000000 | (c & 0b111111));
} else if (c <= 0xD7FF || c >= 0xE000) {
writeByte(sb, 0b11100000 | ((c >> 12) & 0b1111));
writeByte(sb, 0b10000000 | ((c >> 6) & 0b111111));
writeByte(sb, 0b10000000 | (c & 0b111111));
} else if (Character.isHighSurrogate(c) && i + 1 < length) {
char d = s.charAt(i + 1);
if (!Character.isLowSurrogate(d)) {
// lone high surrogate
return null;
}
int cp = Character.toCodePoint(c, d);
writeByte(sb, 0b11110000 | ((cp >> 18) & 0b111));
writeByte(sb, 0b10000000 | ((cp >> 12) & 0b111111));
writeByte(sb, 0b10000000 | ((cp >> 6) & 0b111111));
writeByte(sb, 0b10000000 | (cp & 0b111111));
// Read two chars, increment i accordingly.
i += 1;
} else {
// lone surrogate
return null;
}
j = i + 1;
}
if (sb == null) {
return s;
}
if (j < length) {
sb.append(s, j, length);
}
return sb.toString();
}
/**
* 18.2.6.1.2 Runtime Semantics: Decode ( string, reservedSet )
* <p>
* Returns the decoded string or {@code null} on error.
*
* @param s
* the string
* @param low
* the low bit set
* @param high
* the high bit set
* @return the decoded string
*/
private static String decode(String s, long low, long high) {
int i = s.indexOf('%');
if (i < 0) {
return s;
}
final int length = s.length();
int j = 0;
StringBuilder sb = null;
while (i >= 0) {
if (i + 2 >= length)
return null;
int c0 = readByte(s, i);
if (c0 < 0)
return null;
int cp;
if (c0 <= 0b01111111) {
// US-ASCII
if (masked(c0, low, high)) {
// leave as-is
i = s.indexOf('%', i + 3);
continue;
}
cp = (char) c0;
} else {
cp = decodeNonASCII(c0, s, i, length);
if (cp < 0)
return null;
}
int k;
if (c0 <= 0b10111111) {
// US-ASCII
// or: illegal (byte sequence part)
k = 3;
} else if (c0 <= 0b11011111) {
// two byte sequence
// or: illegal (two byte encoding for US-ASCII)
k = 6;
} else if (c0 <= 0b11101111) {
// three byte sequence
k = 9;
} else {
// four byte sequence
// or: illegal (cf. RFC-3629)
k = 12;
}
if (sb == null) {
if (i == 0 && k == length) {
// Single character escape
return Strings.fromCodePoint(cp);
}
sb = new StringBuilder(length);
}
if (j < i) {
// append substring before '%'
sb.append(s, j, i);
}
sb.appendCodePoint(cp);
j = i + k;
i = s.indexOf('%', j);
}
if (sb == null) {
return s;
}
if (j < length) {
// append remaining substring
sb.append(s, j, length);
}
return sb.toString();
}
private static int decodeNonASCII(int c0, String s, int i, int len) {
if (c0 < 0)
return -1;
if (c0 <= 0b01111111) {
// US-ASCII (handled in caller)
return -1;
} else if (c0 <= 0b10111111) {
// illegal (byte sequence part)
return -1;
} else if (c0 <= 0b11000001) {
// illegal (two byte encoding for US-ASCII)
return -1;
} else if (c0 <= 0b11011111) {
// two byte sequence
if (!(i + 2 + 3 < len))
return -1;
int c1 = readByte(s, i + 3);
if (c1 < 0x80 || c1 > 0xBF)
return -1;
return (char) ((c0 & 0b11111) << 6 | (c1 & 0b111111));
} else if (c0 <= 0b11101111) {
// three byte sequence
if (!(i + 2 + 6 < len))
return -1;
int c1 = readByte(s, i + 3);
int c2 = readByte(s, i + 6);
if (c0 == 0b11100000 ? (c1 < 0xA0 || c1 > 0xBF)
: c0 == 0b11101101 ? (c1 < 0x80 || c1 > 0x9F) : (c1 < 0x80 || c1 > 0xBF))
return -1;
if (c2 < 0x80 || c2 > 0xBF)
return -1;
return (char) ((c0 & 0b1111) << 12 | (c1 & 0b111111) << 6 | (c2 & 0b111111));
} else if (c0 <= 0b11110100) {
// four byte sequence
if (!(i + 2 + 9 < len))
return -1;
int c1 = readByte(s, i + 3);
int c2 = readByte(s, i + 6);
int c3 = readByte(s, i + 9);
if (c0 == 0b11110000 ? (c1 < 0x90 || c1 > 0xBF)
: c0 == 0b11110100 ? (c1 < 0x80 || c1 > 0x8F) : (c1 < 0x80 || c1 > 0xBF))
return -1;
if (c2 < 0x80 || c3 < 0x80 || c2 > 0xBF || c3 > 0xBF)
return -1;
int cp = ((c0 & 0b111) << 18 | (c1 & 0b111111) << 12 | (c2 & 0b111111) << 6 | (c3 & 0b111111));
if (cp <= Character.MAX_CODE_POINT) {
return cp;
} else {
// illegal code point
return -1;
}
} else {
// illegal (cf. RFC-3629)
return -1;
}
}
/* embedded BitMaskUtil */
private static boolean masked(char c, long low, long high) {
return (c == 0) ? false : //
(c < 64) ? ((1L << c) & low) != 0 : //
(c < 128) ? ((1L << (c - 64)) & high) != 0 : false;
}
private static boolean masked(int c, long low, long high) {
return (c == 0) ? false : //
(c < 64) ? ((1L << c) & low) != 0 : //
(c < 128) ? ((1L << (c - 64)) & high) != 0 : false;
}
private static long low(String s) {
return low(s.toCharArray());
}
private static long high(String s) {
return high(s.toCharArray());
}
private static long low(char[] cs) {
long lo = 0;
for (char c : cs) {
assert c < 64;
lo |= (1L << c);
}
return lo;
}
private static long high(char[] cs) {
long hi = 0;
for (char c : cs) {
assert c >= 64 && c < 128;
hi |= (1L << (c - 64));
}
return hi;
}
private static long low(char c) {
assert c < 64;
return (1L << c);
}
@SuppressWarnings("unused")
private static long high(char c) {
assert c >= 64 && c < 128;
return (1L << (c - 64));
}
}
}