blob: a2a3f02010c60583db090db9c3cc6b8846bc6a84 [file]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
#if NETSTANDARD
using Microsoft.Extensions.DependencyModel;
#endif
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>
/// Helper class for loading SPI classes from classpath (META-INF files).
/// this is a light impl of <seealso cref="java.util.ServiceLoader"/> but is guaranteed to
/// be bug-free regarding classpath order and does not instantiate or initialize
/// the classes found.
///
/// @lucene.internal
/// </summary>
///
public class SPIClassIterator<S> : IEnumerable<Type>
{
private static HashSet<Type> types;
static SPIClassIterator()
{
types = new HashSet<Type>();
// .NET Port Hack: We do a 2-level deep check here because if the assembly you're
// hoping would be loaded hasn't been loaded yet into the app domain,
// it is unavailable. So we go to the next level on each and check each referenced
// assembly.
#if NETSTANDARD
var dependencyContext = DependencyContext.Default;
var assemblyNames = dependencyContext.RuntimeLibraries
.SelectMany(lib => lib.GetDefaultAssemblyNames(dependencyContext))
.Where(x => !DotNetFrameworkFilter.IsFrameworkAssembly(x))
.Distinct();
var assembliesLoaded = LoadAssemblyFromName(assemblyNames);
#else
var assembliesLoaded = AppDomain.CurrentDomain.GetAssemblies();
#endif
assembliesLoaded = assembliesLoaded.Where(x => !DotNetFrameworkFilter.IsFrameworkAssembly(x)).ToArray();
var referencedAssemblies = assembliesLoaded
.SelectMany(assembly =>
{
return assembly
.GetReferencedAssemblies()
.Where(reference => !DotNetFrameworkFilter.IsFrameworkAssembly(reference))
.Select(assemblyName => LoadAssemblyFromName(assemblyName));
})
.Where(x => x != null)
.Distinct();
var assembliesToExamine = assembliesLoaded.Concat(referencedAssemblies).Distinct().ToList();
// LUCENENET HACK:
// Tests such as TestImpersonation.cs expect that the assemblies
// are probed in a certain order. NamedSPILoader, lines 68 - 75 adds
// the first item it sees with that name. So if you have multiple
// codecs, it may not add the right one, depending on the order of
// the assemblies that were examined.
// This results in many test failures if Types from Lucene.Net.Codecs
// are examined and added to NamedSPILoader first before
// Lucene.Net.TestFramework.
var testFrameworkAssembly = assembliesToExamine.FirstOrDefault(x => string.Equals(x.GetName().Name, "Lucene.Net.TestFramework", StringComparison.Ordinal));
if (testFrameworkAssembly != null)
{
assembliesToExamine.Remove(testFrameworkAssembly);
assembliesToExamine.Insert(0, testFrameworkAssembly);
}
foreach (var assembly in assembliesToExamine)
{
try
{
foreach (var type in assembly.GetTypes().Where(x => x.GetTypeInfo().IsPublic))
{
try
{
if (!IsInvokableSubclassOf<S>(type))
{
continue;
}
// We are looking for types with a default ctor
// (which is used in NamedSPILoader) or has a single parameter
// of type IDictionary<string, string> (for AnalysisSPILoader)
var matchingCtors = type.GetConstructors().Where(ctor =>
{
var parameters = ctor.GetParameters();
switch (parameters.Length)
{
case 0: // default ctor
return false; // LUCENENET NOTE: Now that we have factored Codecs into Abstract Factories, we don't need default constructors here
case 1:
return typeof(IDictionary<string, string>).IsAssignableFrom(parameters[0].ParameterType);
default:
return false;
}
});
if (matchingCtors.Any())
{
types.Add(type);
}
}
catch
{
// swallow
}
}
}
catch
{
// swallow
}
}
}
internal static bool IsInvokableSubclassOf<T>(Type type)
{
return typeof(T).IsAssignableFrom(type) && !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().IsInterface;
}
public static SPIClassIterator<S> Get()
{
return new SPIClassIterator<S>();
}
public IEnumerator<Type> GetEnumerator()
{
return types.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private static IEnumerable<Assembly> LoadAssemblyFromName(IEnumerable<AssemblyName> assemblyNames)
{
return assemblyNames.Select(x => LoadAssemblyFromName(x)).Where(x => x != null);
}
private static Assembly LoadAssemblyFromName(AssemblyName assemblyName)
{
try
{
return Assembly.Load(assemblyName);
}
catch
{
return null;
}
}
/// <summary>
/// Assembly filter logic from:
/// https://raw.githubusercontent.com/Microsoft/dotnet-apiport/master/src/Microsoft.Fx.Portability/Analyzer/DotNetFrameworkFilter.cs
/// </summary>
private static class DotNetFrameworkFilter
{
/// <summary>
/// These keys are a collection of public key tokens derived from all the reference assemblies in
/// "%ProgramFiles%\Reference Assemblies\Microsoft" on a Windows 10 machine with VS 2015 installed
/// </summary>
private static readonly ICollection<string> s_microsoftKeys = new HashSet<string>(new[]
{
"b77a5c561934e089", // ECMA
"b03f5f7f11d50a3a", // DEVDIV
"7cec85d7bea7798e", // SLPLAT
"31bf3856ad364e35", // Windows
"24eec0d8c86cda1e", // Phone
"0738eb9f132ed756", // Mono
"ddd0da4d3e678217", // Component model
"84e04ff9cfb79065", // Mono Android
"842cf8be1de50553" // Xamarin.iOS
}, StringComparer.OrdinalIgnoreCase);
private static readonly IEnumerable<string> s_frameworkAssemblyNamePrefixes = new[]
{
"System.",
"Microsoft.",
"Mono."
};
/// <summary>
/// Gets a best guess as to whether this assembly is a .NET Framework assembly or not.
/// </summary>
public static bool IsFrameworkAssembly(Assembly assembly)
{
return assembly != null && IsFrameworkAssembly(assembly.GetName());
}
/// <summary>
/// Gets a best guess as to whether this assembly is a .NET Framework assembly or not.
/// </summary>
public static bool IsFrameworkAssembly(AssemblyName assembly)
{
if (assembly == null)
{
return false;
}
if (s_frameworkAssemblyNamePrefixes.Any(p => assembly.Name.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
var publicKey = assembly.GetPublicKeyToken();
if (publicKey == default(byte[]))
{
return false;
}
var publicKeyToken = string.Concat(publicKey.Select(i => i.ToString("x2")));
return s_microsoftKeys.Contains(publicKeyToken);
}
}
}
/* Being Re-written
public sealed class SPIClassIterator<S> : IEnumerator<Type>
{
private const string META_INF_SERVICES = "META-INF/services/";
private readonly Type Clazz;
private readonly ClassLoader Loader;
private readonly IEnumerator<URL> ProfilesEnum;
private IEnumerator<string> LinesIterator;
public static SPIClassIterator<S> Get<S>(Type clazz)
{
return new SPIClassIterator<S>(clazz, Thread.CurrentThread.ContextClassLoader);
}
public static SPIClassIterator<S> Get<S>(Type clazz, ClassLoader loader)
{
return new SPIClassIterator<S>(clazz, loader);
}
/// <summary>
/// Utility method to check if some class loader is a (grand-)parent of or the same as another one.
/// this means the child will be able to load all classes from the parent, too.
/// </summary>
public static bool IsParentClassLoader(ClassLoader parent, ClassLoader child)
{
while (child != null)
{
if (child == parent)
{
return true;
}
child = child.Parent;
}
return false;
}
private SPIClassIterator(Type clazz, ClassLoader loader)
{
this.Clazz = clazz;
try
{
string fullName = META_INF_SERVICES + clazz.Name;
this.ProfilesEnum = (loader == null) ? ClassLoader.getSystemResources(fullName) : loader.getResources(fullName);
}
catch (System.IO.IOException ioe)
{
throw new ServiceConfigurationError("Error loading SPI profiles for type " + clazz.Name + " from classpath", ioe);
}
this.Loader = (loader == null) ? ClassLoader.SystemClassLoader : loader;
this.LinesIterator = Collections.emptySet<string>().GetEnumerator();
}
private bool LoadNextProfile()
{
List<string> lines = null;
while (ProfilesEnum.MoveNext())
{
if (lines != null)
{
lines.Clear();
}
else
{
lines = new List<string>();
}
URL url = ProfilesEnum.Current;
try
{
InputStream @in = url.openStream();
System.IO.IOException priorE = null;
try
{
BufferedReader reader = new BufferedReader(new InputStreamReader(@in, IOUtils.CHARSET_UTF_8));
string line;
while ((line = reader.readLine()) != null)
{
int pos = line.IndexOf('#');
if (pos >= 0)
{
line = line.Substring(0, pos);
}
line = line.Trim();
if (line.Length > 0)
{
lines.Add(line);
}
}
}
catch (System.IO.IOException ioe)
{
priorE = ioe;
}
finally
{
IOUtils.CloseWhileHandlingException(priorE, @in);
}
}
catch (System.IO.IOException ioe)
{
throw new ServiceConfigurationError("Error loading SPI class list from URL: " + url, ioe);
}
if (lines.Count > 0)
{
this.LinesIterator = lines.GetEnumerator();
return true;
}
}
return false;
}
public override bool HasNext()
{
}
public override Type Next()
{
// hasNext() implicitely loads the next profile, so it is essential to call this here!
if (!HasNext())
{
throw new NoSuchElementException();
}
string c = LinesIterator.next();
try
{
// don't initialize the class (pass false as 2nd parameter):
return Type.GetType(c, false, Loader).asSubclass(Clazz);
}
catch (ClassNotFoundException cnfe)
{
throw new ServiceConfigurationError(string.format(Locale.ROOT, "A SPI class of type %s with classname %s does not exist, " + "please fix the file '%s%1$s' in your classpath.", Clazz.Name, c, META_INF_SERVICES));
}
}
public override void Remove()
{
throw new System.NotSupportedException();
}
}*/
}