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