/** * Copyright 2015 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.noga.njexl.testing.api; import com.noga.njexl.lang.internal.logging.Log; import com.noga.njexl.lang.internal.logging.LogFactory; import com.noga.njexl.testing.Utils; import com.noga.njexl.testing.dataprovider.DataSource; import com.noga.njexl.testing.dataprovider.ProviderFactory; import java.lang.reflect.*; import java.math.BigDecimal; import java.math.BigInteger; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * <pre> * Standard Spring/Hibernate/xBatis magic * of converting string to object - also known as DeSerialization * This does the conversion of method parameters from relational strings * </pre> */ public class ArgConverter { private static Log logger = LogFactory.getLog( ArgConverter.class ); public static String TLN(Class clazz){ return clazz.getName().toLowerCase(); } public static final String OBJECT = TLN ( Object.class) ; public static final String CLASS = TLN ( Class.class) ; public static final String STRING = TLN(String.class); public static final String DATE = TLN ( Date.class); // INT TYPES public static final String LONG_B = TLN (Long.class); public static final String LONG = "long"; public static final String INT = "int"; public static final String INTEGER = TLN(Integer.class); public static final String BIG_INTEGER = TLN(BigInteger.class); public static final String SHORT_B = TLN(Short.class); public static final String SHORT = "short"; public static final String CHARACTER = TLN( Character.class) ; public static final String CHAR = "char"; public static final String BYTE = "byte"; public static final String BYTE_B = TLN(Byte.class); // The Number types public static final String DOUBLE = "double"; public static final String DOUBLE_B = TLN(Double.class); public static final String FLOAT = "float"; public static final String FLOAT_B = TLN(Float.class); public static final String BIG_DECIMAL = TLN(BigDecimal.class); // The Logic Type public static final String BOOLEAN = "boolean"; public static final String BOOLEAN_B = TLN(Boolean.class); // The list Type public static final String LIST = "list"; public static final String ARRAY = "[L"; // Other Strings // This is the signature to pass NULL public static final String NULL_OBJECT= "#null#"; // This is the signature to pass empty set, hash, list, array or String public static final String EMPTY_CONTAINER = "#empty#"; public static final String SPLIT_STRING = ":"; public static final String LIST_SEP_STRING = ","; public static final String HASH_SEP_STRING = "=>"; public static final String CALL_CONSTRUCTOR = "#"; public static final String LIST_REDIRECT = "@"; public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd.MM.yyyy" ) ; /** * Checks if the class is a primitive type * * @param c The class type * @return Is the class 'c' primitive? */ public static boolean IsPrimitive(Class<?> c) { String name = c.getName(); name = name.toLowerCase(); // Enum is a primitive type if (__IsPrimitive(name) || c.isEnum()) { return true; } return c.isPrimitive(); } /** * Checks if the full name of the class belongs to a primitive type * * @param name Fully qualified Name of the class * @return boolean yes/no answer */ private static boolean __IsPrimitive(String name) { if (name.equals(STRING) || name.equals(OBJECT) || name.equals(DATE) /* Now the Un-Boxed types */ || name.equals(INT) || name.equals(LONG) || name.equals(BYTE) || name.equals(SHORT) || name.equals(CHAR) || name.equals(FLOAT) || name.equals(DOUBLE) || name.equals(BOOLEAN) /* The Boxed Types */ || name.equals(INTEGER) || name.equals(BIG_DECIMAL) || name.equals(LONG_B) || name.equals(CHARACTER) || name.equals(DOUBLE_B) || name.equals(FLOAT_B) || name.equals(BYTE_B) || name.equals(SHORT_B) || name.equals(BIG_INTEGER) || name.equals(BOOLEAN_B) || name.equals(CLASS)) { return true; } return false; } /** * <pre> * Checks if the class type is a list type or not. Basically tells you if it * is a List or an array type * </pre> * * @param c The class type * @return A boolean yes/no */ public static boolean IsList(Class<?> c) { return List.class.isAssignableFrom( c ) ; } /** * <pre> * Checks if the class type is an Array type or not. Basically tells you if it * is an array type * </pre> * * @param c The class type * @return A boolean yes/no */ public static boolean IsArray(Class<?> c) { if (c.isArray()) { return true; } return false; } /** * <pre> * Is the class c belongs to :- * [1] Map * [2] Set * [3] Dictionary * * </pre> * * @param c a class to be tested * @return then return true, else return false */ public static boolean IsHash(Class<?> c) { if (Map.class.isAssignableFrom(c)) { return true; } if (Set.class.isAssignableFrom(c)) { return true; } if (Dictionary.class.isAssignableFrom(c)) { return true; } return false; } /** * A field map for fields of classes */ public static ConcurrentHashMap<Class,HashMap<String,Field>> cachedFieldMap = new ConcurrentHashMap<>(); /** * Gets the field given class and field name * @param clazz the class * @param fieldName the field * @return the reflective field, if exists */ public static Field getField(Class clazz, String fieldName) { HashMap<String,Field> map = cachedFieldMap.get(clazz); if ( map == null ){ map = new HashMap<>(); Field[] fields = clazz.getDeclaredFields(); for( Field f : fields ){ f.setAccessible(true); map.put(f.getName(),f); } cachedFieldMap.put(clazz,map); } Field f = map.get(fieldName); if ( f != null){ return f ; } // get the super too f = getField( clazz.getSuperclass(),fieldName); return f ; } Method method; public Method method(){ return method ; } Type[] parameterTypes; Class[] parameterClasses; protected String formattedMethodSignature = "" ; /** * Generates method signature given a method * @param method the method * @return the compilable method signature for that method */ public static String getMethodSignature(Method method){ String[] cols = method.getGenericReturnType().toString().split(" "); String retType = cols[cols.length - 1]; String methodName = method.getName(); Type[] typed_params = method.getGenericParameterTypes(); StringBuffer output = new StringBuffer( String.format("%s#%s(", retType, methodName) ); for (int i = 0; i < typed_params.length; i++) { cols = typed_params[i].toString().split(" "); output.append( String.format("%s", cols[cols.length - 1]) ); output.append( "," ); } String ret = output.substring( 0, output.length() - 1); ret += ")" ; logger.info(ret); System.out.println(ret); return ret; } /** * Prints the method formatted */ private void printMethod() { String output = getMethodSignature( this.method ); formattedMethodSignature = output ; logger.debug("Matched Method is :: " + output); System.out.println("=========matched method=========="); System.out.println(output); System.out.println("================================="); } DataSource dataSource ; String dataTableName; /** * Gets the associated data source * @return the data source */ public DataSource dataSource(){ return dataSource ; } /** * Create a call container from the given data source * @param row the row of the data table * @return a call container */ public CallContainer container(int row){ String[] values = dataSource.tables.get(dataTableName).row(row); if ( parameterTypes.length > values.length ){ return null; } CallContainer container = new CallContainer(); container.parameters = new Object[ parameterTypes.length ] ; String[] headers = dataSource.tables.get(dataTableName).row(0); for ( int i = 0 ; i < container.parameters.length; i++ ){ try { container.parameters[i] = object(values[i], parameterClasses[i], parameterTypes[i], headers[i]); }catch (Exception e){ logger.error(String.format("Error converting parameter %d : %s", i, parameterTypes[i]), e); } } container.method = method ; container.rowId = row ; container.dataTable = dataSource.tables.get(dataTableName) ; return container; } /** * Gets all the containers from the data table * @param removeDisable if true, disabled items won't be added * @return an array of @{CallContainer} objects */ public CallContainer[] allContainers(boolean removeDisable){ int size = dataSource.tables.get(dataTableName).length(); ArrayList<CallContainer> containers = new ArrayList<>(); for ( int i = 1; i < size; i++ ){ CallContainer cc = container(i); if ( removeDisable && cc.disabled() ){ continue; } containers.add(cc); } CallContainer[] ccArr = new CallContainer[containers.size()]; containers.toArray( ccArr ); return ccArr ; } /** * Gets all the containers from the data table * removing the disabled items * @return an array of @{CallContainer} objects */ public CallContainer[] allContainers(){ return allContainers(true); } /** * Creates an object from serialized relational form * @param value the string value * @param clazz the object's class * @param type the object's type * @param header header ( metadata ) information * @return instance of an object if it could * @throws Exception if it can not create one */ public Object object(String value, Class clazz, Type type, String header) throws Exception { if ( value.equalsIgnoreCase(NULL_OBJECT) ){ return null; } Object object ; if ( IsPrimitive(clazz) ){ object = primitive(value, clazz, header); } else if ( IsList(clazz) ){ if ( value.equalsIgnoreCase( EMPTY_CONTAINER ) ){ return new ArrayList<>(); } object = list(value, type, header); } else if ( IsHash(clazz)){ if ( value.equalsIgnoreCase( EMPTY_CONTAINER ) ){ if ( Dictionary.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)) { return new HashMap<>(); } return new HashSet<>(); } object = map(value, clazz, type, header); } else { object = complex(value, clazz); } return object ; } private Object primitive(String value, Class clazz, String header) throws Exception { if ( clazz.equals( String.class ) ){ if ( value.equalsIgnoreCase( EMPTY_CONTAINER ) ){ return ""; } return value ; } if ( clazz.equals( Date.class) ){ String[] arr = header.split(SPLIT_STRING); if ( arr.length > 1 ) { SimpleDateFormat sdf = new SimpleDateFormat(arr[1].trim()) ; return sdf.parse( value ); } return DATE_FORMAT.parse(value); } if ( clazz.isEnum() ){ return Enum.valueOf( clazz, value ); } if ( Boolean.class.isAssignableFrom(clazz) || boolean.class.isAssignableFrom(clazz)){ return Boolean.parseBoolean(value); } if ( Byte.class.isAssignableFrom(clazz) || byte.class.isAssignableFrom(clazz) ){ return Byte.parseByte(value); } if ( Short.class.isAssignableFrom(clazz) || short.class.isAssignableFrom(clazz)){ return Short.parseShort(value); } if ( Integer.class.isAssignableFrom(clazz) || int.class.isAssignableFrom(clazz)){ return Integer.parseInt(value); } if ( Float.class.isAssignableFrom(clazz) || float.class.isAssignableFrom(clazz)){ return Float.parseFloat(value); } if ( Double.class.isAssignableFrom(clazz) || double.class.isAssignableFrom(clazz)){ return Double.parseDouble(value); } if ( BigInteger.class.isAssignableFrom(clazz) ){ return new BigInteger(value,10); } if ( BigDecimal.class.isAssignableFrom(clazz) ){ return new BigDecimal(value); } logger.error(String.format("Why %s is in here?",clazz)); return null; } /** * Gets constructor of a class from metadata info * @param clazz the class * @param headers the metadata information * @return a matching constructor object */ public Constructor constructor(Class clazz, String[] headers){ Constructor[] constructors = clazz.getConstructors(); Constructor c = null; for ( int i = 0 ; i < constructors.length; i++){ if ( constructors[i].getParameterCount() == headers.length ){ // possible return, but try checking the c = constructors[i]; boolean match = true; Parameter[] params = c.getParameters(); for ( int j = 0 ; j < params.length; j++){ match = params[j].getName().equals(headers[j]); if ( !match ){ break; } } if ( match ){ return c; } } } return c; } private Object complex(String value, Class clazz) { String[] arr = value.split(SPLIT_STRING); Object instance = null; String tableName = arr[0].trim(); int rowNum = Integer.parseInt(arr[1].trim()); boolean constructor = false ; if ( tableName.startsWith(CALL_CONSTRUCTOR) ){ tableName = tableName.substring(1); //call constructor constructor = true ; } if ( constructor ){ String[] headers = dataSource.tables.get(tableName).row(0); String[] values = dataSource.tables.get(tableName).row(rowNum); if ( values == null ){ logger.error("There is no value in the table with that row 0 based!"); return null; } Object[] params = new Object[values.length] ; boolean findConstructor = false ; for ( int i = 0 ; i < headers.length ;i++ ){ try { arr = headers[i].split(SPLIT_STRING) ; if ( arr.length > 1 ) { String paramClassName = arr[1].trim(); Class paramType = Class.forName(paramClassName); params[i] = object(values[i], paramType, paramType, headers[i]); }else{ findConstructor = true; break; } }catch (Exception e){ logger.error( String.format("Error setting up constructor parameter value : %s : %s", clazz, headers[i]), e); } } // am here now if ( findConstructor ){ Constructor c = constructor(clazz, headers); if ( c == null){ logger.error( String.format("Error creating object through constructor by intelligence: %s", clazz)); return null; } Class[] parameters = c.getParameterTypes(); Type[] types = c.getGenericParameterTypes(); for ( int i = 0 ; i < parameters.length ;i++ ){ try { params[i] = object(values[i], parameters[i], types[i], headers[i]); }catch (Exception e){ logger.error( String.format("Error setting up constructor parameter value : %s : %s", clazz, headers[i]), e); } } try { instance = c.newInstance(params); } catch (Exception e) { logger.error( String.format("Error creating object through constructor by intelligence: %s", clazz)); } } else{ try { instance = Utils.createInstance(clazz.getName(), params); } catch (Exception e) { logger.error( String.format("Error creating object through constructor by parameter type : %s", clazz),e); } } } else{ instance = Utils.createInstance(clazz.getName()); String[] headers = dataSource.tables.get(tableName).row(0); String[] values = dataSource.tables.get(tableName).row(rowNum); for ( int i = 0 ; i < headers.length ;i++ ){ logger.debug( String.format("Working field with : [%s]",headers[i])); arr = headers[i].split(SPLIT_STRING) ; String fieldName = arr[0].trim(); Field field = getField(clazz,fieldName); if ( field == null ){ logger.error( String.format("In the column no [%d] : A field with name [%s] does not exist in the class [%s]!", i, fieldName,clazz.getName()) ); continue; } try { Object fieldValue = object(values[i], field.getType(), field.getGenericType(), headers[i]); field.set( instance, fieldValue); }catch (Exception e){ logger.error(String.format("Error setting up field value : %s : %s", clazz, fieldName),e); } } } return instance; } private Object map(String value, Class clazz, Type type, String header) { if ( Map.class.isAssignableFrom( clazz) || Dictionary.class.isAssignableFrom(clazz) ){ return new HashMap(); } if ( Set.class.isAssignableFrom(clazz)){ return new HashSet<>(); } return null; } /** * For container types, tries to get inner type * @param type the type * @return inner type, if possible */ public static String getInnerType(Type type){ String name = type.getTypeName(); int firstIndex = name.indexOf('<'); int lastIndex = name.lastIndexOf('>'); name = name.substring(firstIndex+1,lastIndex); return name; } private Object list(String value, Type type, String header) { ArrayList list = new ArrayList(); String innerName = getInnerType(type); try{ String[] values; Class innerClass = Class.forName(innerName); if ( value.startsWith( LIST_REDIRECT ) ){ value = value.substring(1); String[] arr = value.split(SPLIT_STRING); String tableName = arr[0].trim(); int rowNum = Integer.parseInt(arr[1].trim()); values = dataSource.tables.get(tableName).row(rowNum); }else{ values = value.split(LIST_SEP_STRING); } for ( int i = 0 ; i < values.length;i++ ){ Object item = object(values[i], innerClass, innerClass, ""); list.add(item); } }catch (Exception e){ logger.error(String.format("Failing to find inner class : %s", innerName),e); } return list; } /** * Given metadata information creates one instance * @param methodRunInformation metadata information */ public ArgConverter(Annotations.MethodRunInformation methodRunInformation){ method = methodRunInformation.method ; parameterTypes = method.getGenericParameterTypes(); parameterClasses = method.getParameterTypes(); String dsLoc = methodRunInformation.base + "/" + methodRunInformation.nApi.dataSource(); dataSource = ProviderFactory.dataSource(dsLoc); dataTableName = methodRunInformation.nApi.dataTable(); } }