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