net.solarnetwork.util.ClassUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of net.solarnetwork.common Show documentation
Show all versions of net.solarnetwork.common Show documentation
Common supporting infrastructure for SolarNode and SolarNet applications.
/* ===================================================================
* ClassUtils.java
*
* Created Jul 15, 2008 8:20:38 AM
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ===================================================================
*/
package net.solarnetwork.util;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyBatchUpdateException;
import org.springframework.util.StringUtils;
import net.solarnetwork.domain.SerializeIgnore;
/**
* Utility methods for dealing with classes at runtime.
*
* @author matt
* @version 2.0
*/
public final class ClassUtils {
/**
* A set of package name prefix values representing built-in Java classes.
*
*
* This can be useful as an exclusion set in the
* {@link #getAllInterfacesForClassAsSet(Class, Set)} method.
*
*
* @since 1.3
*/
public static final Set JAVA_PACKAGE_PREFIXES = Collections
.unmodifiableSet(new HashSet(Arrays.asList("java.", "javax.")));
private static final Set DEFAULT_BEAN_PROP_NAME_IGNORE = new HashSet(
Arrays.asList(new String[] { "class" }));
/* Do not instantiate me. */
private ClassUtils() {
super();
}
/**
* Instantiate a class of a specific interface type.
*
* @param
* the desired interface type
* @param className
* the class name that implements the interface
* @param type
* the desired interface
* @return new instance of the desired type
*/
public static T instantiateClass(String className, Class type) {
Class extends T> clazz = loadClass(className, type);
try {
T o = clazz.newInstance();
return o;
} catch ( Exception e ) {
throw new RuntimeException("Unable to instantiate class [" + className + ']', e);
}
}
/**
* Load a class of a particular type.
*
*
* This uses the {@code type}'s ClassLoader to load the class. If that is
* not available, it will use the current thread's context class loader.
*
*
* @param
* the desired interface type
* @param className
* the class name that implements the interface
* @param type
* the desired interface
* @return the class
*/
public static Class extends T> loadClass(String className, Class type) {
try {
ClassLoader loader = type.getClassLoader();
if ( loader == null ) {
loader = Thread.currentThread().getContextClassLoader();
}
Class> clazz = loader.loadClass(className);
if ( !type.isAssignableFrom(clazz) ) {
throw new RuntimeException("Class [" + clazz + "] is not a [" + type + ']');
}
return clazz.asSubclass(type);
} catch ( ClassNotFoundException e ) {
throw new RuntimeException("Unable to load class [" + className + ']', e);
}
}
/**
* Set bean property values on an object from a Map.
*
* @param o
* the bean to set JavaBean properties on
* @param values
* a Map of JavaBean property names and their corresponding values to
* set
*/
public static void setBeanProperties(Object o, Map values) {
BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(o);
bean.setAutoGrowNestedPaths(true);
bean.setPropertyValues(values);
}
/**
* Set bean property values on an object from a Map.
*
* @param o
* The bean to set JavaBean properties on.
* @param values
* A Map of JavaBean property names and their corresponding values to
* set.
* @param ignoreErrors
* Flag to ignore unknown and invalid properties.
* @since 1.1
*/
public static void setBeanProperties(Object o, Map values, boolean ignoreErrors) {
if ( o == null || values == null ) {
return;
}
BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(o);
bean.setAutoGrowNestedPaths(true);
MutablePropertyValues pvs = new MutablePropertyValues(values);
try {
bean.setPropertyValues(pvs, ignoreErrors, ignoreErrors);
} catch ( PropertyBatchUpdateException e ) {
if ( ignoreErrors == false ) {
throw e;
}
}
}
/**
* Get a Map of non-null bean properties for an object.
*
* @param o
* the object to inspect
* @param ignore
* a set of property names to ignore (optional)
* @return Map (never null)
*/
public static Map getBeanProperties(Object o, Set ignore) {
if ( ignore == null ) {
ignore = DEFAULT_BEAN_PROP_NAME_IGNORE;
}
Map result = new LinkedHashMap();
BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(o);
PropertyDescriptor[] props = bean.getPropertyDescriptors();
for ( PropertyDescriptor prop : props ) {
if ( prop.getReadMethod() == null ) {
continue;
}
String propName = prop.getName();
if ( ignore != null && ignore.contains(propName) ) {
continue;
}
Object propValue = bean.getPropertyValue(propName);
if ( propValue == null ) {
continue;
}
result.put(propName, propValue);
}
return result;
}
/**
* Get a Map of non-null simple bean properties for an object.
*
* @param o
* the object to inspect
* @param ignore
* a set of property names to ignore (optional)
* @return Map (never {@literal null})
* @since 1.1
*/
public static Map getSimpleBeanProperties(Object o, Set ignore) {
if ( ignore == null ) {
ignore = DEFAULT_BEAN_PROP_NAME_IGNORE;
}
Map result = new LinkedHashMap();
BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(o);
PropertyDescriptor[] props = bean.getPropertyDescriptors();
for ( PropertyDescriptor prop : props ) {
if ( prop.getReadMethod() == null ) {
continue;
}
String propName = prop.getName();
if ( ignore != null && ignore.contains(propName) ) {
continue;
}
Class> propType = bean.getPropertyType(propName);
if ( !(propType.isPrimitive() || propType.isEnum() || String.class.isAssignableFrom(propType)
|| Number.class.isAssignableFrom(propType)
|| Character.class.isAssignableFrom(propType)
|| Byte.class.isAssignableFrom(propType)
|| Date.class.isAssignableFrom(propType)) ) {
continue;
}
Object propValue = bean.getPropertyValue(propName);
if ( propValue == null ) {
continue;
}
if ( propType.isEnum() ) {
propValue = propValue.toString();
} else if ( Date.class.isAssignableFrom(propType) ) {
propValue = ((Date) propValue).getTime();
}
result.put(propName, propValue);
}
return result;
}
/**
* Copy non-null bean properties from one object to another.
*
* @param src
* the object to copy values from
* @param dest
* the object to copy values to
* @param ignore
* a set of property names to ignore (optional) where {@literal null}
*/
public static void copyBeanProperties(Object src, Object dest, Set ignore) {
copyBeanProperties(src, dest, ignore, false);
}
/**
* Copy non-null bean properties from one object to another.
*
* @param src
* the object to copy values from
* @param dest
* the object to copy values to
* @param ignore
* a set of property names to ignore (optional)
* @param emptyStringToNull
* if {@literal true} then String values that are empty or contain only
* whitespace will be treated as if they where {@literal null}
*/
public static void copyBeanProperties(Object src, Object dest, Set ignore,
boolean emptyStringToNull) {
if ( ignore == null ) {
ignore = DEFAULT_BEAN_PROP_NAME_IGNORE;
}
BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(src);
BeanWrapper to = PropertyAccessorFactory.forBeanPropertyAccess(dest);
PropertyDescriptor[] props = bean.getPropertyDescriptors();
for ( PropertyDescriptor prop : props ) {
if ( prop.getReadMethod() == null ) {
continue;
}
String propName = prop.getName();
if ( ignore != null && ignore.contains(propName) ) {
continue;
}
Object propValue = bean.getPropertyValue(propName);
if ( propValue == null || (emptyStringToNull && (propValue instanceof String)
&& !StringUtils.hasText((String) propValue)) ) {
continue;
}
if ( to.isWritableProperty(propName) ) {
to.setPropertyValue(propName, propValue);
}
}
}
/**
* Get a Map of non-null bean properties for an object.
*
* @param o
* the object to inspect
* @param ignore
* a set of property names to ignore (optional)
* @param serializeIgnore
* if {@literal true} test for the {@link SerializeIgnore} annotation
* for ignoring properties
* @return Map (never null)
*/
public static Map getBeanProperties(Object o, Set ignore,
boolean serializeIgnore) {
if ( o == null ) {
return null;
}
if ( ignore == null ) {
ignore = DEFAULT_BEAN_PROP_NAME_IGNORE;
}
Map result = new LinkedHashMap();
BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(o);
PropertyDescriptor[] props = bean.getPropertyDescriptors();
for ( PropertyDescriptor prop : props ) {
if ( prop.getReadMethod() == null ) {
continue;
}
String propName = prop.getName();
if ( ignore != null && ignore.contains(propName) ) {
continue;
}
Object propValue = bean.getPropertyValue(propName);
if ( propValue == null ) {
continue;
}
if ( serializeIgnore ) {
Method getter = prop.getReadMethod();
if ( getter != null && getter.isAnnotationPresent(SerializeIgnore.class) ) {
continue;
}
}
result.put(propName, propValue);
}
return result;
}
/**
* Load a textual classpath resource into a String.
*
* @param resourceName
* the resource to load
* @param clazz
* the Class to load the resource from
* @return the String
*/
public static String getResourceAsString(String resourceName, Class> clazz) {
return getResourceAsString(resourceName, clazz, null);
}
/**
* Load a textual classpath resource into a String.
*
* @param resourceName
* the resource to load
* @param clazz
* the Class to load the resource from
* @param skip
* an optional pattern that will be used to match against lines;
* matches will be left out of the string used to match
* @return the text
* @throws RuntimeException
* if the resource cannot be loaded
* @since 1.3
*/
public static String getResourceAsString(String resourceName, Class> clazz, Pattern skip) {
try (InputStream in = clazz.getResourceAsStream(resourceName)) {
if ( in == null ) {
throw new RuntimeException(
"Resource " + resourceName + " not found from class " + clazz.getName() + ".");
}
StringBuilder buf = new StringBuilder(512);
try (BufferedReader r = new BufferedReader(
new InputStreamReader(in, Charset.forName("UTF-8")))) {
while ( true ) {
String line = r.readLine();
if ( line == null ) {
break;
}
if ( skip != null && skip.matcher(line).find() ) {
continue;
}
buf.append(line).append("\n");
}
}
if ( buf.length() > 0 ) {
// remove trailing newline
buf.deleteCharAt(buf.length() - 1);
}
return buf.toString();
} catch ( IOException e ) {
throw new RuntimeException("Error reading resource [" + resourceName + "]", e);
}
}
private static void addClassesToSetUnlessExcludedByPackagePrefix(Collection> classes,
Set> set, Set excluding) {
if ( classes == null ) {
return;
}
if ( excluding != null ) {
for ( Class> clazz : classes ) {
String fqn = clazz.getName();
for ( String prefix : excluding ) {
if ( fqn.startsWith(prefix) ) {
return;
}
}
}
}
set.addAll(classes);
}
/**
* Get all interfaces implemented by a class, excluding those in the
* {@link #JAVA_PACKAGE_PREFIXES} package set.
*
* @param clazz
* the class to get all implemented interfaces for
* @return the set of interfaces
* @since 1.1
*/
public static Set> getAllNonJavaInterfacesForClassAsSet(Class> clazz) {
return getAllInterfacesForClassAsSet(clazz, JAVA_PACKAGE_PREFIXES);
}
/**
* Get a set of interfaces implemented by a class and any superclasses or
* extended interfaces, optionally excluding based on a set of name prefix
* values.
*
*
* The iteration order of the returned set will be equal to the order
* returned by {@link Class#getInterfaces()} method, in reverse class
* hierarchy order.
*
*
* @param clazz
* the class to get all implemented interfaces for
* @param excluding
* a set of class name prefix values to exclude from the results
* @return the set of interfaces
* @since 1.1
*/
public static Set> getAllInterfacesForClassAsSet(Class> clazz, Set excluding) {
Set> interfaces = new LinkedHashSet>();
while ( clazz != null ) {
if ( clazz.isInterface() ) {
Set> classes = Collections.> singleton(clazz);
addClassesToSetUnlessExcludedByPackagePrefix(classes, interfaces, excluding);
}
Class>[] ifcs = clazz.getInterfaces();
for ( Class> ifc : ifcs ) {
addClassesToSetUnlessExcludedByPackagePrefix(
getAllInterfacesForClassAsSet(ifc, excluding), interfaces, excluding);
}
clazz = clazz.getSuperclass();
}
return interfaces;
}
}