All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.talsmasoftware.reflection.beans.BeanReflectionSupport Maven / Gradle / Ivy

There is a newer version: 1.0.5
Show newest version
/*
 * Copyright (C) 2016 Talsma ICT
 *
 * 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 nl.talsmasoftware.reflection.beans;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Collections.*;

/**
 * Support-class for bean reflection based on getter / setter methods and / or public field access for Java objects.
 * Unfortunately this is necessary because the standard {@link Introspector bean introspector} does not return any
 * public fields.
 * 

* Class diagram:

class diagram
* * @author Sjoerd Talsma */ public final class BeanReflectionSupport { private static final Logger LOGGER = Logger.getLogger(BeanReflectionSupport.class.getName()); /** * Cache, similar to the Introspector method cache. */ private static final Map, Reference>> reflectedPropertiesCache = new WeakHashMap, Reference>>(); /** * Private constructor to avoid instantiation of this class. */ private BeanReflectionSupport() { throw new UnsupportedOperationException(); } /** * This method reflects the requested object and returns a map from property name to property instance. * The map may not be modified again after it has been returned from this method; therefore an unmodifiable view of * the map will be returned. * * @param source The object to be reflected. * @return The unmodifiable map of reflected properties for the specified object. */ private static Map reflectedPropertiesOf(Object source) { if (source == null) return emptyMap(); final Class sourceType = source instanceof Class ? (Class) source : source.getClass(); Map properties = null; synchronized (reflectedPropertiesCache) { Reference> reference = reflectedPropertiesCache.get(sourceType); if (reference != null) properties = reference.get(); } if (properties == null) { properties = reflectProperties(sourceType); synchronized (reflectedPropertiesCache) { reflectedPropertiesCache.put(sourceType, new WeakReference>(properties)); } } return properties; } /** * The bean reflection logic, not to be called directly but always through {@link #reflectedPropertiesOf(Object)}. * That method provides important caching. * * @param sourceType The type to be reflected. * @return The map with reflected properties for the specified type. */ private static Map reflectProperties(Class sourceType) { Map properties = new LinkedHashMap(); if (sourceType != null) { addPublicFields(sourceType, properties); addPropertyDescriptors(sourceType, properties); } return unmodifiable(properties); } /** * Add all public fields for the sourceType to the map of reflected properties. * * @param sourceType The source type to be reflected. * @param properties The map of reflected properties to add public fields to. */ private static void addPublicFields(Class sourceType, Map properties) { for (Field field : sourceType.getFields()) { if (isPublic(field.getModifiers()) && !isStatic(field.getModifiers())) { properties.put(field.getName(), new ReflectedBeanProperty(null, field)); } } } /** * Add all {@link PropertyDescriptor property descriptors} from the {@link Introspector} to the map of reflected * properties. * * @param sourceType The source type to be reflected. * @param properties The map of reflected properties to add public fields to. */ private static void addPropertyDescriptors(Class sourceType, Map properties) { try { // java.beans.Introspector properties: for (PropertyDescriptor descriptor : Introspector.getBeanInfo(sourceType).getPropertyDescriptors()) { String name = descriptor.getName(); ReflectedBeanProperty property = properties.get(name); if (property == null) properties.put(name, new ReflectedBeanProperty(descriptor, null)); else properties.put(name, property.withDescriptor(descriptor)); } } catch (IntrospectionException is) { LOGGER.log(Level.FINEST, "Could not reflect bean information of {0} because: {1}", new Object[]{sourceType.getName(), is}); } catch (RuntimeException beanException) { LOGGER.log(Level.FINEST, "Exception reflecting bean information of {0}: {1}", new Object[]{sourceType.getName(), beanException}); } } /** * This method flushes the caches of internally reflected information and asks the Bean {@link Introspector} to do * the same. */ public static void flushCaches() { synchronized (reflectedPropertiesCache) { reflectedPropertiesCache.clear(); } Introspector.flushCaches(); } /** * This method returns a property value for a specific object instance. * * @param bean The object instance to return the requested property value for. * @param propertyName The name of the requested bean property. * @return The value of the property or null in case it could not be reflected. */ public static Object getPropertyValue(Object bean, String propertyName) { Object propertyValue = null; final ReflectedBeanProperty reflectedProperty = reflectedPropertiesOf(bean).get(propertyName); if (reflectedProperty == null) { LOGGER.log(Level.FINEST, "Property \"{0}\" not found in object: {1}", new Object[]{propertyName, bean}); } else { propertyValue = reflectedProperty.read(bean); } return propertyValue; } /** * This method writes the specified property value in an object instance. *

* Obviously, the object should have a 'writable' property with the specified name. * * @param bean The object instance to set the property value for. * @param propertyName The name of the (writable) bean property to be set. * @param propertyValue The new property value to be set. * @return true if the property was succesfully set, * or false if this was not possibile for some reason. */ public static boolean setPropertyValue(Object bean, String propertyName, Object propertyValue) { boolean result = false; final ReflectedBeanProperty reflectedProperty = reflectedPropertiesOf(bean).get(propertyName); if (reflectedProperty == null) { LOGGER.log(Level.FINEST, "Property \"{0}\" not found in object: {1}", new Object[]{propertyName, bean}); } else { result = reflectedProperty.write(bean, propertyValue); } return result; } /** * This method returns the reflected properties for the specified bean. * * @param bean The object instance to reflect the bean properties of. * @return The reflected bean properties. */ @SuppressWarnings("unchecked") // collection is read-only so this is safe. public static Collection getBeanProperties(Object bean) { final Collection properties = reflectedPropertiesOf(bean).values(); return (Collection) properties; } /** * This method returns all bean properties for the specified bean. * * @param bean The object instance to read the bean properties of. * @return A map of all bean property names mapping to the corresponding property values. */ public static Map getPropertyValues(Object bean) { Map propertyValues = new LinkedHashMap(); if (bean != null) for (BeanProperty property : getBeanProperties(bean)) { if (property.isReadable()) propertyValues.put(property.getName(), property.read(bean)); } return unmodifiable(propertyValues); } /** * This method creates a new bean instance of the requested type using the default constructor and attempts to * set all the specified property values. * * @param beanType The bean type to be instantiated using the default constructor. * @param propertyValues The properties that have to be set. * @param The bean type to be returned. * @return The instantiated bean with the specified property values. */ public static T createBean(Class beanType, Map propertyValues) { if (beanType == null) throw new IllegalArgumentException("Bean type was null."); try { T bean = beanType.getConstructor().newInstance(); if (propertyValues != null) for (Map.Entry propertyValue : propertyValues.entrySet()) { setPropertyValue(bean, propertyValue.getKey(), propertyValue.getValue()); } return bean; } catch (NoSuchMethodException nsme) { throw new IllegalArgumentException("Bean type " + beanType.getSimpleName() + " does not have an accessible default constructor.", nsme); } catch (InstantiationException ie) { throw new IllegalArgumentException("Bean type " + beanType.getSimpleName() + " could not be instantiated. Could it be that it is an abstract type?", ie); } catch (IllegalAccessException iae) { throw new IllegalArgumentException("Bean type " + beanType.getSimpleName() + " does not have an accessible default constructor.", iae); } catch (InvocationTargetException ite) { throw ite.getCause() instanceof RuntimeException ? (RuntimeException) ite.getCause() : new IllegalStateException("Exception occurred while creating a new instance of " + beanType.getSimpleName() + ": " + ite.getMessage(), ite.getCause()); } } /** * Returns an unmodifiable view of the given map. * * @param map The map to be made unmodifiable. * @param The type of the map keys. * @param The type of the map values. * @return An unmodifiable view of the given map. */ private static Map unmodifiable(Map map) { switch (map.size()) { case 0: return emptyMap(); case 1: final Map.Entry entry = map.entrySet().iterator().next(); return singletonMap(entry.getKey(), entry.getValue()); default: return unmodifiableMap(map); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy