SPRNET-1325

Introduced support for generics-friendly-get-type-as-string method and adjusted sdkRegularExpressionPointcut to rely upon this for pattern-matching
This commit is contained in:
sbohlen
2010-10-01 16:17:43 +00:00
parent ce22e68a9d
commit 448753d110
4 changed files with 346 additions and 263 deletions

View File

@@ -1,5 +1,5 @@
#region License
#region License
/*
* Copyright <20> 2002-2005 the original author or authors.
*
@@ -14,97 +14,97 @@
* 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
#region Imports
using System;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;
using AopAlliance.Aop;
using Spring.Core;
using Spring.Objects;
using Spring.Util;
#endregion
namespace Spring.Aop.Support
{
/// <summary>
/// Abstract base regular expression pointcut object.
/// </summary>
/// <remarks>
/// <p>
/// The regular expressions must be a match. For example, the
/// <code>.*Get.*</code> pattern will match <c>Com.Mycom.Foo.GetBar()</c>, and
/// <code>Get.*</code> will not.
/// </p>
/// <p>
/// This base class is serializable. Subclasses should decorate all
/// fields with the <see cref="System.NonSerializedAttribute"/> - the
/// <see cref="AbstractRegularExpressionMethodPointcut.InitPatternRepresentation"/>
/// method in this class will be invoked again on the client side on deserialization.
/// </p>
/// </remarks>
/// <author>Rod Johnson</author>
/// <author>Juergen Hoeller</author>
/// <author>Simon White (.NET)</author>
[Serializable]
public abstract class AbstractRegularExpressionMethodPointcut
: StaticMethodMatcherPointcut, ITypeFilter, ISerializable
{
[NonSerialized]
private object[] _patterns = ObjectUtils.EmptyObjects;
#region Constructors
/// <summary>
/// Creates a new instance of the
/// <see cref="AbstractRegularExpressionMethodPointcut"/>
/// class.
/// </summary>
/// <remarks>
/// <p>
/// This is an abstract class, and as such has no publicly
/// visible constructors.
/// </p>
/// </remarks>
protected AbstractRegularExpressionMethodPointcut()
{
}
/// <summary>
/// Creates a new instance of the <see cref="AbstractRegularExpressionMethodPointcut"/>
/// class.
/// </summary>
/// <param name="info">
/// The <see cref="System.Runtime.Serialization.SerializationInfo"/>
/// that holds the serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="System.Runtime.Serialization.StreamingContext"/>
/// that contains contextual information about the source or destination.
/// </param>
/// <exception cref="AopAlliance.Aop.AspectException">
/// If an error was encountered during the deserialization process.
/// </exception>
protected AbstractRegularExpressionMethodPointcut(
SerializationInfo info, StreamingContext context)
{
_patterns = (object[]) info.GetValue("Patterns", typeof(object[]));
*/
#endregion
#region Imports
using System;
using System.Reflection;
using System.Runtime.Serialization;
using System.Security.Permissions;
using AopAlliance.Aop;
using Spring.Core;
using Spring.Objects;
using Spring.Util;
#endregion
namespace Spring.Aop.Support
{
/// <summary>
/// Abstract base regular expression pointcut object.
/// </summary>
/// <remarks>
/// <p>
/// The regular expressions must be a match. For example, the
/// <code>.*Get.*</code> pattern will match <c>Com.Mycom.Foo.GetBar()</c>, and
/// <code>Get.*</code> will not.
/// </p>
/// <p>
/// This base class is serializable. Subclasses should decorate all
/// fields with the <see cref="System.NonSerializedAttribute"/> - the
/// <see cref="AbstractRegularExpressionMethodPointcut.InitPatternRepresentation"/>
/// method in this class will be invoked again on the client side on deserialization.
/// </p>
/// </remarks>
/// <author>Rod Johnson</author>
/// <author>Juergen Hoeller</author>
/// <author>Simon White (.NET)</author>
[Serializable]
public abstract class AbstractRegularExpressionMethodPointcut
: StaticMethodMatcherPointcut, ITypeFilter, ISerializable
{
[NonSerialized]
private object[] _patterns = ObjectUtils.EmptyObjects;
#region Constructors
/// <summary>
/// Creates a new instance of the
/// <see cref="AbstractRegularExpressionMethodPointcut"/>
/// class.
/// </summary>
/// <remarks>
/// <p>
/// This is an abstract class, and as such has no publicly
/// visible constructors.
/// </p>
/// </remarks>
protected AbstractRegularExpressionMethodPointcut()
{
}
/// <summary>
/// Creates a new instance of the <see cref="AbstractRegularExpressionMethodPointcut"/>
/// class.
/// </summary>
/// <param name="info">
/// The <see cref="System.Runtime.Serialization.SerializationInfo"/>
/// that holds the serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="System.Runtime.Serialization.StreamingContext"/>
/// that contains contextual information about the source or destination.
/// </param>
/// <exception cref="AopAlliance.Aop.AspectException">
/// If an error was encountered during the deserialization process.
/// </exception>
protected AbstractRegularExpressionMethodPointcut(
SerializationInfo info, StreamingContext context)
{
_patterns = (object[])info.GetValue("Patterns", typeof(object[]));
if (_patterns == null)
{
_patterns = ObjectUtils.EmptyObjects;
}
}
/// <summary>
/// Overridden to ensure proper initialization
/// </summary>
protected override void OnDeserialization(object sender)
/// <summary>
/// Overridden to ensure proper initialization
/// </summary>
protected override void OnDeserialization(object sender)
{
base.OnDeserialization(sender);
try
@@ -116,172 +116,184 @@ namespace Spring.Aop.Support
throw new AspectException(
"Failed to deserialize AOP regular expression pointcut: " + ex.Message);
}
}
#endregion
#region Properties
/// <summary>
/// The <see cref="Spring.Aop.ITypeFilter"/> for this pointcut.
/// </summary>
/// <value>
/// The current <see cref="Spring.Aop.ITypeFilter"/>.
/// </value>
public override ITypeFilter TypeFilter
{
get { return this; }
}
/// <summary>
/// Convenience property for setting a single pattern.
/// </summary>
/// <remarks>
/// Use this property or Patterns, not both.
/// </remarks>
public virtual object Pattern
{
get { return (_patterns.Length > 0 ? _patterns[0] : null); }
set
{
AssertUtils.ArgumentNotNull(value, "Pattern");
this.Patterns = new object[] {value};
}
}
/// <summary>
/// The regular expressions defining methods to match.
/// </summary>
/// <remarks>
/// Matching will be the union of all these; if any match,
/// the pointcut matches.
/// </remarks>
public virtual object[] Patterns
{
get { return _patterns; }
set
{
AssertUtils.ArgumentNotNull(value, "Patterns");
this._patterns = value;
InitPatternRepresentation(this.Patterns);
}
}
#endregion
#region Methods
/// <summary>
/// Populates a <see cref="System.Runtime.Serialization.SerializationInfo"/> with
/// the data needed to serialize the target object.
/// </summary>
/// <param name="info">
/// The <see cref="System.Runtime.Serialization.SerializationInfo"/> to populate
/// with data.
/// </param>
/// <param name="context">
/// The destination (see <see cref="System.Runtime.Serialization.StreamingContext"/>)
/// for this serialization.
/// </param>
[SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Patterns", _patterns);
}
/// <summary>
/// Subclasses must implement this to initialize regular expression pointcuts.
/// </summary>
/// <remarks>
/// <p>
/// Can be invoked multiple times.
/// </p>
/// <p>
/// This method will be invoked from the <see cref="Patterns"/> property,
/// and also on deserialization.
/// </p>
/// </remarks>
/// <param name="patterns">
/// The patterns to initialize.
/// </param>
/// <exception cref="System.ArgumentException">
/// In the case of an invalid pattern.
/// </exception>
protected abstract void InitPatternRepresentation(object[] patterns);
/// <summary>
/// Does the pattern at the supplied <paramref name="patternIndex"/>
/// match this <paramref name="pattern"/>?
/// </summary>
/// <param name="pattern">The pattern to match</param>
/// <param name="patternIndex">The index of pattern.</param>
/// <returns>
/// <see langword="true"/> if there is a match.
/// </returns>
protected abstract bool Matches(string pattern, int patternIndex);
/// <summary>
/// Does the supplied <paramref name="method"/> satisfy this matcher?
/// </summary>
/// <remarks>
/// <p>
/// Try to match the regular expression against the fully qualified name
/// of the method's declaring <see cref="System.Type"/>, plus the name of
/// the supplied <paramref name="method"/>.
/// </p>
/// <p>
/// Note that the declaring <see cref="System.Type"/> is that
/// <see cref="System.Type"/> that originally declared
/// the method, not necessarily the <see cref="System.Type"/> that is
/// currently exposing it. For example, <see cref="System.Object.Equals(object)"/>
/// matches any subclass of <see cref="System.Object"/>'s
/// <see cref="System.Object.Equals(object)"/> method.
/// </p>
/// </remarks>
/// <param name="method">The candidate method.</param>
/// <param name="targetType">
/// The target <see cref="System.Type"/> (may be <see langword="null"/>,
/// in which case the candidate <see cref="System.Type"/> must be taken
/// to be the <paramref name="method"/>'s declaring class).
/// </param>
/// <returns>
/// <see langword="true"/> if this this method matches statically.
/// </returns>
public override bool Matches(MethodInfo method, Type targetType)
{
string patt = method.DeclaringType.FullName + "." + method.Name;
for (int i = 0; i < this.Patterns.Length; ++i)
{
bool matched = Matches(patt, i);
if (matched)
{
return true;
}
}
return false;
}
/// <summary>
/// Should the pointcut apply to the supplied
/// <see cref="System.Type"/>?
/// </summary>
/// <remarks>
/// <p>
/// In this instance, simply returns <see langword="true"/>.
/// </p>
/// </remarks>
/// <param name="type">
/// The candidate <see cref="System.Type"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the advice should apply to the supplied
/// <paramref name="type"/>
/// </returns>
public bool Matches(Type type)
{
return true;
}
#endregion
}
}
#endregion
#region Properties
/// <summary>
/// The <see cref="Spring.Aop.ITypeFilter"/> for this pointcut.
/// </summary>
/// <value>
/// The current <see cref="Spring.Aop.ITypeFilter"/>.
/// </value>
public override ITypeFilter TypeFilter
{
get { return this; }
}
/// <summary>
/// Convenience property for setting a single pattern.
/// </summary>
/// <remarks>
/// Use this property or Patterns, not both.
/// </remarks>
public virtual object Pattern
{
get { return (_patterns.Length > 0 ? _patterns[0] : null); }
set
{
AssertUtils.ArgumentNotNull(value, "Pattern");
this.Patterns = new object[] { value };
}
}
/// <summary>
/// The regular expressions defining methods to match.
/// </summary>
/// <remarks>
/// Matching will be the union of all these; if any match,
/// the pointcut matches.
/// </remarks>
public virtual object[] Patterns
{
get { return _patterns; }
set
{
AssertUtils.ArgumentNotNull(value, "Patterns");
this._patterns = value;
InitPatternRepresentation(this.Patterns);
}
}
#endregion
#region Methods
/// <summary>
/// Populates a <see cref="System.Runtime.Serialization.SerializationInfo"/> with
/// the data needed to serialize the target object.
/// </summary>
/// <param name="info">
/// The <see cref="System.Runtime.Serialization.SerializationInfo"/> to populate
/// with data.
/// </param>
/// <param name="context">
/// The destination (see <see cref="System.Runtime.Serialization.StreamingContext"/>)
/// for this serialization.
/// </param>
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Patterns", _patterns);
}
/// <summary>
/// Subclasses must implement this to initialize regular expression pointcuts.
/// </summary>
/// <remarks>
/// <p>
/// Can be invoked multiple times.
/// </p>
/// <p>
/// This method will be invoked from the <see cref="Patterns"/> property,
/// and also on deserialization.
/// </p>
/// </remarks>
/// <param name="patterns">
/// The patterns to initialize.
/// </param>
/// <exception cref="System.ArgumentException">
/// In the case of an invalid pattern.
/// </exception>
protected abstract void InitPatternRepresentation(object[] patterns);
/// <summary>
/// Does the pattern at the supplied <paramref name="patternIndex"/>
/// match this <paramref name="pattern"/>?
/// </summary>
/// <param name="pattern">The pattern to match</param>
/// <param name="patternIndex">The index of pattern.</param>
/// <returns>
/// <see langword="true"/> if there is a match.
/// </returns>
protected abstract bool Matches(string pattern, int patternIndex);
/// <summary>
/// Does the supplied <paramref name="method"/> satisfy this matcher?
/// </summary>
/// <remarks>
/// <p>
/// Try to match the regular expression against the fully qualified name
/// of the method's declaring <see cref="System.Type"/>, plus the name of
/// the supplied <paramref name="method"/>.
/// </p>
/// <p>
/// Note that the declaring <see cref="System.Type"/> is that
/// <see cref="System.Type"/> that originally declared
/// the method, not necessarily the <see cref="System.Type"/> that is
/// currently exposing it. For example, <see cref="System.Object.Equals(object)"/>
/// matches any subclass of <see cref="System.Object"/>'s
/// <see cref="System.Object.Equals(object)"/> method.
/// </p>
/// </remarks>
/// <param name="method">The candidate method.</param>
/// <param name="targetType">
/// The target <see cref="System.Type"/> (may be <see langword="null"/>,
/// in which case the candidate <see cref="System.Type"/> must be taken
/// to be the <paramref name="method"/>'s declaring class).
/// </param>
/// <returns>
/// <see langword="true"/> if this this method matches statically.
/// </returns>
public override bool Matches(MethodInfo method, Type targetType)
{
string patt = string.Empty;
patt = String.Format("{0}.{1}", method.DeclaringType, method.Name);
#if NET_2_0
if (method.DeclaringType.IsGenericType)
{
patt = String.Format("{0}.{1}", ReflectionUtils.GetTypeFriendlyName(method.DeclaringType), method.Name);
}
else
{
patt = String.Format("{0}.{1}", method.DeclaringType, method.Name);
}
#endif
for (int i = 0; i < this.Patterns.Length; ++i)
{
bool matched = Matches(patt, i);
if (matched)
{
return true;
}
}
return false;
}
/// <summary>
/// Should the pointcut apply to the supplied
/// <see cref="System.Type"/>?
/// </summary>
/// <remarks>
/// <p>
/// In this instance, simply returns <see langword="true"/>.
/// </p>
/// </remarks>
/// <param name="type">
/// The candidate <see cref="System.Type"/>.
/// </param>
/// <returns>
/// <see langword="true"/> if the advice should apply to the supplied
/// <paramref name="type"/>
/// </returns>
public bool Matches(Type type)
{
return true;
}
#endregion
}
}

View File

@@ -669,6 +669,33 @@ namespace Spring.Util
return paramsType;
}
/// <summary>
/// Given the <see cref="System.Type"/> return its representation as
/// it would appear in the source code files.
/// </summary>
/// <remarks>
/// Largely intended to handle generic types where .ToString() will typically return:
/// "System.Collections.Generic.List`1[System.Collections.Generic.Dictionary`2[System.String,System.Int32]]"
/// and this method will instead return:
/// "System.Collections.Generic.List&lt;System.Collections.Generic.Dictionary&lt;string,int&gt;&gt;"
/// </remarks>
/// <param name="type">The type.</param>
/// <returns>Friendly string representing the Type</returns>
public static string GetTypeFriendlyName(Type type)
{
#if MONO
//no csharp services in mono (verify this!) so have to fall back to returning the ToString() for now
//TODO: investigate whether there is another equivalent manner of providing this functionality under MONO
return type.ToString();
#endif
#if NET_2_0
return (new Microsoft.CSharp.CSharpCodeProvider()).GetTypeOutput(new System.CodeDom.CodeTypeReference(type));
#endif
return type.ToString();
}
/// <summary>
/// Does the given <see cref="System.Type"/> and/or it's superclasses
/// have at least one or more methods with the given name (with any

View File

@@ -74,12 +74,46 @@ namespace Spring.Aop.Support
ExactMatchTests(pointcut);
}
protected void ExactMatchTests(AbstractRegularExpressionMethodPointcut rpc)
{
// assumes rpc.setPattern("java.lang.Object.hashCode");
Assert.IsTrue(rpc.Matches(typeof(object).GetMethod("GetHashCode"), typeof(int)));
Assert.IsFalse(rpc.Matches(typeof(object).GetMethod("GetType"), typeof(Type)));
}
#if NET_2_0
protected void ExactMatchWithGenericTypeTests(AbstractRegularExpressionMethodPointcut rpc)
{
// assumes rpc.setPattern("System.Collections.Generic.List<string>");
Assert.IsTrue(rpc.Matches(typeof(System.Collections.Generic.List<string>).GetMethod("Add"), typeof(int)));
Assert.IsFalse(rpc.Matches(typeof(System.Collections.Generic.List<string>).GetMethod("GetType"), typeof(Type)));
}
[Test]
public void ExactMatchWithGenericType()
{
pointcut.Pattern = "System.Collections.Generic.List<string>.Add";
ExactMatchWithGenericTypeTests(pointcut);
pointcut = (AbstractRegularExpressionMethodPointcut)SerializationTestUtils.SerializeAndDeserialize(pointcut);
ExactMatchWithGenericTypeTests(pointcut);
}
[Test]
public void WildcardWithGenericType()
{
pointcut.Pattern = ".*List<string>.Add";
Assert.IsTrue(pointcut.Matches(typeof(System.Collections.Generic.List<string>).GetMethod("Add"), typeof(int)));
Assert.IsFalse(pointcut.Matches(typeof(System.Collections.Generic.List<string>).GetMethod("GetType"), typeof(Type)));
}
[Test]
public void WildcardForOneClassWithGenericType()
{
pointcut.Pattern = "System.Collections.*";
Assert.IsTrue(pointcut.Matches(typeof(System.Collections.Generic.List<string>).GetMethod("Add"), typeof(int)));
Assert.IsFalse(pointcut.Matches(typeof(System.Collections.Generic.List<string>).GetMethod("GetType"), typeof(Type)));
}
#endif
protected void ExactMatchTests(AbstractRegularExpressionMethodPointcut rpc)
{
// assumes rpc.setPattern("java.lang.Object.hashCode");
Assert.IsTrue(rpc.Matches(typeof(object).GetMethod("GetHashCode"), typeof(int)));
Assert.IsFalse(rpc.Matches(typeof(object).GetMethod("GetType"), typeof(Type)));
}
[Test]
public void Wildcard()
@@ -88,7 +122,7 @@ namespace Spring.Aop.Support
Assert.IsTrue(pointcut.Matches(typeof(object).GetMethod("GetHashCode"), typeof(int)));
Assert.IsFalse(pointcut.Matches(typeof(object).GetMethod("GetType"), typeof(Type)));
}
[Test]
public void WildcardForOneClass()
{
@@ -100,10 +134,11 @@ namespace Spring.Aop.Support
[Test]
public void MatchesObjectClass()
{
pointcut.Pattern = "System.Object.*";
pointcut.Pattern = "Object.*";
Assert.IsTrue(pointcut.Matches(typeof(Exception).GetMethod("GetHashCode"), typeof(TargetException)));
// Doesn't match
Assert.IsFalse(pointcut.Matches(typeof(Exception).GetMethod("ToString"), typeof(Exception)));
}
}
}

View File

@@ -1222,6 +1222,15 @@ namespace Spring.Util
Assert.AreEqual(ex, appEx);
}
#if NET_2_0
[Test]
public void CanGetFriendlyNamesForGenericTypes()
{
Type t = typeof(System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, int>>);
Assert.AreEqual("System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, int>>", ReflectionUtils.GetTypeFriendlyName(t));
}
#endif
#endregion
#region Helper Methods