/*
* Copyright [2012-2014] PayPal Software Foundation
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 ml.shifu.shifu;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class JavaBeanTester {
/**
* Tests the get/set methods of the specified class.
*/
public static <T> void test(final Class<T> clazz, boolean skipValidation, final String... skipThese)
throws IntrospectionException {
T bean = newInstance(clazz);
if (bean == null) {
throw new RuntimeException("Cannot create a bean class:" + clazz.getName());
}
final PropertyDescriptor[] props = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
List<String> skipList = Arrays.asList(skipThese);
for (PropertyDescriptor prop : props) {
// Check the list of properties that we don't want to test
if (skipList.contains(prop.getName())) {
continue;
}
final Method getter = prop.getReadMethod();
final Method setter = findSetter(prop.getWriteMethod(), getter, clazz);
Object actualValue = null;
Object expectedValue = null;
try {
if (getter != null) {
actualValue = getter.invoke(bean);
}
if (setter != null) {
final Class<?>[] params = setter.getParameterTypes();
if (params.length == 1) {
// The set method has 1 argument, which is of the same type as the return type of the get
// method, so
// we can test this property
// Build a value of the correct type to be passed to the set method
Object value = buildValue(params[0]);
// Call the set method, then check the same value comes back out of the get method
setter.invoke(bean, value);
expectedValue = value;
}
}
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
if (getter != null && setter != null && !skipValidation) {
if (!actualValue.equals(expectedValue)) {
throw new RuntimeException("After setter, getter gets wrong result: expectedValue:" + expectedValue
+ " actualValue:" + actualValue);
}
}
}
// call three common method
bean.toString();
bean.equals(bean);
bean.hashCode();
}
private static <T> Method findSetter(Method writeMethod, Method getter, Class<T> clazz) {
if (writeMethod != null) {
return writeMethod;
}
if (getter == null) {
return null;
}
String setterName = getter.getName().replaceAll("get", "set");
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
if (setterName.equals(methods[i].getName())) {
return methods[i];
}
}
return null;
}
@SuppressWarnings("unchecked")
private static <T> T newInstance(final Class<T> clazz) {
Constructor<?>[] ctrs = clazz.getDeclaredConstructors();
T initialInstance = null;
Arrays.sort(ctrs, new Comparator<Constructor<?>>() {
@Override
public int compare(Constructor<?> o1, Constructor<?> o2) {
int primitiveParamsSize1 = findPrimitiveParamsSize(o1);
int primitiveParamsSize2 = findPrimitiveParamsSize(o2);
return Integer.valueOf(primitiveParamsSize2).compareTo(primitiveParamsSize1);
}
private int findPrimitiveParamsSize(Constructor<?> o1) {
int size = 0;
for (int i = 0; i < o1.getParameterTypes().length; i++) {
if (o1.getParameterTypes()[i].isPrimitive() || o1.getParameterTypes()[i] == List.class
|| o1.getParameterTypes()[i] == Map.class || o1.getParameterTypes()[i] == Set.class) {
size++;
}
}
return size;
}
});
for (Constructor<?> ctr : ctrs) {
try {
ctr.setAccessible(true);
if (ctr.getParameterTypes().length == 0) {
// The class has a no-arg constructor, so just call it
initialInstance = (T) ctr.newInstance();
return initialInstance;
} else {
Object[] params = new Object[ctr.getParameterTypes().length];
for (int i = 0; i < ctr.getParameterTypes().length; i++) {
params[i] = buildValue(ctr.getParameterTypes()[i]);
}
return (T) ctr.newInstance(params);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
/**
* Tests the get/set methods of the specified class.
*/
public static <T> void test(final Class<T> clazz, final String... skipThese) throws IntrospectionException {
test(clazz, true, skipThese);
}
@SuppressWarnings("rawtypes")
private static Object buildValue(Class<?> clazz) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, SecurityException, InvocationTargetException {
// Next check for a no-arg constructor
// Specific rules for common classes
if (clazz == String.class) {
return "testvalue";
} else if (clazz.isArray()) {
return Array.newInstance(clazz.getComponentType(), 1);
} else if (clazz == boolean.class || clazz == Boolean.class) {
return true;
} else if (clazz == int.class || clazz == Integer.class) {
return 1;
} else if (clazz == long.class || clazz == Long.class) {
return 1L;
} else if (clazz == double.class || clazz == Double.class) {
return 1.0D;
} else if (clazz == float.class || clazz == Float.class) {
return 1.0F;
} else if (clazz == char.class || clazz == Character.class) {
return 'Y';
} else if (clazz == List.class) {
return new ArrayList();
} else if (clazz == Map.class) {
return new HashMap();
} else if (clazz == Set.class) {
return new HashSet();
} else if (clazz.isEnum()) {
return clazz.getEnumConstants()[0];
}
Class<?> newClazz = clazz;
if (clazz.isInterface()) {
throw new RuntimeException("Cannot find implement class for interface:" + clazz.getName());
// List<Class<?>> allClassesByInterface = null;
// try {
// allClassesByInterface = getAllClassesByInterface(clazz, false);
// } catch (Exception e) {
// throw new RuntimeException("Cannot find implement class for interface:" + clazz.getName(), e);
// }
// if(allClassesByInterface == null || allClassesByInterface.size() == 0) {
// throw new RuntimeException("Cannot find implement class for interface:" + clazz.getName());
// }
// newClazz = allClassesByInterface.get(0);
}
Object result = null;
result = newInstance(newClazz);
if (result != null) {
return result;
} else {
throw new RuntimeException("Cannot instance parameter with class:" + newClazz.getName());
}
}
public static List<Class<?>> getAllClassesByInterface(Class<?> interfaceClass, boolean samePackage)
throws IOException, ClassNotFoundException, IllegalStateException {
if (!interfaceClass.isInterface()) {
throw new IllegalStateException("Class is not a interface.");
}
String packageName = samePackage ? interfaceClass.getPackage().getName() : "/";
List<Class<?>> result = new ArrayList<Class<?>>();
String[] classpaths = System.getProperty("java.class.path").split(";");
for (int i = 0; i < classpaths.length; i++) {
if (classpaths[i].endsWith("jar")) {
if (classpaths[i].endsWith(".jar") || classpaths[i].endsWith(".zip")) {
ZipFile zip = new ZipFile(classpaths[i]);
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String thisClassName = getClassName(entry);
if (thisClassName.endsWith(".class")) {
Class<?> tmpClazz = null;
try {
Thread.currentThread().getContextClassLoader().loadClass(thisClassName);
tmpClazz = Class.forName(thisClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
continue;
}
if (interfaceClass.isAssignableFrom(tmpClazz) && !interfaceClass.equals(tmpClazz)) {
result.add(tmpClazz);
}
}
}
zip.close();
}
} else {
result.addAll(findResources(interfaceClass, new File(classpaths[i]), packageName));
}
}
return result;
}
private static String getClassName(ZipEntry entry) {
StringBuffer className = new StringBuffer(entry.getName().replace('/', '.'));
return className.toString();
}
public static List<Class<?>> findClasses(Class<?> interfaceClass, ClassLoader loader, String packageName)
throws IOException, ClassNotFoundException {
ClassLoader tmpLoader = loader;
List<Class<?>> allClasses = new ArrayList<Class<?>>();
// while(tmpLoader != null) {
String packagePath = packageName.replace(".", "/");
if (!packagePath.equals("/")) {
Enumeration<URL> resources = tmpLoader.getResources(packagePath);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
allClasses.addAll(findResources(interfaceClass, new File(url.getFile()), packageName));
}
} else {
String path = tmpLoader.getResource("").getPath();
allClasses.addAll(findResources(interfaceClass, new File(path), packageName));
}
// tmpLoader = loader.getParent();
// }
return allClasses;
}
@SuppressWarnings("unchecked")
private static List<Class<?>> findResources(Class<?> interfaceClass, File directory, String packageName)
throws ClassNotFoundException, IOException {
List<Class<?>> results = new ArrayList<Class<?>>();
if (directory == null || !directory.isDirectory())
return Collections.EMPTY_LIST;
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
if (!file.getName().contains(".")) {
if (!packageName.equals("/")) {
results.addAll(findResources(interfaceClass, file, packageName + "." + file.getName()));
} else {
results.addAll(findResources(interfaceClass, file, file.getName()));
}
}
} else if (file.getName().endsWith(".class")) {
Class<?> clazz = null;
if (!packageName.equals("/")) {
clazz = Class.forName(packageName + "." + file.getName().substring(0, file.getName().length() - 6));
} else {
clazz = Class.forName(file.getName().substring(0, file.getName().length() - 6));
}
if (interfaceClass.isAssignableFrom(clazz) && !interfaceClass.equals(clazz)) {
results.add(clazz);
}
}
}
} else {
throw new IOException(String.format("Failed to list files in %s", directory.getAbsolutePath()));
}
return results;
}
}