package com.yahoo.dtf.actions.parse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.PushbackInputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.yahoo.dtf.actions.Action; import com.yahoo.dtf.config.Config; import com.yahoo.dtf.exception.DTFException; import com.yahoo.dtf.exception.ParseException; import com.yahoo.dtf.util.StringUtil; /** * @dtf.tag scanf * * @dtf.since 1.0 * @dtf.author Rodney Gomes * * @dtf.tag.desc The scanf tag can be used to process data from the input * attribute and parse/extract different elements from this * property while following the syntax of the scanf function found * in the C programming language. * </br> * </br> * <b> * One thing to note that is important with scanf is that it will * read exactly what you tell it, so if you say read back "%2.2f" * it will try to read back a float that has 2 digits in the whole * part of the number and two fractional digits. So be aware * because it wont' consume any extra digits or do any type of * conversion.</b> * </br> * * @dtf.tag.example * <scanf args="float,int,hex" * format="%.2f some text %5d %04x" * input="2.12 some text 00001 000a"/> * * @dtf.tag.example * <scanf args="float,hex" * format="%.2f %x" * input="12.3456789 0x"/> * */ public class Scanf extends Action { private static int ARGS_INDEX = 1; private static int PERC_INDEX = 2; private static int FLAG_INDEX = 3; private static int WIDT_INDEX = 4; private static int PREC_INDEX = 5; private static int TYPE_INDEX = 6; private static int STRI_INDEX = 7; private static Pattern ELEM = Pattern.compile( // flags width precision conversion type "((\\%)([\\-\\+\\#0]*)([0-9]*)(\\.?[0-9]*)([slLcdiubxXoeEfgG\\%]{1}))|([^%]*)"); /** * @dtf.attr format * @dtf.attr.desc The scanf format string that follows a syntax compliant * with the C land implementation of scanf. Note that the * format you specify has to be the format the input is in * otherwise you won't get the right values out of your data. * Right now here are the available options: * * <pre> * %[flags][width][.precision]type * </pre> * * <b>flags</b> * <ul> * <li>0 - will pad the numerical values using 0's</li> * </ul> * * <b>width</b> * <ul> * <li>[0-9]* - integer value representing the amount of * digits to read for the numerical value * being processed by the scanf tag</li> * </ul> * * <b>precision</b> * <ul> * <li>.[0-9]* - a dot followed by an integer value * representing the amount of digits to read * for the fractional part of the number * being processed by the scanf tag</li> * </ul> * * <b>type</b> * <ul> * <li>% - matches the literal character %.</li> * <li>d,D,i matches with an integer which can be in * hexa-decimal format.</li> * <li>x,X matches a hexa-decimal number.</li> * <li>f,e,E,g matches an optionally signed floating-point * number.</li> * <li>s matches a sequence of non-white-space characters; * The input string stops at white space or at the * maximum field width, whichever occurs first.<li> * <li>c matches a sequence of characters whose length is * specified by the maximum field width (default 1); * <li>[ matches a non-empty sequence of characters from * the specified set of accepted characters.</li> * </ul> */ private String format = null; /** * @dtf.attr input * @dtf.attr.desc The input string to process using the format specified by * the format attribute. */ private String input = null; /** * @dtf.attr args * @dtf.attr.desc A comma separated list of the property names to use when * storing the various elements specified in the format string * so that the test writer can access those values after the * tag has completed execution. */ private String args = null; @Override public void execute() throws DTFException { String format = getFormat(); String[] args = getArgs().split(","); Matcher matcher = ELEM.matcher(format); byte[] buffer = getInput().getBytes(); ByteArrayInputStream is = new ByteArrayInputStream(buffer); PushbackInputStream pis = new PushbackInputStream(is); Config config = getConfig(); try { int index = 0; while ( matcher.find() ) { String elem = matcher.group(ARGS_INDEX); if ( elem != null && elem.length() != 0 ) { String prop = args[index++]; String flagStr = matcher.group(FLAG_INDEX); char flag = (flagStr.length() != 0 ? flagStr.charAt(0) : '0'); String widthStr = matcher.group(WIDT_INDEX); int width = (widthStr.length() != 0 ? Integer.decode(widthStr) : -1); String preciStr = matcher.group(PREC_INDEX).replace(".", ""); int preci = (preciStr.length() != 0 ? Integer.decode(preciStr) : -1); char type = matcher.group(TYPE_INDEX).charAt(0); StringBuffer num = new StringBuffer(""); String aux = null; int read = 0, cnt = 0; switch (type) { case 'd': case 'i': while ( (width == -1 || cnt++ < width) && (read = pis.read()) != -1 && Character.isDigit(read) ) { num.append((char)read); } if ( read != -1 && !Character.isDigit(read) ) pis.unread(read); aux = "" + Long.valueOf(num.toString()); aux = StringUtil.padString(aux, width, flag); config.setProperty(prop, aux); break; case 'e': case 'E': case 'f': // read the whole part while ( (width == -1 || cnt++ < width) && (read = pis.read()) != -1 && Character.isDigit(read) && ((char)read) != '.' ) { num.append((char)read); } // read the fractional part if ( read == '.' ) { num.append('.'); while ( (preci == -1 || cnt++ < preci) && (read = pis.read()) != -1 && Character.isDigit(read) ) { num.append((char)read); } } if ( read != -1 && !Character.isDigit(read) ) pis.unread(read); aux = "" + num; aux = StringUtil.padString(aux, width, flag); config.setProperty(prop, aux); break; case 'x': case 'X': String HEXDIGIT = "0123456789abcdefABCDEFxX"; while ( (width == -1 || cnt++ < width) && (read = pis.read()) != -1 && HEXDIGIT.indexOf(read) != -1 ) { num.append((char)read); } if ( read != -1 && HEXDIGIT.indexOf(read) == -1 ) pis.unread(read); aux = "" + num; aux = StringUtil.padString(aux, width, flag); config.setProperty(prop, aux); break; case 's': StringBuffer s = new StringBuffer(); while ( (width == -1 || cnt++ < width) && (read = pis.read()) != -1 ) { // if there is no width attribute then read // everything up to the next whitespace character if ( width == -1 && !Character.isWhitespace(read) ) break; s.append((char)read); } if ( read != -1 ) pis.unread(read); config.setProperty(prop, s.toString()); break; case 'c': s = new StringBuffer(); if ( width == -1 ) width = 1; while ( cnt++ < width && (read = pis.read()) != -1 ) { s.append((char)read); } if ( cnt < width ) throw new ParseException("Expected more characters."); config.setProperty(prop, s.toString()); break; case '%': if ( (read = pis.read()) != '%' ) { throw new ParseException("Expected [%] got [" + (char)read + "]"); } } } else { // last one should have matched any other string elem = matcher.group(STRI_INDEX); if ( elem.length() != 0 ) { int cnt = 0; int read = 0; while ( cnt < elem.length() && (read = pis.read()) != -1) { if ( ((char)read) != elem.charAt(cnt) ) { throw new ParseException("Expected [" + elem.charAt(cnt) + "] got [" + ((char)read) + "]"); } cnt++; } } } /* * If there are unresolved properties we can dynamically resolve * them to a value that was previously instantiated by this * scanf tag. * * This allows you to create scanf format argument that is * dynamic and saves CPU cycles by not having to run scanf twice * on the same data, here's an example: * * Lets say you wanted to scan the string "000cHelloWorldXXXXX" * knowing that the first thing in the string were 4 hex bytes * that dictated how long the following string was. * */ if ( format.contains("${") ) { int start = matcher.start(); format = replaceProperties(format, true); matcher = ELEM.matcher(format); matcher.find(start); } } } catch (IOException e) { throw new DTFException("Error processing scanf.",e); } } public String getFormat() throws ParseException { return replaceProperties(format,true); } public void setFormat(String format) { this.format = format; } public String getInput() throws ParseException { return replaceProperties(input); } public void setInput(String input) { this.input = input; } public String getArgs() throws ParseException { return replaceProperties(args); } public void setArgs(String args) { this.args = args; } }