#region License
/*
* Copyright 2002-2005 the original author or authors.
*
* 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.
*/
#endregion
using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using Common.Logging;
using Spring.Core;
using Spring.Expressions;
using Spring.Expressions.Parser.antlr;
using Spring.Util;
using StringUtilsSpring.Util.StringUtils;
namespace Spring.Objects{
/// <summary>
/// Default implementation of the <see cref="Spring.Objects.IObjectWrapper"/>
/// interface that should be sufficient for all normal uses.
/// </summary>
/// <remarks>
/// <p>
/// <see cref="Spring.Objects.ObjectWrapper"/> will convert
/// <see cref="System.Collections.IList"/> and array
/// values to the corresponding target arrays, if necessary. Custom
/// <see cref="System.ComponentModel.TypeConverter"/>s that deal with
/// <see cref="System.Collections.IList"/>s or arrays can be written against a
/// comma delimited <see cref="System.String"/> as <see cref="System.String"/>
/// arrays are converted in such a format if the array itself is not assignable.
/// </p>
/// </remarks>
/// <author>Rod Johnson</author>
/// <author>Juergen Hoeller </author>
/// <author>Jean-Pierre Pawlak</author>
/// <author>Mark Pollack (.NET)</author>
/// <author>Aleksandar Seovic(.NET)</author>
[Serializable]
public class ObjectWrapper : IObjectWrapper
{
private ILog Log = LogManager.GetLogger(typeof(ObjectWrapper));
#region Fields
/// <summary>The wrapped object.</summary>
private object wrappedObject;
/// <summary>
/// The ILog instance for this class. We'll create a lot of these objects,
/// so we don't want a new instance every time.
/// </summary>
private static readonly ILog log = LogManager.GetLogger(typeof(ObjectWrapper));
#endregion
#region Constructor (s) / Destructor
/// <summary>
/// Creates a new instance of the <see cref="Spring.Objects.ObjectWrapper"/> class.
/// </summary>
/// <remarks>
/// <p>
/// The wrapped target instance will need to be set afterwards.
/// </p>
/// </remarks>
/// <seealso cref="Spring.Objects.ObjectWrapper.WrappedInstance"/>
public ObjectWrapper()
{}
/// <summary>
/// Creates a new instance of the <see cref="Spring.Objects.ObjectWrapper"/> class.
/// </summary>
/// <param name="instance">
/// The object wrapped by this <see cref="Spring.Objects.ObjectWrapper"/>.
/// </param>
/// <exception cref="Spring.Objects.FatalObjectException">
/// If the supplied <paramref name="instance"/> is <see lang="null"/>.
/// </exception>
public ObjectWrapper(object instance)
{
WrappedInstance = instance;
}
/// <summary>
/// Creates a new instance of the <see cref="Spring.Objects.ObjectWrapper"/> class,
/// instantiating a new instance of the specified <see cref="System.Type"/> and using
/// it as the <see cref="Spring.Objects.ObjectWrapper.WrappedInstance"/>.
/// </summary>
/// <remarks>
/// <p>
/// Please note that the <see cref="System.Type"/> passed as the
/// <paramref name="type"/> argument must have a no-argument constructor.
/// If it does not, an exception will be thrown when this class attempts
/// to instantiate the supplied <paramref name="type"/> using it's
/// (non-existent) constructor.
/// </p>
/// </remarks>
/// <param name="type">
/// The <see cref="System.Type"/> to instantiate and wrap.
/// </param>
/// <exception cref="Spring.Objects.FatalObjectException">
/// If the <paramref name="type"/> is <see langword="null"/>, or if the
/// invocation of the <paramref name="type"/>s default (no-arg) constructor
/// fails (due to invalid arguments, insufficient permissions, etc).
/// </exception>
public ObjectWrapper(Type type) : this(true)
{
try
{
WrappedInstance = ObjectUtils.InstantiateType(type);
} catch (FatalReflectionException e)
{
throw new FatalObjectException(e.Message, e);
}
}
#endregion
#region Properties
/// <summary>
/// The object wrapped by this <see cref="Spring.Objects.ObjectWrapper"/>.
/// </summary>
/// <exception cref="Spring.Objects.FatalObjectException">
/// If the object cannot be changed; or an attempt is made to set the
/// value of this property to <see langword="null"/>.
/// </exception>
public object WrappedInstance
{
get { return wrappedObject; }
set
{
if (value == null)
{
throw new FatalObjectException("Wraped instance cannot be null.");
}
this.wrappedObject = value;
}
}
/// <summary>
/// Convenience method to return the <see cref="System.Type"/> of the wrapped object.
/// </summary>
/// <remarks>
/// <p>
/// Do <b>not</b> use this (convenience) method prior to setting the
/// <see cref="Spring.Objects.ObjectWrapper.WrappedInstance"/> property.
/// </p>
/// </remarks>
/// <returns>
/// The <see cref="System.Type"/> of the wrapped object.
/// </returns>
/// <exception cref="System.NullReferenceException">
/// If the <see cref="Spring.Objects.ObjectWrapper.WrappedInstance"/> property
/// is <see lang="null"/>.
/// </exception>
public Type WrappedType
{
get { return WrappedInstance.GetType(); }
}
#endregion
#region Methods
/// <summary>Gets the value of a property.</summary>
/// <param name="propertyName">
/// The name of the property to get the value of.
/// </param>
/// <returns>The value of the property.</returns>
/// <exception cref="Spring.Objects.FatalObjectException">
/// If there is no such property, if the property isn't readable, or
/// if getting the property value throws an exception.
/// </exception>
public virtual object GetPropertyValue(string propertyName)
{
try
{
IExpression propertyExpression = GetPropertyExpression(propertyName);
return GetPropertyValue(propertyExpression);
}
catch (RecognitionException e)
{
throw new InvalidPropertyException("Failed to parse property name '" + propertyName + "'.", e);
}
catch (TokenStreamRecognitionException e)
{
throw new InvalidPropertyException("Failed to parse property name '" + propertyName + "'.", e);
}
}
/// <summary>Gets the value of a property.</summary>
/// <param name="propertyExpression">
/// The property expression that should be used to retrieve the property value.
/// </param>
/// <returns>The value of the property.</returns>
/// <exception cref="Spring.Objects.FatalObjectException">
/// If there is no such property, if the property isn't readable, or
/// if getting the property value throws an exception.
/// </exception>
public virtual object GetPropertyValue(IExpression propertyExpression)
{
return propertyExpression.GetValue(this.wrappedObject);
}
/// <summary>
/// Sets a property value.
/// </summary>
/// <remarks>
/// <p>
/// This method is provided for convenience only. The
/// <see cref="Spring.Objects.ObjectWrapper.SetPropertyValue(PropertyValue)"/>
/// method is more powerful.
/// </p>
/// </remarks>
/// <param name="propertyName">
/// The name of the property to set value of.
/// </param>
/// <param name="val">The new value.</param>
public virtual void SetPropertyValue(string propertyName, object val)
{
try
{
IExpression propertyExpression = GetPropertyExpression(propertyName);
SetPropertyValue(propertyExpression, val);
}
catch (RecognitionException e)
{
throw new InvalidPropertyException("Failed to parse property name '" + propertyName + "'.", e);
}
catch (TokenStreamRecognitionException e)
{
throw new InvalidPropertyException("Failed to parse property name '" + propertyName + "'.", e);
}
}
/// <summary>
/// Sets a property value.
/// </summary>
/// <param name="propertyExpression">
/// The property expression that should be used to set the property value.
/// </param>
/// <param name="val">The new value.</param>
public virtual void SetPropertyValue(IExpression propertyExpression, object val)
{
propertyExpression.SetValue(this.wrappedObject, val);
}
/// <summary>
/// Sets a property value.
/// </summary>
/// <remarks>
/// <p>
/// <b>This is the preferred way to update an individual property.</b>
/// </p>
/// </remarks>
/// <param name="pv">
/// The object containing new property value.
/// </param>
public virtual void SetPropertyValue(PropertyValue pv)
{
SetPropertyValue(pv.Expression, pv.Value);
}
/// <summary>Set a number of property values in bulk.</summary>
/// <remarks>
/// <p>
/// Does not allow unknown fields. Equivalent to
/// <see cref="Spring.Objects.ObjectWrapper.SetPropertyValues(IPropertyValues, bool)"/>
/// with <see langword="null"/> and <cref lang="false"/> for
/// arguments.
/// </p>
/// </remarks>
/// <param name="pvs">
/// The <see cref="Spring.Objects.IPropertyValues"/> to set on the target
/// object.
/// </param>
/// <exception cref="Spring.Core.NotWritablePropertyException">
/// If an error is encountered while setting a property.
/// </exception>
/// <exception cref="Spring.Objects.PropertyAccessExceptionsException">
/// On a <see cref="System.Type"/> mismatch while setting a property, insufficient permissions, etc.
/// </exception>
/// <seealso cref="Spring.Objects.IObjectWrapper.SetPropertyValues(IPropertyValues, bool)"/>
public virtual void SetPropertyValues(IPropertyValues pvs)
{
SetPropertyValues(pvs, false);
}
/// <summary>
/// Perform a bulk update with full control over behavior.
/// </summary>
/// <remarks>
/// <p>
/// This method may throw a reflection-based exception, if there is a critical
/// failure such as no matching field... less serious exceptions will be accumulated
/// and thrown as a single <see cref="Spring.Objects.PropertyAccessExceptionsException"/>.
/// </p>
/// </remarks>
/// <param name="propertyValues">
/// The <see cref="Spring.Objects.PropertyValue"/>s to set on the target object.
/// </param>
/// <param name="ignoreUnknown">
/// Should we ignore unknown values (not found in the object!?).
/// </param>
/// <exception cref="NotWritablePropertyException">
/// If an error is encountered while setting a property (only thrown if the
/// <paramref name="ignoreUnknown"/> parameter is set to <see langword="false"/>).
/// </exception>
/// <exception cref="Spring.Objects.PropertyAccessExceptionsException">
/// On a <see cref="System.Type"/> mismatch while setting a property, insufficient permissions, etc.
/// </exception>
/// <seealso cref="Spring.Objects.IObjectWrapper.SetPropertyValues(IPropertyValues, bool)"/>
public virtual void SetPropertyValues(IPropertyValues propertyValues, bool ignoreUnknown)
{
ArrayList propertyAccessExceptions = new ArrayList();
foreach (PropertyValue pv in propertyValues)
{
try
{
SetPropertyValue(pv);
}
catch (NotWritablePropertyException ex)
{
if (!ignoreUnknown)
{
Log.Error(string.Format("Failed setting property '{0}'", pv.Name), ex);
throw;
}
}
catch (InvalidPropertyException ex)
{
if (!ignoreUnknown)
{
Log.Error(string.Format("Failed setting property '{0}'", pv.Name), ex);
throw;
}
}
catch (TypeMismatchException ex) // otherwise, just ignore it and continue...
{
Log.Error(string.Format("Failed setting property '{0}'", pv.Name), ex);
propertyAccessExceptions.Add(ex);
}
catch (MethodInvocationException ex)
{
Log.Error(string.Format("Failed setting property '{0}'", pv.Name), ex);
propertyAccessExceptions.Add(ex);
}
catch (Exception ex)
{
Log.Error(string.Format("Failed setting property '{0}' on instance of type '{1}'", pv.Name, this.WrappedType.FullName), ex);
throw;
}
}
// if we encountered individual exceptions, throw the composite exception...
if (propertyAccessExceptions.Count > 0)
{
throw new PropertyAccessExceptionsException(this,
(PropertyAccessException[]) propertyAccessExceptions.ToArray(typeof(PropertyAccessException)));
}
}
/// <summary>
/// Returns PropertyInfo for the specified property
/// </summary>
/// <param name="propertyName">The name of the property to search for.</param>
/// <returns>The <see cref="PropertyInfo"/> for the specified property.</returns>
/// <exception cref="FatalObjectException">If <see cref="PropertyInfo"/> cannot be determined.</exception>
public PropertyInfo GetPropertyInfo(string propertyName)
{
return (PropertyInfo) this.GetPropertyOrFieldInfo(propertyName);
}
/// <summary>
/// Get the <see cref="System.Type"/> for a particular property.
/// </summary>
/// <param name="propertyName">
/// The property the <see cref="System.Type"/> of which is to be retrieved.
/// </param>
/// <returns>
/// The <see cref="System.Type"/> for a particular property..
/// </returns>
public Type GetPropertyType(string propertyName)
{
MemberInfo memberInfo = this.GetPropertyOrFieldInfo(propertyName);
switch(memberInfo.MemberType)
{
case MemberTypes.Property:
return ((PropertyInfo) memberInfo).PropertyType;
case MemberTypes.Field:
return ((FieldInfo) memberInfo).FieldType;
default:
throw new FatalObjectException("'" + propertyName + "' is not a valid property expression.");
}
}
/// <summary>
/// Returns MemberInfo for the specified property or field
/// </summary>
/// <param name="propertyOrFieldName">The name of the property or field to search for.</param>
/// <returns>The <see cref="PropertyInfo"/> or <see cref="FieldInfo"/> for the specified property or field.</returns>
/// <exception cref="FatalObjectException">If <paramref name="propertyOrFieldName"/> does not resolve to a property or field.</exception>
private MemberInfo GetPropertyOrFieldInfo(string propertyOrFieldName)
{
if(StringUtils.IsNullOrEmpty(propertyOrFieldName))
{
throw new FatalObjectException("Can't find property or field info for null or zero length property name.");
}
try
{
IExpression propertyExpression = GetPropertyExpression(propertyOrFieldName);
if(propertyExpression is PropertyOrFieldNode)
{
return ((PropertyOrFieldNode)propertyExpression).GetMemberInfo(this.wrappedObject);
}
else if(propertyExpression is IndexerNode)
{
return ((IndexerNode)propertyExpression).GetPropertyInfo(this.wrappedObject, null);
}
else if(propertyExpression is Expression)
{
return ((Expression)propertyExpression).GetPropertyInfo(this.wrappedObject, null);
}
else
{
throw new FatalObjectException("'" + propertyOrFieldName + "' is not a valid property or field expression.");
}
}
catch(RecognitionException e)
{
throw new FatalObjectException("Failed to parse property or field name '" + propertyOrFieldName + "'.", e);
}
catch(TokenStreamRecognitionException e)
{
throw new FatalObjectException("Failed to parse property or field name '" + propertyOrFieldName + "'.", e);
}
}
/// <summary>
/// Get the properties of the wrapped object.
/// </summary>
/// <returns>
/// An array of <see cref="System.Reflection.PropertyInfo"/>s.
/// </returns>
public PropertyInfo[] GetPropertyInfos()
{
return WrappedType.GetProperties();
}
/// <summary>
/// Return the collection of property descriptors.
/// </summary>
public PropertyDescriptorCollection PropertyDescriptors
{
get { return TypeDescriptor.GetProperties(WrappedInstance); }
}
/// <summary>
/// This method is expensive! Only call for diagnostics and debugging reasons,
/// not in production.
/// </summary>
/// <returns>
/// A string describing the state of this object.
/// </returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
try
{
sb.Append("ObjectWrapper: wrapping class [");
sb.Append(WrappedType.FullName);
sb.Append("]; ");
foreach (PropertyDescriptor p in PropertyDescriptors)
{
object val = GetPropertyValue(p.Name);
string valStr = (val != null) ? val.ToString() : "null";
sb.Append(p.Name).Append("={").Append(valStr).Append("}");
}
}
catch (Exception ex)
{
sb.Append("Exception encountered: ").Append(ex.ToString());
}
return sb.ToString();
}
#endregion
#region Helper methods
/// <summary>
/// Attempts to parse property expression first and falls back to full expression
/// if that fails. Performance optimization.
/// </summary>
/// <param name="propertyName">Property expression to parse.</param>
/// <returns>Parsed proeprty expression.</returns>
internal static IExpression GetPropertyExpression(string propertyName)
{
IExpression propertyExpression = null;
if (propertyName.IndexOfAny(new char[] { '.', '[', '(', ' ', '{' }) < 0)
{
try
{
propertyExpression = Expression.ParseProperty(propertyName);
}
catch (Exception)
{
propertyExpression = Expression.ParsePrimary(propertyName);
}
}
else
{
propertyExpression = Expression.ParsePrimary(propertyName);
}
return propertyExpression;
}
#endregion
}
}
|