/* This file is part of EasyTest CodeGen, a project to generate JUnit test cases from source code in EasyTest Template format and helping to keep them in sync during refactoring. EasyTest CodeGen, a tool provided by EaseTech Organization Under Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt */ package org.easetech.easytest.codegen; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.easetech.easytest.io.FileSystemResource; import org.easetech.easytest.io.Resource; import org.easetech.easytest.loader.Loader; import org.easetech.easytest.loader.LoaderFactory; import org.easetech.easytest.loader.LoaderType; /** * An implementation of IWritingStrategy * responsible for persisting the generated test cases, test data, converters to files * * @author Ravi Polampelli * */ public class WritingStrategy extends ConfigurableStrategy implements IWritingStrategy, JUnitDocletProperties { public static final String TESTSOURCE_INDENT_WIDTH = "testsource.indent.width"; public static final String TESTSOURCE_INDENT_WIDTH_DEFAULT = "2"; private Map<String,StringBuffer> javaSourceFileMap = new HashMap<String,StringBuffer>(); /** * adjusts the source code with proper and standard indentation * @param StringBuffer sourceCode */ public void indent(StringBuffer sourceCode) { int indentLevel = 0; int indentWidth = 0; int beginIndex = 0; int endIndex = 0; int opening = 0; int closing = 0; int inserted = 0; boolean openingFirst = false; boolean closingFirst = false; if (sourceCode != null) { indentWidth = getIndentWidth(); while (endIndex<sourceCode.length()) { switch (sourceCode.charAt(endIndex)) { case '{': opening++; if (!closingFirst) { openingFirst = true; } break; case '}': closing++; if (!openingFirst) { closingFirst = true; } break; case '\n': if (closing>opening) { indentLevel = indentLevel-(closing-opening); } if (closing == opening) { if (closingFirst) indentLevel--; } inserted = 0; for (int i=0; (i<(indentLevel* indentWidth)); i++) { if (sourceCode.charAt(beginIndex+i) != ' ') { sourceCode.insert(beginIndex+i, " "); inserted++; } } endIndex += inserted; if (closing == opening) { if (closingFirst) indentLevel++; } if (opening>closing) { indentLevel = indentLevel+(opening-closing); } beginIndex = endIndex+1; opening = 0; closing = 0; openingFirst = false; closingFirst = false; break; // no default } endIndex++; } } } /** * Merges generated source code with class file for given class name. * * @param String root * @param String fullClassName * @return true if successfully merged or target file does not exist, false if class file contains no JUnitDoclet markers. */ public StringBuffer loadClassSource(String root, String fullClassName) { StringBuffer returnValue = null; File file; BufferedReader bufferedReader; String name; String line; name = translateClassNameToFileName(fullClassName); try { file = new File(root, name); if (file.exists()) { bufferedReader = new BufferedReader(new FileReader(file)); returnValue = new StringBuffer(); while (bufferedReader.ready()) { line = bufferedReader.readLine(); returnValue.append(line); returnValue.append("\n"); } bufferedReader.close(); //noinspection UnusedAssignment bufferedReader = null; } // no else //noinspection UnusedAssignment file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } return returnValue; } /** * Load actual source code from the path for given class name. * It first checks if file is available in cache(i.e. javaSourceFileMap) * @param String fullClassName * @param String path file path * @return StringBuffer if successfully loaded or null if file does not exist */ public StringBuffer loadSourceClassSource(String fullClassName, String path) { StringBuffer javaSourceFile = null; if(javaSourceFileMap.get(fullClassName)!=null){ javaSourceFile = javaSourceFileMap.get(fullClassName); } else { File file; BufferedReader bufferedReader; String name; String line; name = translateClassNameToFileName(fullClassName); try { file = new File(path,name); if (file.exists()) { bufferedReader = new BufferedReader(new FileReader(file)); javaSourceFile = new StringBuffer(); while (bufferedReader.ready()) { line = bufferedReader.readLine(); javaSourceFile.append(line); javaSourceFile.append("\n"); } bufferedReader.close(); //noinspection UnusedAssignment bufferedReader = null; javaSourceFileMap.put(fullClassName, javaSourceFile); } // no else //noinspection UnusedAssignment file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } } return javaSourceFile; } private String translatePathToFilePath(String path) { String returnValue; returnValue = path.replace('.', '/'); returnValue = returnValue.replace('\\', '/'); return returnValue; } /** * writes the source code to the destination file. * it creates the required folder structure if doesn't exist. * * @param String root, * @param String fullClassName * @param StringBuffer sourceCode * */ public void writeClassSource(String root, String fullClassName, StringBuffer sourceCode) { File file; FileWriter fileWriter; BufferedWriter bufferedWriter; String name; name = translateClassNameToFileName(fullClassName); try { file = new File(root, name); file.getParentFile().mkdirs(); fileWriter = new FileWriter(file); bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write(sourceCode.toString()); bufferedWriter.flush(); bufferedWriter.close(); bufferedWriter = null; fileWriter = null; file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } if (fileWriter != null) { // Any error while working with BufferedWriter ? try { fileWriter.close(); fileWriter = null; file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } } } public String translateClassNameToFileName(String className) { String returnValue; returnValue = className.replace('.', '/') + ".java"; return returnValue; } /** * checks if file exists and it this file is the latest (newer). * * @param String dirInQuestion, * @param String fullClassNameInQuestion, * @param String dirReference, * @param String fullClassNameReference * */ public boolean isExistingAndNewer(String dirInQuestion, String fullClassNameInQuestion, String dirReference, String fullClassNameReference) { boolean returnValue = false; File inQuestion; File reference; if ((dirInQuestion != null) && (fullClassNameInQuestion != null) && (dirReference != null) && (fullClassNameReference != null)) { inQuestion = new File(dirInQuestion, translateClassNameToFileName(fullClassNameInQuestion)); reference = new File(dirReference, translateClassNameToFileName(fullClassNameReference)); returnValue = inQuestion.exists() && (inQuestion.lastModified()>reference.lastModified()); } return returnValue; } public int getIndentWidth() { int returnValue; Properties properties; String stringValue; properties = getProperties(); if (properties != null) { stringValue = properties.getProperty(TESTSOURCE_INDENT_WIDTH, TESTSOURCE_INDENT_WIDTH_DEFAULT); returnValue = Integer.parseInt(stringValue); } else { returnValue = Integer.parseInt(TESTSOURCE_INDENT_WIDTH_DEFAULT); } return returnValue; } /** * writes test data file to the intended directory * It checks the user configuration whether to overwrite the test data file if exists or not * It also checks if mandatory fields are missing in the seed data file and accordningly it creates the log file * * @param String root, * @param String fullClassName, * @param TestCaseVO testCaseVO, * @param Properties seedData */ public void writeTestDataFile(String root, String fullClassName, TestCaseVO testCaseVO,Properties seedData) { File file; //FileOutputStream fos; String name; Map<String, List<Map<String, Object>>> testData = testCaseVO.getTestData(); //LoaderType loaderType; Loader dataLoader = LoaderFactory.getLoader(testCaseVO.getLoaderType()); name = translateClassNameToTestDataFileName(fullClassName,testCaseVO.getLoaderType()); LOG.debug("WriteTestDataFile root:"+root+",name:"+name); root = root.replaceFirst("test/java", "test/resources"); LOG.debug("seedData:"+seedData); Map<String,List<String>> seedDataValuesMap = null; if(seedData!=null){ seedDataValuesMap = loadSeedDataValues(seedData); } if(seedDataValuesMap!=null){ mergeSeedDataWithTestData(testData,seedDataValuesMap); } file = new File(root, name); if(file.exists()){ LOG.debug("file exists so loading old data:"); String overwrite = testCaseVO.getProperties().getProperty(OVERWRITE_EXISTING_TEST_DATA); //merge the existing test data if value doesn't exist or value is 'NO' if(overwrite == null || "NO".equalsIgnoreCase(overwrite)) { mergeToExistingDataFile(testData,file,dataLoader); } } file.getParentFile().mkdirs(); //fos = new FileOutputStream(file); //dataLoader.writeFullData(fos, testData); //ResourceLoader resourceLoader = new ResourceLoaderStrategy(); //Resource resource = resourceLoader.getResource(file.getAbsolutePath()); Resource resource = new FileSystemResource(root+File.separator+ name); dataLoader.writeData(resource, testData); //fos = null; file = null; // if (fos != null) { // Any error while working with BufferedWriter ? // try { // fos.close(); // // fos = null; // file = null; // } catch (IOException ioe) { // ioe.printStackTrace(); // // throw new RuntimeException(ioe.toString()); // } // } checkMandatoryFields(testCaseVO,seedDataValuesMap); if(testCaseVO.getTestDataMissingFields().size()>0){ String logFileName = fullClassName.replace('.', File.separatorChar)+".log"; FileWriter fileWriter; BufferedWriter bufferedWriter; try { file = new File(root, logFileName); file.getParentFile().mkdirs(); fileWriter = new FileWriter(file); bufferedWriter = new BufferedWriter(fileWriter); StringBuffer writeLog = new StringBuffer(); writeLog.append("Test data is missing for below methods <method : [parameter]>\n"); writeLog.append("-------------------------------------------------------------\n"); for(String methodName:testCaseVO.getTestDataMissingFields().keySet()){ writeLog.append(methodName + " : "); writeLog.append(testCaseVO.getTestDataMissingFields().get(methodName)+ "\n"); } bufferedWriter.write(writeLog.toString()); bufferedWriter.flush(); bufferedWriter.close(); bufferedWriter = null; fileWriter = null; file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } if (fileWriter != null) { // Any error while working with BufferedWriter ? try { fileWriter.close(); fileWriter = null; file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } } } } /** * Checks if mandatory fields exist in the seed data file * and sets the missing data fields in the testCaseVO for further processing * * @param testCaseVO * @param seedDataValuesMap */ private void checkMandatoryFields(TestCaseVO testCaseVO,Map<String,List<String>> seedDataValuesMap) { //Map<String, List<Map<String, Object>>> testData = testCaseVO.getTestData(); Map<String,List<String>> mandatoryFields = testCaseVO.getTestDataMandatoryFields(); LOG.debug("checkMandatoryFields started: mandatoryFields:"+mandatoryFields); LOG.debug("seedDataValuesMap:"+seedDataValuesMap); Map<String,List<String>> missingFields = new HashMap<String,List<String>>(); if(seedDataValuesMap != null && seedDataValuesMap.size() > 0){ for(String methodName:mandatoryFields.keySet()){ List<String> testMethodFields = mandatoryFields.get(methodName); for(String paramName:testMethodFields){ boolean isParamExists = false; for(String seedKey:seedDataValuesMap.keySet()){ if(paramName.equalsIgnoreCase(seedKey)){ isParamExists = true; break; } } if(!isParamExists) { if(!missingFields.containsKey(methodName)){ missingFields.put(methodName, new ArrayList<String>()); } missingFields.get(methodName).add(paramName); } } } } else { missingFields.putAll(mandatoryFields); } testCaseVO.setTestDataMissingFields(missingFields); LOG.debug("checkMandatoryFields finished: count:"+mandatoryFields.size()); } /** * Merge to existing test data file so that existing test data will not be overwritten * * @param testData * @param file * @param dataLoader */ private void mergeToExistingDataFile( Map<String, List<Map<String, Object>>> testData, File file, Loader dataLoader) { LOG.debug("mergeToExistingData() started:"+file.getAbsolutePath()); Map<String, List<Map<String, Object>>> oldData = null; // try { //FileInputStream fis = new FileInputStream(file); //oldData = dataLoader.loadFromInputStream(fis); Resource resource = new FileSystemResource(file.getAbsolutePath()); oldData = dataLoader.loadData(resource); // if(fis != null){ // try { // fis.close(); // } catch (IOException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // fis = null; // } // } catch (FileNotFoundException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } LOG.debug("oldData loaded:"+oldData); if(oldData!=null) mergeTestDataWithOldData(testData,oldData); LOG.debug("mergeToExistingData() finished:"+testData); } /** * Merge testdata map with old test data map * * @param testData * @param oldData */ private void mergeTestDataWithOldData(Map<String, List<Map<String, Object>>> testData, Map<String, List<Map<String, Object>>> oldData) { LOG.debug("mergeTestDataWithOldData() started:"); for(String oldTestMethod:oldData.keySet()){ LOG.debug("oldTestMethod() :"+oldTestMethod); List<Map<String, Object>> oldTestMethodData = oldData.get(oldTestMethod); for(String newTestMethod:testData.keySet()){ LOG.debug("newTestMethod() :"+newTestMethod); List<Map<String, Object>> newTestMethodData = testData.get(newTestMethod); if(newTestMethod.equals(oldTestMethod)){ for(Map<String, Object> oldTestDataRow : oldTestMethodData){ boolean isSameTestDataRow = false; LOG.debug("oldTestDataRow:"+oldTestDataRow); for(Map<String, Object> newTestDataRow : newTestMethodData){ LOG.debug("newTestDataRow:"+newTestDataRow); //LOG.debug("oldTestDataRow.equals(newTestDataRow):"+oldTestDataRow.equals(newTestDataRow)); //LOG.debug("oldTestDataRow.toString().equals(newTestDataRow.toString()):"+oldTestDataRow.toString().equals(newTestDataRow.toString())); if(oldTestDataRow.equals(newTestDataRow) || oldTestDataRow.toString().equals(newTestDataRow.toString())){ LOG.debug("oldTestDataRow and newTestDataRow are same:"); isSameTestDataRow = true; break; } } if(!isSameTestDataRow){ LOG.debug("oldTestDataRow and newTestDataRow are not same:"); for(String oldParam:oldTestDataRow.keySet()){ boolean paramNameExists = false; for(String newParam:newTestMethodData.get(0).keySet()){ if(oldParam.equals(newParam)){ paramNameExists = true; break; } } if(!paramNameExists){ oldTestDataRow.remove(oldParam); } } LOG.debug("Adding oldTestDataRow to newTestMethodData:"+oldTestDataRow); newTestMethodData.add(oldTestDataRow); } } break; } } } LOG.debug("mergeTestDataWithOldData() finsihed:"); } /** * Important method for populating test data from seed data values map. * It checks if the parameter name in the seed data map is same as the parameter name in the test data, * then it inserts the seed data values to test data. It does for appropriate permutation and combinations of parameters. * * @param testData * @param seedDataValuesMap */ private void mergeSeedDataWithTestData( Map<String, List<Map<String, Object>>> testData, Map<String, List<String>> seedDataValuesMap) { LOG.debug("mergeSeedDataWithTestData() started:"); for(List<Map<String, Object>> methodData:testData.values()){ int origMethodDataSize = methodData.size(); LOG.debug("origMethodDataSize:"+origMethodDataSize); // Get the list of all parameters from first row Map<String, Object> data = methodData.get(0); for(String paramName:data.keySet()){ for(String seedDataParamName:seedDataValuesMap.keySet()){ LOG.debug("paramName"+paramName); LOG.debug("seedDataParamName"+seedDataParamName); if(paramName.equalsIgnoreCase(seedDataParamName)){ //initializing value to original data index, since // we need start adding seed data from there. int sdIndex = origMethodDataSize; for(String seedDataParamValue:seedDataValuesMap.get(seedDataParamName)){ Map<String, Object> dataRow = null; LOG.debug("seeddataIndex:"+sdIndex); // if seed data size for a parameter is more than existing data list size // then need to create new row of data. if(sdIndex >= methodData.size()){ dataRow = new LinkedHashMap<String, Object>(); methodData.add(dataRow); } else { //take the existing row and add param value there dataRow = methodData.get(sdIndex); } dataRow.put(paramName, seedDataParamValue); sdIndex++; } } } } } } /** * loads seed data from properties to map * @param seedData * @return Map<String, List<String>> seed data values map */ private Map<String, List<String>> loadSeedDataValues(Properties seedData) { LOG.debug("loadSeedDataValues() started:"+seedData); Map<String,List<String>> seedDataValuesMap = new HashMap<String,List<String>>(); for(Object keyObj:seedData.keySet()){ String keyStr = (String)keyObj; String[] keys = null; if(keyStr.contains(",")){ keys = keyStr.split(","); } else { keys = new String[]{keyStr}; } String valueStr = (String)seedData.get(keyObj); String[] values = null; if(valueStr.contains(",")){ values = valueStr.split(","); } else { values = new String[]{valueStr}; } List<String> valueList = new ArrayList<String>(); for(String value:values) { valueList.add(value); } for(String key:keys){ seedDataValuesMap.put(key, valueList); } } LOG.debug("loadSeedDataValues() finished:"+seedDataValuesMap); return seedDataValuesMap; } private String translateClassNameToTestDataFileName(String fullClassName, LoaderType loaderType) { String returnValue; String extension = StringHelper.getFileExtensionName(loaderType); returnValue = fullClassName.replace('.', File.separatorChar) + extension; return returnValue; } /** * Writes the converters class source code to appropriate destination folders * * @param String outputRoot, * @param String packageName, * @param Map<String, StringBuffer> convertersMap, * @param String overwriteConverters */ public void writeConverterSources(String outputRoot, String packageName, Map<String, StringBuffer> convertersMap, String overwriteConverters) { for(String fullClassName:convertersMap.keySet()) { File file; FileWriter fileWriter; BufferedWriter bufferedWriter; String name; LOG.debug("packageName+fullClassName:"+packageName+"."+fullClassName); name = translateClassNameToFileName(packageName+"."+fullClassName); try { file = new File(outputRoot, name); if(file.exists() && "NO".equalsIgnoreCase(overwriteConverters)){ continue; } file.getParentFile().mkdirs(); fileWriter = new FileWriter(file); bufferedWriter = new BufferedWriter(fileWriter); StringBuffer sourceCode = convertersMap.get(fullClassName); indent(sourceCode); bufferedWriter.write(sourceCode.toString()); bufferedWriter.flush(); bufferedWriter.close(); bufferedWriter = null; fileWriter = null; file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } if (fileWriter != null) { // Any error while working with BufferedWriter ? try { fileWriter.close(); fileWriter = null; file = null; } catch (IOException ioe) { ioe.printStackTrace(); throw new RuntimeException(ioe.toString()); } } } } }