/* * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.api.scripting; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Formatter is a class to get the type conversion between javascript types and * java types for the format (sprintf) method working. * * <p>In javascript the type for numbers can be different from the format type * specifier. For format type '%d', '%o', '%x', '%X' double need to be * converted to integer. For format type 'e', 'E', 'f', 'g', 'G', 'a', 'A' * integer needs to be converted to double. * * <p>Format type "%c" and javascript string needs special handling. * * <p>The javascript date objects can be handled if they are type double (the * related javascript code will convert with Date.getTime() to double). So * double date types are converted to long. * * <p>Pattern and the logic for parameter position: java.util.Formatter * */ final class Formatter { private Formatter() { } /** * Method which converts javascript types to java types for the * String.format method (jrunscript function sprintf). * * @param format a format string * @param args arguments referenced by the format specifiers in format * @return a formatted string */ static String format(final String format, final Object[] args) { final Matcher m = FS_PATTERN.matcher(format); int positionalParameter = 1; while (m.find()) { int index = index(m.group(1)); final boolean previous = isPreviousArgument(m.group(2)); final char conversion = m.group(6).charAt(0); // skip over some formats if (index < 0 || previous || conversion == 'n' || conversion == '%') { continue; } // index 0 here means take a positional parameter if (index == 0) { index = positionalParameter++; } // out of index, String.format will handle if (index > args.length) { continue; } // current argument final Object arg = args[index - 1]; // for date we convert double to long if (m.group(5) != null) { // convert double to long if (arg instanceof Double) { args[index - 1] = ((Double) arg).longValue(); } } else { // we have to convert some types switch (conversion) { case 'd': case 'o': case 'x': case 'X': if (arg instanceof Double) { // convert double to long args[index - 1] = ((Double) arg).longValue(); } else if (arg instanceof String && ((String) arg).length() > 0) { // convert string (first character) to int args[index - 1] = (int) ((String) arg).charAt(0); } break; case 'e': case 'E': case 'f': case 'g': case 'G': case 'a': case 'A': if (arg instanceof Integer) { // convert integer to double args[index - 1] = ((Integer) arg).doubleValue(); } break; case 'c': if (arg instanceof Double) { // convert double to integer args[index - 1] = ((Double) arg).intValue(); } else if (arg instanceof String && ((String) arg).length() > 0) { // get the first character from string args[index - 1] = (int) ((String) arg).charAt(0); } break; default: break; } } } return String.format(format, args); } /** * Method to parse the integer of the argument index. * * @param s string to parse * @return -1 if parsing failed, 0 if string is null, > 0 integer */ private static int index(final String s) { int index = -1; if (s != null) { try { index = Integer.parseInt(s.substring(0, s.length() - 1)); } catch (final NumberFormatException e) { //ignored } } else { index = 0; } return index; } /** * Method to check if a string contains '<'. This is used to find out if * previous parameter is used. * * @param s string to check * @return true if '<' is in the string, else false */ private static boolean isPreviousArgument(final String s) { return (s != null && s.indexOf('<') >= 0); } // %[argument_index$][flags][width][.precision][t]conversion private static final String formatSpecifier = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])"; // compiled format string private static final Pattern FS_PATTERN; static { FS_PATTERN = Pattern.compile(formatSpecifier); } }