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.runtime.JSType.digit;
  29 import static jdk.nashorn.internal.lookup.Lookup.MH;
  30 
  31 import java.lang.invoke.MethodHandle;
  32 import java.lang.invoke.MethodHandles;
  33 import java.util.Locale;
  34 
  35 /**
  36  * Utilities used by Global class.
  37  *
  38  * These are actual implementation methods for functions exposed by global
  39  * scope. The code lives here to share the code across the contexts.
  40  */
  41 public final class GlobalFunctions {
  42 
  43     /** Methodhandle to implementation of ECMA 15.1.2.2, parseInt */
  44     public static final MethodHandle PARSEINT = findOwnMH("parseInt",   double.class, Object.class, Object.class, Object.class);
  45 
  46     /** Methodhandle to implementation of ECMA 15.1.2.3, parseFloat */
  47     public static final MethodHandle PARSEFLOAT = findOwnMH("parseFloat", double.class, Object.class, Object.class);
  48 
  49     /** Methodhandle to implementation of ECMA 15.1.2.4, isNaN */
  50     public static final MethodHandle IS_NAN = findOwnMH("isNaN",      boolean.class, Object.class, Object.class);
  51 
  52     /** Methodhandle to implementation of ECMA 15.1.2.5, isFinite */
  53     public static final MethodHandle IS_FINITE = findOwnMH("isFinite",   boolean.class, Object.class, Object.class);
  54 
  55     /** Methodhandle to implementation of ECMA 15.1.3.3, encodeURI */
  56     public static final MethodHandle ENCODE_URI = findOwnMH("encodeURI",  Object.class, Object.class, Object.class);
  57 
  58     /** Methodhandle to implementation of ECMA 15.1.3.4, encodeURIComponent */
  59     public static final MethodHandle ENCODE_URICOMPONENT = findOwnMH("encodeURIComponent", Object.class, Object.class, Object.class);
  60 
  61     /** Methodhandle to implementation of ECMA 15.1.3.1, decodeURI */
  62     public static final MethodHandle DECODE_URI = findOwnMH("decodeURI", Object.class, Object.class, Object.class);
  63 
  64     /** Methodhandle to implementation of ECMA 15.1.3.2, decodeURIComponent */
  65     public static final MethodHandle DECODE_URICOMPONENT = findOwnMH("decodeURIComponent", Object.class, Object.class, Object.class);
  66 
  67     /** Methodhandle to implementation of ECMA B.2.1, escape */
  68     public static final MethodHandle ESCAPE = findOwnMH("escape",    String.class, Object.class, Object.class);
  69 
  70     /** Methodhandle to implementation of ECMA B.2.2, unescape */
  71     public static final MethodHandle UNESCAPE = findOwnMH("unescape",  String.class, Object.class, Object.class);
  72 
  73     /** Methodhandle to implementation of ECMA 15.3.4, "anonymous" - Properties of the Function Prototype Object. */
  74     public static final MethodHandle ANONYMOUS = findOwnMH("anonymous", Object.class, Object.class);
  75 
  76     private static final String UNESCAPED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_+-./";
  77 
  78     private GlobalFunctions() {
  79     }
  80 
  81     /**
  82      * ECMA 15.1.2.2 parseInt implementation
  83      *
  84      * TODO: specialize
  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 (TODO: specialize for int case)
  91      */
  92     //TODO specialize
  93     public static double parseInt(final Object self, final Object string, final Object rad) {
  94         final String str    = JSType.trimLeft(JSType.toString(string));
  95         final int    length = str.length();
  96 
  97         // empty string is not valid
  98         if (length == 0) {
  99             return Double.NaN;
 100         }
 101 
 102         boolean negative = false;
 103         int idx = 0;
 104 
 105         // checking for the sign character
 106         final char firstChar = str.charAt(idx);
 107         if (firstChar < '0') {
 108             // Possible leading "+" or "-"
 109             if (firstChar == '-') {
 110                 negative = true;
 111             } else if (firstChar != '+') {
 112                 return Double.NaN;
 113             }
 114             // skip the sign character
 115             idx++;
 116         }
 117 
 118         boolean stripPrefix = true;
 119         int     radix = JSType.toInt32(rad);
 120 
 121         if (radix != 0) {
 122             if (radix < 2 || radix > 36) {
 123                 return Double.NaN;
 124             }
 125             if (radix != 16) {
 126                 stripPrefix = false;
 127             }
 128         } else {
 129             // default radix
 130             radix = 10;
 131         }
 132         // strip "0x" or "0X" and treat radix as 16
 133         if (stripPrefix && ((idx + 1) < length)) {
 134             final char c1 = str.charAt(idx);
 135             final char c2 = str.charAt(idx + 1);
 136             if (c1 == '0' && (c2 == 'x' || c2 == 'X')) {
 137                 radix = 16;
 138                 // skip "0x" or "0X"
 139                 idx += 2;
 140             }
 141         }
 142 
 143         double result = 0.0;
 144         int digit;
 145         // we should see atleast one valid digit
 146         boolean entered = false;
 147         while (idx < length) {
 148             digit = digit(str.charAt(idx++), radix, true);
 149             if (digit < 0) {
 150                 break;
 151             }
 152             // we have seen atleast one valid digit in the specified radix
 153             entered = true;
 154             result *= radix;
 155             result += digit;
 156         }
 157 
 158         return entered ? (negative ? -result : result) : Double.NaN;
 159     }
 160 
 161     /**
 162      * ECMA 15.1.2.3 parseFloat implementation
 163      *
 164      * @param self   self reference
 165      * @param string string to parse
 166      *
 167      * @return numeric type representing string contents
 168      */
 169     public static double parseFloat(final Object self, final Object string) {
 170         final String str    = JSType.trimLeft(JSType.toString(string));
 171         final int    length = str.length();
 172 
 173         // empty string is not valid
 174         if (length == 0) {
 175             return Double.NaN;
 176         }
 177 
 178         int     start    = 0;
 179         boolean negative = false;
 180         char    ch       = str.charAt(0);
 181 
 182         if (ch == '-') {
 183             start++;
 184             negative = true;
 185         } else if (ch == '+') {
 186             start++;
 187         } else if (ch == 'N') {
 188             if (str.startsWith("NaN")) {
 189                 return Double.NaN;
 190             }
 191         }
 192 
 193         if (start == length) {
 194             // just the sign character
 195             return Double.NaN;
 196         }
 197 
 198         ch = str.charAt(start);
 199         if (ch == 'I') {
 200             if (str.substring(start).startsWith("Infinity")) {
 201                 return negative? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
 202             }
 203         }
 204 
 205         boolean dotSeen    = false;
 206         boolean exponentOk = false;
 207         int exponentOffset = -1;
 208         int end;
 209 
 210 loop:
 211         for (end = start; end < length; end++) {
 212             ch = str.charAt(end);
 213 
 214             switch (ch) {
 215             case '.':
 216                 // dot allowed only once
 217                 if (dotSeen) {
 218                     break loop;
 219                 }
 220                 dotSeen = true;
 221                 break;
 222 
 223             case 'e':
 224             case 'E':
 225                 // 'e'/'E' allow only once
 226                 if (exponentOffset != -1) {
 227                     break loop;
 228                 }
 229                 exponentOffset = end;
 230                 break;
 231 
 232             case '+':
 233             case '-':
 234                 // Sign of the exponent. But allowed only if the
 235                 // previous char in the string was 'e' or 'E'.
 236                 if (exponentOffset != end - 1) {
 237                     break loop;
 238                 }
 239                 break;
 240 
 241             case '0':
 242             case '1':
 243             case '2':
 244             case '3':
 245             case '4':
 246             case '5':
 247             case '6':
 248             case '7':
 249             case '8':
 250             case '9':
 251                 if (exponentOffset != -1) {
 252                     // seeing digit after 'e' or 'E'
 253                     exponentOk = true;
 254                 }
 255                 break;
 256 
 257             default: // ignore garbage at the end
 258                 break loop;
 259             }
 260         }
 261 
 262         // ignore 'e'/'E' followed by '+/-' if not real exponent found
 263         if (exponentOffset != -1 && !exponentOk) {
 264             end = exponentOffset;
 265         }
 266 
 267         if (start == end) {
 268             return Double.NaN;
 269         }
 270 
 271         try {
 272             final double result = Double.valueOf(str.substring(start, end));
 273             return negative ? -result : result;
 274         } catch (final NumberFormatException e) {
 275             return Double.NaN;
 276         }
 277     }
 278 
 279     /**
 280      * ECMA 15.1.2.4, isNaN implementation
 281      *
 282      * @param self    self reference
 283      * @param number  number to check
 284      *
 285      * @return true if number is NaN
 286      */
 287     public static boolean isNaN(final Object self, final Object number) {
 288         return Double.isNaN(JSType.toNumber(number));
 289     }
 290 
 291     /**
 292      * ECMA 15.1.2.5, isFinite implementation
 293      *
 294      * @param self   self reference
 295      * @param number number to check
 296      *
 297      * @return true if number is infinite
 298      */
 299     public static boolean isFinite(final Object self, final Object number) {
 300         final double value = JSType.toNumber(number);
 301         return ! (Double.isInfinite(value) || Double.isNaN(value));
 302     }
 303 
 304 
 305     /**
 306      * ECMA 15.1.3.3, encodeURI implementation
 307      *
 308      * @param self  self reference
 309      * @param uri   URI to encode
 310      *
 311      * @return encoded URI
 312      */
 313     public static Object encodeURI(final Object self, final Object uri) {
 314         return URIUtils.encodeURI(self, JSType.toString(uri));
 315     }
 316 
 317     /**
 318      * ECMA 15.1.3.4, encodeURIComponent implementation
 319      *
 320      * @param self  self reference
 321      * @param uri   URI component to encode
 322      *
 323      * @return encoded URIComponent
 324      */
 325     public static Object encodeURIComponent(final Object self, final Object uri) {
 326         return URIUtils.encodeURIComponent(self, JSType.toString(uri));
 327     }
 328 
 329     /**
 330      * ECMA 15.1.3.1, decodeURI implementation
 331      *
 332      * @param self  self reference
 333      * @param uri   URI to decode
 334      *
 335      * @return decoded URI
 336      */
 337     public static Object decodeURI(final Object self, final Object uri) {
 338         return URIUtils.decodeURI(self, JSType.toString(uri));
 339     }
 340 
 341     /**
 342      * ECMA 15.1.3.2, decodeURIComponent implementation
 343      *
 344      * @param self  self reference
 345      * @param uri   URI component to encode
 346      *
 347      * @return decoded URI
 348      */
 349     public static Object decodeURIComponent(final Object self, final Object uri) {
 350         return URIUtils.decodeURIComponent(self, JSType.toString(uri));
 351     }
 352 
 353     /**
 354      * ECMA B.2.1, escape implementation
 355      *
 356      * @param self    self reference
 357      * @param string  string to escape
 358      *
 359      * @return escaped string
 360      */
 361     public static String escape(final Object self, final Object string) {
 362         final String str = JSType.toString(string);
 363         final int length = str.length();
 364 
 365         if (length == 0) {
 366             return str;
 367         }
 368 
 369         final StringBuilder sb = new StringBuilder();
 370         for (int k = 0; k < length; k++) {
 371             final char ch = str.charAt(k);
 372             if (UNESCAPED.indexOf(ch) != -1) {
 373                 sb.append(ch);
 374             } else if (ch < 256) {
 375                 sb.append('%');
 376                 final byte b = (byte)ch;
 377                 sb.append(Integer.toHexString(b & 0xFF).toUpperCase(Locale.ENGLISH));
 378             } else {
 379                 sb.append("%u");
 380                 sb.append(Integer.toHexString(ch & 0xFFFF).toUpperCase(Locale.ENGLISH));
 381             }
 382         }
 383 
 384         return sb.toString();
 385     }
 386 
 387     /**
 388      * ECMA B.2.2, unescape implementation
 389      *
 390      * @param self    self reference
 391      * @param string  string to unescape
 392      *
 393      * @return unescaped string
 394      */
 395     public static String unescape(final Object self, final Object string) {
 396         final String str    = JSType.toString(string);
 397         final int    length = str.length();
 398 
 399         if (length == 0) {
 400             return str;
 401         }
 402 
 403         final StringBuilder sb = new StringBuilder();
 404         for (int k = 0; k < length; k++) {
 405             char ch = str.charAt(k);
 406             if (ch != '%') {
 407                 sb.append(ch);
 408             } else {
 409                 if (k < (length - 5)) {
 410                    if (str.charAt(k + 1) == 'u') {
 411                        try {
 412                            ch = (char) Integer.parseInt(str.substring(k + 2, k + 6), 16);
 413                            sb.append(ch);
 414                            k += 5;
 415                            continue;
 416                        } catch (final NumberFormatException e) {
 417                            //ignored
 418                        }
 419                    }
 420                 }
 421 
 422                 if (k < (length - 2)) {
 423                     try {
 424                         ch = (char) Integer.parseInt(str.substring(k + 1, k + 3), 16);
 425                         sb.append(ch);
 426                         k += 2;
 427                         continue;
 428                     } catch (final NumberFormatException e) {
 429                         //ignored
 430                     }
 431                 }
 432 
 433                 // everything fails
 434                 sb.append(ch);
 435             }
 436         }
 437 
 438         return sb.toString();
 439     }
 440 
 441 
 442     /**
 443      * ECMA 15.3.4 Properties of the Function Prototype Object.
 444      * The Function prototype object is itself a Function object
 445      * (its [[Class]] is "Function") that, when invoked, accepts
 446      * any arguments and returns undefined. This method is used to
 447      * implement that anonymous function.
 448      *
 449      * @param self  self reference
 450      *
 451      * @return undefined
 452      */
 453     public static Object anonymous(final Object self) {
 454         return ScriptRuntime.UNDEFINED;
 455     }
 456 
 457     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 458         return MH.findStatic(MethodHandles.lookup(), GlobalFunctions.class, name, MH.type(rtype, types));
 459     }
 460 
 461 }
--- EOF ---