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 final 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);
 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 }