1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import static jdk.nashorn.internal.lookup.Lookup.MH;
  29 
  30 import java.lang.invoke.MethodHandle;
  31 import java.lang.invoke.MethodHandles;
  32 import java.util.Locale;
  33 
  34 /**
  35  * Utilities used by Global class.
  36  */
  37 public final class GlobalFunctions {
  38 
  39     /** Methodhandle to implementation of ECMA 15.1.2.2, parseInt */
  40     public static final MethodHandle PARSEINT = findOwnMH("parseInt", double.class, Object.class, Object.class, Object.class);
  41 
  42     /** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */
  43     public static final MethodHandle PARSEINT_OI = findOwnMH("parseInt", double.class, Object.class, Object.class, int.class);
  44 
  45     /** Methodhandle (specialized) to implementation of ECMA 15.1.2.2, parseInt */
  46     public static final MethodHandle PARSEINT_O = findOwnMH("parseInt", double.class, Object.class, Object.class);
  47 
  48     /** Methodhandle to implementation of ECMA 15.1.2.3, parseFloat */
  49     public static final MethodHandle PARSEFLOAT = findOwnMH("parseFloat", double.class, Object.class, Object.class);
  50 
  51     /** Methodhandle to implementation of ECMA 15.1.2.4, isNaN */
  52     public static final MethodHandle IS_NAN = findOwnMH("isNaN",      boolean.class, Object.class, Object.class);
  53 
  54     /** Methodhandle to implementation of ECMA 15.1.2.5, isFinite */
  55     public static final MethodHandle IS_FINITE = findOwnMH("isFinite",   boolean.class, Object.class, Object.class);
  56 
  57     /** Methodhandle to implementation of ECMA 15.1.3.3, encodeURI */
  58     public static final MethodHandle ENCODE_URI = findOwnMH("encodeURI",  Object.class, Object.class, Object.class);
  59 
  60     /** Methodhandle to implementation of ECMA 15.1.3.4, encodeURIComponent */
  61     public static final MethodHandle ENCODE_URICOMPONENT = findOwnMH("encodeURIComponent", Object.class, Object.class, Object.class);
  62 
  63     /** Methodhandle to implementation of ECMA 15.1.3.1, decodeURI */
  64     public static final MethodHandle DECODE_URI = findOwnMH("decodeURI", Object.class, Object.class, Object.class);
  65 
  66     /** Methodhandle to implementation of ECMA 15.1.3.2, decodeURIComponent */
  67     public static final MethodHandle DECODE_URICOMPONENT = findOwnMH("decodeURIComponent", Object.class, Object.class, Object.class);
  68 
  69     /** Methodhandle to implementation of ECMA B.2.1, escape */
  70     public static final MethodHandle ESCAPE = findOwnMH("escape",    String.class, Object.class, Object.class);
  71 
  72     /** Methodhandle to implementation of ECMA B.2.2, unescape */
  73     public static final MethodHandle UNESCAPE = findOwnMH("unescape",  String.class, Object.class, Object.class);
  74 
  75     /** Methodhandle to implementation of ECMA 15.3.4, "anonymous" - Properties of the Function Prototype Object. */
  76     public static final MethodHandle ANONYMOUS = findOwnMH("anonymous", Object.class, Object.class);
  77 
  78     private static final String UNESCAPED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
  79 
  80     private GlobalFunctions() {
  81     }
  82 
  83     /**
  84      * ECMA 15.1.2.2 parseInt implementation
  85      *
  86      * @param self   self reference
  87      * @param string string to parse
  88      * @param rad    radix
  89      *
  90      * @return numeric type representing string contents as an int
  91      */
  92     public static double parseInt(final Object self, final Object string, final Object rad) {
  93         return parseIntInternal(JSType.trimLeft(JSType.toString(string)), JSType.toInt32(rad));
  94     }
  95 
  96     /**
  97      * ECMA 15.1.2.2 parseInt implementation specialized for int radix
  98      *
  99      * @param self   self reference
 100      * @param string string to parse
 101      * @param rad    radix
 102      *
 103      * @return numeric type representing string contents as an int
 104      */
 105     public static double parseInt(final Object self, final Object string, final int rad) {
 106         return parseIntInternal(JSType.trimLeft(JSType.toString(string)), rad);
 107     }
 108 
 109     /**
 110      * ECMA 15.1.2.2 parseInt implementation specialized for no radix argument
 111      *
 112      * @param self   self reference
 113      * @param string string to parse
 114      *
 115      * @return numeric type representing string contents as an int
 116      */
 117     public static double parseInt(final Object self, final Object string) {
 118         return parseIntInternal(JSType.trimLeft(JSType.toString(string)), 0);
 119     }
 120 
 121     private static double parseIntInternal(final String str, final int rad) {
 122         final int length = str.length();
 123         int radix = rad;
 124 
 125         // empty string is not valid
 126         if (length == 0) {
 127             return Double.NaN;
 128         }
 129 
 130         boolean negative = false;
 131         int idx = 0;
 132 
 133         // checking for the sign character
 134         final char firstChar = str.charAt(idx);
 135         if (firstChar < '0') {
 136             // Possible leading "+" or "-"
 137             if (firstChar == '-') {
 138                 negative = true;
 139             } else if (firstChar != '+') {
 140                 return Double.NaN;
 141             }
 142             // skip the sign character
 143             idx++;
 144         }
 145 
 146         boolean stripPrefix = true;
 147 
 148         if (radix != 0) {
 149             if (radix < 2 || radix > 36) {
 150                 return Double.NaN;
 151             }
 152             if (radix != 16) {
 153                 stripPrefix = false;
 154             }
 155         } else {
 156             // default radix
 157             radix = 10;
 158         }
 159         // strip "0x" or "0X" and treat radix as 16
 160         if (stripPrefix && ((idx + 1) < length)) {
 161             final char c1 = str.charAt(idx);
 162             final char c2 = str.charAt(idx + 1);
 163             if (c1 == '0' && (c2 == 'x' || c2 == 'X')) {
 164                 radix = 16;
 165                 // skip "0x" or "0X"
 166                 idx += 2;
 167             }
 168         }
 169 
 170         double result = 0.0;
 171         int digit;
 172         // we should see atleast one valid digit
 173         boolean entered = false;
 174         while (idx < length) {
 175             digit = fastDigit(str.charAt(idx++), radix);
 176             if (digit < 0) {
 177                 break;
 178             }
 179             // we have seen atleast one valid digit in the specified radix
 180             entered = true;
 181             result *= radix;
 182             result += digit;
 183         }
 184 
 185         return entered ? (negative ? -result : result) : Double.NaN;
 186     }
 187 
 188     /**
 189      * ECMA 15.1.2.3 parseFloat implementation
 190      *
 191      * @param self   self reference
 192      * @param string string to parse
 193      *
 194      * @return numeric type representing string contents
 195      */
 196     public static double parseFloat(final Object self, final Object string) {
 197         final String str    = JSType.trimLeft(JSType.toString(string));
 198         final int    length = str.length();
 199 
 200         // empty string is not valid
 201         if (length == 0) {
 202             return Double.NaN;
 203         }
 204 
 205         int     start    = 0;
 206         boolean negative = false;
 207         char    ch       = str.charAt(0);
 208 
 209         if (ch == '-') {
 210             start++;
 211             negative = true;
 212         } else if (ch == '+') {
 213             start++;
 214         } else if (ch == 'N') {
 215             if (str.startsWith("NaN")) {
 216                 return Double.NaN;
 217             }
 218         }
 219 
 220         if (start == length) {
 221             // just the sign character
 222             return Double.NaN;
 223         }
 224 
 225         ch = str.charAt(start);
 226         if (ch == 'I') {
 227             if (str.substring(start).startsWith("Infinity")) {
 228                 return negative? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
 229             }
 230         }
 231 
 232         boolean dotSeen    = false;
 233         boolean exponentOk = false;
 234         int exponentOffset = -1;
 235         int end;
 236 
 237 loop:
 238         for (end = start; end < length; end++) {
 239             ch = str.charAt(end);
 240 
 241             switch (ch) {
 242             case '.':
 243                 // dot allowed only once
 244                 if (exponentOffset != -1 || dotSeen) {
 245                     break loop;
 246                 }
 247                 dotSeen = true;
 248                 break;
 249 
 250             case 'e':
 251             case 'E':
 252                 // 'e'/'E' allow only once
 253                 if (exponentOffset != -1) {
 254                     break loop;
 255                 }
 256                 exponentOffset = end;
 257                 break;
 258 
 259             case '+':
 260             case '-':
 261                 // Sign of the exponent. But allowed only if the
 262                 // previous char in the string was 'e' or 'E'.
 263                 if (exponentOffset != end - 1) {
 264                     break loop;
 265                 }
 266                 break;
 267 
 268             case '0':
 269             case '1':
 270             case '2':
 271             case '3':
 272             case '4':
 273             case '5':
 274             case '6':
 275             case '7':
 276             case '8':
 277             case '9':
 278                 if (exponentOffset != -1) {
 279                     // seeing digit after 'e' or 'E'
 280                     exponentOk = true;
 281                 }
 282                 break;
 283 
 284             default: // ignore garbage at the end
 285                 break loop;
 286             }
 287         }
 288 
 289         // ignore 'e'/'E' followed by '+/-' if not real exponent found
 290         if (exponentOffset != -1 && !exponentOk) {
 291             end = exponentOffset;
 292         }
 293 
 294         if (start == end) {
 295             return Double.NaN;
 296         }
 297 
 298         try {
 299             final double result = Double.valueOf(str.substring(start, end));
 300             return negative ? -result : result;
 301         } catch (final NumberFormatException e) {
 302             return Double.NaN;
 303         }
 304     }
 305 
 306     /**
 307      * ECMA 15.1.2.4, isNaN implementation
 308      *
 309      * @param self    self reference
 310      * @param number  number to check
 311      *
 312      * @return true if number is NaN
 313      */
 314     public static boolean isNaN(final Object self, final Object number) {
 315         return Double.isNaN(JSType.toNumber(number));
 316     }
 317 
 318     /**
 319      * ECMA 15.1.2.5, isFinite implementation
 320      *
 321      * @param self   self reference
 322      * @param number number to check
 323      *
 324      * @return true if number is infinite
 325      */
 326     public static boolean isFinite(final Object self, final Object number) {
 327         final double value = JSType.toNumber(number);
 328         return ! (Double.isInfinite(value) || Double.isNaN(value));
 329     }
 330 
 331 
 332     /**
 333      * ECMA 15.1.3.3, encodeURI implementation
 334      *
 335      * @param self  self reference
 336      * @param uri   URI to encode
 337      *
 338      * @return encoded URI
 339      */
 340     public static Object encodeURI(final Object self, final Object uri) {
 341         return URIUtils.encodeURI(self, JSType.toString(uri));
 342     }
 343 
 344     /**
 345      * ECMA 15.1.3.4, encodeURIComponent implementation
 346      *
 347      * @param self  self reference
 348      * @param uri   URI component to encode
 349      *
 350      * @return encoded URIComponent
 351      */
 352     public static Object encodeURIComponent(final Object self, final Object uri) {
 353         return URIUtils.encodeURIComponent(self, JSType.toString(uri));
 354     }
 355 
 356     /**
 357      * ECMA 15.1.3.1, decodeURI implementation
 358      *
 359      * @param self  self reference
 360      * @param uri   URI to decode
 361      *
 362      * @return decoded URI
 363      */
 364     public static Object decodeURI(final Object self, final Object uri) {
 365         return URIUtils.decodeURI(self, JSType.toString(uri));
 366     }
 367 
 368     /**
 369      * ECMA 15.1.3.2, decodeURIComponent implementation
 370      *
 371      * @param self  self reference
 372      * @param uri   URI component to encode
 373      *
 374      * @return decoded URI
 375      */
 376     public static Object decodeURIComponent(final Object self, final Object uri) {
 377         return URIUtils.decodeURIComponent(self, JSType.toString(uri));
 378     }
 379 
 380     /**
 381      * ECMA B.2.1, escape implementation
 382      *
 383      * @param self    self reference
 384      * @param string  string to escape
 385      *
 386      * @return escaped string
 387      */
 388     public static String escape(final Object self, final Object string) {
 389         final String str = JSType.toString(string);
 390         final int length = str.length();
 391 
 392         if (length == 0) {
 393             return str;
 394         }
 395 
 396         final StringBuilder sb = new StringBuilder();
 397         for (int k = 0; k < length; k++) {
 398             final char ch = str.charAt(k);
 399             if (UNESCAPED.indexOf(ch) != -1) {
 400                 sb.append(ch);
 401             } else if (ch < 256) {
 402                 sb.append('%');
 403                 if (ch < 16) {
 404                     sb.append('0');
 405                 }
 406                 sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH));
 407             } else {
 408                 sb.append("%u");
 409                 if (ch < 4096) {
 410                     sb.append('0');
 411                 }
 412                 sb.append(Integer.toHexString(ch).toUpperCase(Locale.ENGLISH));
 413             }
 414         }
 415 
 416         return sb.toString();
 417     }
 418 
 419     /**
 420      * ECMA B.2.2, unescape implementation
 421      *
 422      * @param self    self reference
 423      * @param string  string to unescape
 424      *
 425      * @return unescaped string
 426      */
 427     public static String unescape(final Object self, final Object string) {
 428         final String str    = JSType.toString(string);
 429         final int    length = str.length();
 430 
 431         if (length == 0) {
 432             return str;
 433         }
 434 
 435         final StringBuilder sb = new StringBuilder();
 436         for (int k = 0; k < length; k++) {
 437             char ch = str.charAt(k);
 438             if (ch != '%') {
 439                 sb.append(ch);
 440             } else {
 441                 if (k < (length - 5)) {
 442                    if (str.charAt(k + 1) == 'u') {
 443                        try {
 444                            ch = (char) Integer.parseInt(str.substring(k + 2, k + 6), 16);
 445                            sb.append(ch);
 446                            k += 5;
 447                            continue;
 448                        } catch (final NumberFormatException e) {
 449                            //ignored
 450                        }
 451                    }
 452                 }
 453 
 454                 if (k < (length - 2)) {
 455                     try {
 456                         ch = (char) Integer.parseInt(str.substring(k + 1, k + 3), 16);
 457                         sb.append(ch);
 458                         k += 2;
 459                         continue;
 460                     } catch (final NumberFormatException e) {
 461                         //ignored
 462                     }
 463                 }
 464 
 465                 // everything fails
 466                 sb.append(ch);
 467             }
 468         }
 469 
 470         return sb.toString();
 471     }
 472 
 473 
 474     /**
 475      * ECMA 15.3.4 Properties of the Function Prototype Object.
 476      * The Function prototype object is itself a Function object
 477      * (its [[Class]] is "Function") that, when invoked, accepts
 478      * any arguments and returns undefined. This method is used to
 479      * implement that anonymous function.
 480      *
 481      * @param self  self reference
 482      *
 483      * @return undefined
 484      */
 485     public static Object anonymous(final Object self) {
 486         return ScriptRuntime.UNDEFINED;
 487     }
 488 
 489     private static int fastDigit(int ch, int radix) {
 490         int n = -1;
 491         if (ch >= '0' && ch <= '9') {
 492             n = ch - '0';
 493         } else if (radix > 10) {
 494             if (ch >= 'a' && ch <= 'z') {
 495                 n = ch - 'a' + 10;
 496             } else if (ch >= 'A' && ch <= 'Z') {
 497                 n = ch - 'A' + 10;
 498             }
 499         }
 500         return n < radix ? n : -1;
 501     }
 502 
 503     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 504         return MH.findStatic(MethodHandles.lookup(), GlobalFunctions.class, name, MH.type(rtype, types));
 505     }
 506 
 507 }