Formatter.java revision 953:221a84ef44c0
1/*
2 * Copyright (c) 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.api.scripting;
27
28import java.util.regex.Matcher;
29import java.util.regex.Pattern;
30
31/**
32 * Formatter is a class to get the type conversion between javascript types and
33 * java types for the format (sprintf) method working.
34 *
35 * <p>In javascript the type for numbers can be different from the format type
36 * specifier. For format type '%d', '%o', '%x', '%X' double need to be
37 * converted to integer. For format type 'e', 'E', 'f', 'g', 'G', 'a', 'A'
38 * integer needs to be converted to double.
39 *
40 * <p>Format type "%c" and javascript string needs special handling.
41 *
42 * <p>The javascript date objects can be handled if they are type double (the
43 * related javascript code will convert with Date.getTime() to double). So
44 * double date types are converted to long.
45 *
46 * <p>Pattern and the logic for parameter position: java.util.Formatter
47 *
48 */
49final class Formatter {
50
51    private Formatter() {
52    }
53
54    /**
55     * Method which converts javascript types to java types for the
56     * String.format method (jrunscript function sprintf).
57     *
58     * @param format a format string
59     * @param args arguments referenced by the format specifiers in format
60     * @return a formatted string
61     */
62    static String format(final String format, final Object[] args) {
63        final Matcher m = FS_PATTERN.matcher(format);
64        int positionalParameter = 1;
65
66        while (m.find()) {
67            int index = index(m.group(1));
68            final boolean previous = isPreviousArgument(m.group(2));
69            final char conversion = m.group(6).charAt(0);
70
71            // skip over some formats
72            if (index < 0 || previous
73                    || conversion == 'n' || conversion == '%') {
74                continue;
75            }
76
77            // index 0 here means take a positional parameter
78            if (index == 0) {
79                index = positionalParameter++;
80            }
81
82            // out of index, String.format will handle
83            if (index > args.length) {
84                continue;
85            }
86
87            // current argument
88            final Object arg = args[index - 1];
89
90            // for date we convert double to long
91            if (m.group(5) != null) {
92                // convert double to long
93                if (arg instanceof Double) {
94                    args[index - 1] = ((Double) arg).longValue();
95                }
96            } else {
97                // we have to convert some types
98                switch (conversion) {
99                    case 'd':
100                    case 'o':
101                    case 'x':
102                    case 'X':
103                        if (arg instanceof Double) {
104                            // convert double to long
105                            args[index - 1] = ((Double) arg).longValue();
106                        } else if (arg instanceof String
107                                && ((String) arg).length() > 0) {
108                            // convert string (first character) to int
109                            args[index - 1] = (int) ((String) arg).charAt(0);
110                        }
111                        break;
112                    case 'e':
113                    case 'E':
114                    case 'f':
115                    case 'g':
116                    case 'G':
117                    case 'a':
118                    case 'A':
119                        if (arg instanceof Integer) {
120                            // convert integer to double
121                            args[index - 1] = ((Integer) arg).doubleValue();
122                        }
123                        break;
124                    case 'c':
125                        if (arg instanceof Double) {
126                            // convert double to integer
127                            args[index - 1] = ((Double) arg).intValue();
128                        } else if (arg instanceof String
129                                && ((String) arg).length() > 0) {
130                            // get the first character from string
131                            args[index - 1] = (int) ((String) arg).charAt(0);
132                        }
133                        break;
134                    default:
135                        break;
136                }
137            }
138        }
139
140        return String.format(format, args);
141    }
142
143    /**
144     * Method to parse the integer of the argument index.
145     *
146     * @param s string to parse
147     * @return -1 if parsing failed, 0 if string is null, > 0 integer
148     */
149    private static int index(final String s) {
150        int index = -1;
151
152        if (s != null) {
153            try {
154                index = Integer.parseInt(s.substring(0, s.length() - 1));
155            } catch (final NumberFormatException e) {
156                //ignored
157            }
158        } else {
159            index = 0;
160        }
161
162        return index;
163    }
164
165    /**
166     * Method to check if a string contains '&lt;'. This is used to find out if
167     * previous parameter is used.
168     *
169     * @param s string to check
170     * @return true if '&lt;' is in the string, else false
171     */
172    private static boolean isPreviousArgument(final String s) {
173        return (s != null && s.indexOf('<') >= 0) ? true : false;
174    }
175
176    // %[argument_index$][flags][width][.precision][t]conversion
177    private static final String formatSpecifier =
178            "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
179    // compiled format string
180    private static final Pattern FS_PATTERN;
181
182    static {
183        FS_PATTERN = Pattern.compile(formatSpecifier);
184    }
185}
186