blob: 975c250f952bab6fd4ce933aff18059e87c7fecc [file] [log] [blame]
using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.Globalization;
#if NETSTANDARD1_6
using System.Linq;
#endif
using System.Reflection;
using System.Runtime.CompilerServices;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Util
{
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/// <summary>
/// A utility for keeping backwards compatibility on previously abstract methods
/// (or similar replacements).
/// <para>Before the replacement method can be made abstract, the old method must kept deprecated.
/// If somebody still overrides the deprecated method in a non-sealed class,
/// you must keep track, of this and maybe delegate to the old method in the subclass.
/// The cost of reflection is minimized by the following usage of this class:</para>
/// <para>Define <strong>static readonly</strong> fields in the base class (<c>BaseClass</c>),
/// where the old and new method are declared:</para>
/// <code>
/// internal static readonly VirtualMethod newMethod =
/// new VirtualMethod(typeof(BaseClass), "newName", parameters...);
/// internal static readonly VirtualMethod oldMethod =
/// new VirtualMethod(typeof(BaseClass), "oldName", parameters...);
/// </code>
/// <para>this enforces the singleton status of these objects, as the maintenance of the cache would be too costly else.
/// If you try to create a second instance of for the same method/<c>baseClass</c> combination, an exception is thrown.</para>
/// <para>To detect if e.g. the old method was overridden by a more far subclass on the inheritance path to the current
/// instance's class, use a <strong>non-static</strong> field:</para>
/// <code>
/// bool isDeprecatedMethodOverridden =
/// oldMethod.GetImplementationDistance(this.GetType()) > newMethod.GetImplementationDistance(this.GetType());
///
/// <em>// alternatively (more readable):</em>
/// bool isDeprecatedMethodOverridden =
/// VirtualMethod.CompareImplementationDistance(this.GetType(), oldMethod, newMethod) > 0
/// </code>
/// <para><seealso cref="GetImplementationDistance"/> returns the distance of the subclass that overrides this method.
/// The one with the larger distance should be used preferable.
/// this way also more complicated method rename scenarios can be handled
/// (think of 2.9 <see cref="Analysis.TokenStream"/> deprecations).</para>
///
/// @lucene.internal
/// </summary>
// LUCENENET NOTE: Pointless to make this class generic, since the generic type is never used (the Type class in .NET
// is not generic).
public sealed class VirtualMethod
{
private static readonly ISet<MethodInfo> singletonSet = new ConcurrentHashSet<MethodInfo>();
private readonly Type baseClass;
private readonly string method;
private readonly Type[] parameters;
// LUCENENET: Replaced IdentityHashMap with ConditionalWeakTable. A Type IS an identity, so there is
// no need for the extra IdentityWeakReference.
private readonly ConditionalWeakTable<Type, Int32Ref> cache = new ConditionalWeakTable<Type, Int32Ref>();
// LUCENENET specific wrapper needed because ConditionalWeakTable requires a reference type.
private class Int32Ref : IEquatable<Int32Ref>
{
private int value;
public Int32Ref(int value)
{
this.value = value;
}
public bool Equals(Int32Ref other)
{
if (other == null)
return false;
return value.Equals(other);
}
public override bool Equals(object obj)
{
if (obj is Int32Ref other)
return Equals(other);
if (obj is int otherInt)
return value.Equals(otherInt);
return false;
}
public override int GetHashCode()
{
return value.GetHashCode();
}
public static implicit operator int(Int32Ref value)
{
return value.value;
}
public static implicit operator Int32Ref(int value)
{
return new Int32Ref(value);
}
}
/// <summary>
/// Creates a new instance for the given <paramref name="baseClass"/> and method declaration. </summary>
/// <exception cref="InvalidOperationException"> if you create a second instance of the same
/// <paramref name="baseClass"/> and method declaration combination. This enforces the singleton status. </exception>
/// <exception cref="ArgumentException"> If <paramref name="baseClass"/> does not declare the given method. </exception>
public VirtualMethod(Type baseClass, string method, params Type[] parameters)
{
this.baseClass = baseClass;
this.method = method;
this.parameters = parameters;
try
{
MethodInfo mi = GetMethod(baseClass, method, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, parameters);
if (mi == null)
{
throw new System.ArgumentException(baseClass.Name + " has no such method.");
}
else if (!singletonSet.Add(mi))
{
throw new System.NotSupportedException("VirtualMethod instances must be singletons and therefore " + "assigned to static final members in the same class, they use as baseClass ctor param.");
}
}
catch (NotSupportedException nsme)
{
throw new System.ArgumentException(baseClass.Name + " has no such method: " + nsme.Message);
}
}
/// <summary>
/// Returns the distance from the <c>baseClass</c> in which this method is overridden/implemented
/// in the inheritance path between <c>baseClass</c> and the given subclass <paramref name="subclazz"/>. </summary>
/// <returns> 0 if and only if not overridden, else the distance to the base class. </returns>
public int GetImplementationDistance(Type subclazz)
{
// LUCENENET: Replaced WeakIdentityMap with ConditionalWeakTable - This operation is simplified over Lucene.
return cache.GetValue(subclazz, (key) => Convert.ToInt32(ReflectImplementationDistance(key), CultureInfo.InvariantCulture));
}
/// <summary>
/// Returns, if this method is overridden/implemented in the inheritance path between
/// <c>baseClass</c> and the given subclass <paramref name="subclazz"/>.
/// <para/>You can use this method to detect if a method that should normally be final was overridden
/// by the given instance's class. </summary>
/// <returns> <c>false</c> if and only if not overridden. </returns>
public bool IsOverriddenAsOf(Type subclazz)
{
return GetImplementationDistance(subclazz) > 0;
}
private int ReflectImplementationDistance(Type subclazz)
{
if (!baseClass.GetTypeInfo().IsAssignableFrom(subclazz))
{
throw new System.ArgumentException(subclazz.Name + " is not a subclass of " + baseClass.Name);
}
bool overridden = false;
int distance = 0;
for (Type clazz = subclazz; clazz != baseClass && clazz != null; clazz = clazz.GetTypeInfo().BaseType)
{
// lookup method, if success mark as overridden
if (!overridden)
{
MethodInfo mi = GetMethod(clazz, method,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly,
parameters);
if (mi != null)
overridden = true;
}
// increment distance if overridden
if (overridden)
{
distance++;
}
}
return distance;
}
/// <summary>
/// Utility method that compares the implementation/override distance of two methods. </summary>
/// <returns>
/// <list type="bullet">
/// <item><description>&gt; 1, iff <paramref name="m1"/> is overridden/implemented in a subclass of the class overriding/declaring <paramref name="m2"/></description></item>
/// <item><description>&lt; 1, iff <paramref name="m2"/> is overridden in a subclass of the class overriding/declaring <paramref name="m1"/></description></item>
/// <item><description>0, iff both methods are overridden in the same class (or are not overridden at all)</description></item>
/// </list>
/// </returns>
public static int CompareImplementationDistance(Type clazz, VirtualMethod m1, VirtualMethod m2)
{
return m1.GetImplementationDistance(clazz).CompareTo(m2.GetImplementationDistance(clazz));
}
private MethodInfo GetMethod(Type clazz, string methodName, BindingFlags bindingFlags, Type[] methodParameters)
{
#if NETSTANDARD1_6
var methods = clazz.GetTypeInfo().GetMethods(bindingFlags).Where(x => {
return x.Name.Equals(methodName, StringComparison.Ordinal)
&& x.GetParameters().Select(y => y.ParameterType).SequenceEqual(methodParameters);
}).ToArray();
if (methods.Length == 0)
{
return default(MethodInfo);
}
else if (methods.Length == 1)
{
return methods[0];
}
else
{
var formatted = string.Format("Found more than one match for type {0}, methodName {1}, bindingFlags {2}, parameters {3}", clazz, methodName, bindingFlags, methodParameters);
throw new AmbiguousMatchException(formatted);
}
#else
return clazz.GetMethod(methodName, bindingFlags, null, methodParameters, null);
#endif
}
}
}