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 '<'. This is used to find out if 167 * previous parameter is used. 168 * 169 * @param s string to check 170 * @return true if '<' 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