package fr.mch.mdo.test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Comparator; import java.util.Properties; import java.util.ResourceBundle; import junit.framework.TestCase; import org.apache.commons.beanutils.BeanUtils; /** * This class is abstract because we do not want JUnit to launch this class. * * @author Mathieu * */ public abstract class MdoTestCase extends TestCase { public static final String DEFAULT_FAILED_MESSAGE = "Could never be there"; protected MdoTestCase(String testName) { super(testName); } /** * @param clazz * used to process Relative properties file path name on * classpath * @param fileExtension * extension of the file * @param newFileExtension * new extension */ protected void renamePropertiesFileExtension(Class<?> clazz, String fileExtension, String newFileExtension) { String relativeFilePathName = clazz.getName().replace('.', '/') + fileExtension; renamePropertiesFileExtension(relativeFilePathName, newFileExtension); } /** * @param clazz * used to process Relative properties file path name on * classpath * @param fileExtension * extension of the file * @param newFileExtension * new extension */ protected void renamePropertiesFileExtension(String relativeFilePathName, String newFileExtension) { URL fileUrl = null; while ((fileUrl = MdoTestCase.class.getClassLoader().getResource(relativeFilePathName)) != null) { File file = null; URI fileUri = null; try { fileUri = fileUrl.toURI(); } catch (Exception e) { fail("Could not get the file URI " + fileUri); } if (!"jar".equals(fileUri.getScheme())) { try { file = new File(fileUri); } catch (Exception e) { fail("Could not get the file from URI " + fileUri + "==>" + e); } // It could be happened that there is more than one properties // files because of classpath String absoluteFilePathName = file.getAbsolutePath().substring(0, file.getAbsolutePath().lastIndexOf(".")) + newFileExtension; File newFile = new File(absoluteFilePathName); assertTrue("Check the file " + file.getAbsolutePath(), file.exists()); assertTrue("Check rename properties file: " + file.getAbsolutePath() + " to " + newFile.getAbsolutePath(), file.renameTo(newFile)); // Refresh resources ResourceBundle.clearCache(); // break; } else { break; } } } protected File getFileFromClassPath(final Class<?> clazz, final String relativeFilePathName) { URL fileUrl = clazz.getResource(relativeFilePathName); if (fileUrl == null) { fileUrl = clazz.getClassLoader().getResource(relativeFilePathName); } assertNotNull("The file url must not be null", fileUrl); File file = null; try { file = new File(fileUrl.toURI()); } catch (URISyntaxException e) { fail("Could not get the file from URL"); } assertNotNull("The file must not be null", file); return file; } protected Properties getProperties(final Class<?> clazz) { // Relative properties file path name on classpath String fileExtension = ".properties"; String relativeFilePathName = clazz.getName().replace('.', '/') + fileExtension; return this.getProperties(clazz, relativeFilePathName); } protected Properties getProperties(final Class<?> clazz, final String relativeFilePathName) { Properties result = new Properties(); File file = getFileFromClassPath(clazz, relativeFilePathName); String absoluteFilePathName = file.getAbsolutePath(); BufferedReader fileBufferedReader = null; try { fileBufferedReader = new BufferedReader(new FileReader(file)); // Load and append new properties from properties file try { result.load(fileBufferedReader); } catch (IOException e) { fail("Could not load the properties file " + absoluteFilePathName); } catch (Exception e) { fail("Unexpected exception for loading file " + absoluteFilePathName); } } catch (FileNotFoundException e) { fail("File not found " + absoluteFilePathName); } catch (Exception e) { fail("Unexpected exception for reading file " + absoluteFilePathName); } finally { try { fileBufferedReader.close(); } catch (IOException e) { fail("Could not close the properties file " + absoluteFilePathName); } } return result; } protected void storeProperties(final Class<?> clazz, final String relativeFilePathName, final Properties properties) { File file = getFileFromClassPath(clazz, relativeFilePathName); String absoluteFilePathName = file.getAbsolutePath(); // Save new properties in properties file BufferedWriter fileBufferedWrite = null; try { fileBufferedWrite = new BufferedWriter(new FileWriter(file)); try { properties.store(fileBufferedWrite, null); } catch (IOException e) { fail("Could not save the properties file " + absoluteFilePathName); } } catch (IOException e) { fail("File not found " + absoluteFilePathName); } finally { try { fileBufferedWrite.close(); } catch (IOException e) { fail("Could not close the properties file " + absoluteFilePathName); } } // Refresh resources ResourceBundle.clearCache(); } /** * @param relativeFilePathName * Relative properties file path name on classpath * @param newProperties * new properties to add or remove * @param isRemoved * do we remove ? * @return the backup updated properties */ protected Properties updatePropertiesFile(final String relativeFilePathName, final Properties newProperties, final boolean isRemoved) { Properties result = new Properties(); Properties properties = this.getProperties(MdoTestCase.class, relativeFilePathName); // Add or remove new properties if (newProperties != null) { for (Object key : newProperties.keySet()) { Object oldValue = null; if (isRemoved) { oldValue = properties.remove(key); } else { oldValue = properties.put(key, newProperties.get(key)); } if (oldValue != null) { result.put(key, oldValue); } } } // Save new properties in properties file storeProperties(MdoTestCase.class, relativeFilePathName, properties); return result; } /** * @param clazz * used to process Relative properties file path name on * classpath * @param newProperties * new properties to add or remove * @param isRemoved * do we remove ? * @return the updated properties */ protected Properties updatePropertiesFile(Class<?> clazz, Properties newProperties, boolean isRemoved) { // Relative properties file path name on classpath String fileExtension = ".properties"; String relativeFilePathName = clazz.getName().replace('.', '/') + fileExtension; return updatePropertiesFile(relativeFilePathName, newProperties, isRemoved); } /** * This is the main method to invoke private/public static/instance methods * * @param targetInstance * if null then call static method else call instance method by * JVM * @param clazz * the method class * @param methodName * the method name * @param argClasses * the type of parameters * @param argObjects * the instances of parameters types * @return null if the method return void * @throws InvocationTargetException * when Invocation Target Exception occurs */ protected Object invokeMethod(Object targetInstance, Class<?> clazz, String methodName, Class<?>[] argClasses, Object[] argObjects) throws InvocationTargetException { Object result = null; if (clazz != null) { try { Method method = clazz.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); result = method.invoke(targetInstance, argObjects); } catch (NoSuchMethodException e) { // Should happen only rarely, because most times the // specified method should exist. If it does happen, just let // the test fail so the programmer can fix the problem. throw new InvocationTargetException(e, e.getMessage()); } catch (SecurityException e) { // Should happen only rarely, because the setAccessible(true) // should be allowed in when running unit tests. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new InvocationTargetException(e, e.getMessage()); } catch (IllegalAccessException e) { // Should never happen, because setting accessible flag to // true. If setting accessible fails, should throw a security // exception at that point and never get to the invoke. But // just in case, wrap it in a TestFailedException and let a // human figure it out. throw new InvocationTargetException(e, e.getMessage()); } catch (IllegalArgumentException e) { // Should happen only rarely, because usually the right // number and types of arguments will be passed. If it does // happen, just let the test fail so the programmer can fix // the problem. throw new InvocationTargetException(e, e.getMessage()); } catch (InvocationTargetException e) { // Should happen every times the invoked method throw an // exception. throw new InvocationTargetException(e.getTargetException(), e.getTargetException().getMessage()); } } return result; } /** * This method invokes private/public instance methods * * @param targetInstance * if null then return null else call instance method by JVM * @param methodName * the method name * @param argClasses * the type of parameters * @param argObjects * the instances of parameters types * @return null if the method return void * @throws InvocationTargetException * when Invocation Target Exception occurs */ protected Object invokeInstanceMethod(Object targetInstance, String methodName, Class<?>[] argClasses, Object[] argObjects) throws InvocationTargetException { if (targetInstance != null) { return invokeMethod(targetInstance, targetInstance.getClass(), methodName, argClasses, argObjects); } return null; } /** * This method invokes private/public static methods * * @param targetClass * call static method of this targetClass parameter * @param methodName * the method name * @param argClasses * the type of parameters * @param argObjects * the instances of parameters types * @return null if the method return void * @throws InvocationTargetException * when Invocation Target Exception occurs */ protected Object invokeStaticMethod(Class<?> targetClass, String methodName, Class<?>[] argClasses, Object[] argObjects) throws InvocationTargetException { return invokeMethod(null, targetClass, methodName, argClasses, argObjects); } /** * This inner class is used to reload an already loaded class * * @author Mathieu * */ protected final class MdoTestClassLoader extends ClassLoader { /** the class to be reloaded */ private Class<?> classToBeReloaded = null; @Override public java.lang.Class<?> loadClass(String name) throws ClassNotFoundException { // This method is call recursively or in the loop by the super // ClassLoader // Call the super method Class<?> clazz = super.loadClass(name); if (classToBeReloaded == null || !classToBeReloaded.getName().equals(name)) { // If the class name and the name is different to the one of // classToBeReloaded // then return the super result return clazz; } InputStream input = null; ByteArrayOutputStream buffer = null; // At this point the class name is equal to the one of // classToBeReloaded try { // Get the class file and convert to an InputStream input = clazz.getResourceAsStream(clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1) + ".class"); buffer = new ByteArrayOutputStream(); int data = input.read(); while (data != -1) { buffer.write(data); data = input.read(); } // Get the data of classToBeReloaded class file byte[] classData = buffer.toByteArray(); // Call the super method to refresh the class clazz = defineClass(clazz.getName(), classData, 0, classData.length); } catch (Exception e) { fail(e.toString()); } finally { try { input.close(); } catch (Exception e) { fail("Could not close the input stream file " + clazz.getSimpleName()); } try { buffer.close(); } catch (Exception e) { fail("Could not close the output stream file " + clazz.getSimpleName()); } } // The new refreshed classToBeReloaded class return clazz; } /** * This method is a convenient method to reload a class * * @param classToBeReloaded * the class to be reloaded * @return the reloaded class * @throws ClassNotFoundException * if Class Not Found Exception occurs */ public Class<?> reloadClass(Class<?> classToBeReloaded) throws ClassNotFoundException { this.classToBeReloaded = classToBeReloaded; return loadClass(classToBeReloaded.getName()); } } /** * This method is a convenient method for subclass to perform a class * reloading * * @param classToBeReloaded * the class to be reloaded * @return the new instance of the classToBeReloaded class */ protected Class<?> reloadClass(Class<?> classToBeReloaded) { Class<?> result = null; // Instance of new custom class loader MdoTestClassLoader myClassLoader = new MdoTestClassLoader(); try { // Reload the class in the new class loader // but this doesn't load yet the class: we have to call new instance // in order to really reload the class Class<?> clazz = (Class<?>) myClassLoader.reloadClass(classToBeReloaded); assertNotNull("The class to be reloaded " + classToBeReloaded + " must not be null", clazz); result = clazz; } catch (Exception e) { fail(e.getMessage()); } return result; } protected Object getField(Object object, String fieldName) { return this.getOrSetField(object, fieldName); } protected Object setField(Object object, String fieldName, Object value) { return this.getOrSetField(object, fieldName, value); } private Object getOrSetField(Object object, String fieldName, Object... value) { Object result = value.length == 1 ? value[0] : null; if (object == null) { fail("Could not access to " + fieldName + " in null object "); } Field field = null; boolean noSuchField = false; Class<?> clazz = object.getClass(); if (object instanceof Class<?>) { clazz = (Class<?>) object; } do { try { field = clazz.getDeclaredField(fieldName); noSuchField = false; } catch (SecurityException e) { noSuchField = true; // fail("Security exception when getting " + fieldName // + " field in class " + clazz + ": " + e); } catch (NoSuchFieldException e) { noSuchField = true; // fail("Could not find " + fieldName + " field in class " + // clazz // + ": " + e); } if (clazz.getName().equals(Object.class.getName())) { fail("Could not find " + fieldName + " field in any super classes of class " + clazz); } // Try to find the field in the super class clazz = clazz.getSuperclass(); } while (noSuchField); try { assertNotNull("Field found", field); field.setAccessible(true); if (value.length == 1) { field.set(object, result); } else { result = field.get(object); } } catch (IllegalAccessException e) { fail("Could not access to " + fieldName + " in object " + e); } catch (Exception e) { fail("Unexpected exception: " + e); } return result; } protected class MdoStringPropertiesComparator<T> implements Comparator<T> { String[] properties; public MdoStringPropertiesComparator(String... properties) { this.properties = properties; } @Override public int compare(T o1, T o2) { int result = 0; if (o1 == null && o2 == null) { } else if (o1 == null) { result = -1; } else if (o2 == null) { result = 1; } else { for (String property : properties) { if (result == 0) { String value1 = this.getProperty(o1, property); String value2 = this.getProperty(o1, property); if (value1 != null) { result = value1.compareTo(value2); } else if (value2 != null) { result = -1; } } } } return result; } private String getProperty(Object o, String property) { String result = null; try { result = BeanUtils.getProperty(o, property); } catch (Exception e) { fail(MdoTestCase.DEFAULT_FAILED_MESSAGE + ": " + e.getMessage()); } return result; } } }