| 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(); |
| } |
| }*/ |
| } |