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