blob: d363c5c263d07de0dc038ed45288cc6282988cb6 [file] [log] [blame]
using J2N.Collections.Generic.Extensions;
using Lucene.Net.Support;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Analysis.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 named SPIs from classpath (e.g. Tokenizers, TokenStreams).
/// @lucene.internal
/// </summary>
internal sealed class AnalysisSPILoader<S> where S : AbstractAnalysisFactory
{
private volatile IDictionary<string, Type> services = Collections.EmptyMap<string, Type>();
private readonly Type clazz = typeof(S);
private readonly string[] suffixes;
public AnalysisSPILoader()
: this(new string[] { typeof(S).Name })
{
}
public AnalysisSPILoader(string[] suffixes)
{
this.suffixes = suffixes;
Reload();
}
/// <summary>
/// Reloads the internal SPI list.
/// Changes to the service list are visible after the method ends, all
/// iterators (e.g, from <see cref="AvailableServices"/>,...) stay consistent.
///
/// <para/><b>NOTE:</b> Only new service providers are added, existing ones are
/// never removed or replaced.
///
/// <para/><em>this method is expensive and should only be called for discovery
/// of new service providers on the given classpath/classloader!</em>
/// </summary>
public void Reload()
{
lock (this)
{
IDictionary<string, Type> services = new JCG.LinkedDictionary<string, Type>(this.services);
SPIClassIterator<S> loader = SPIClassIterator<S>.Get();
foreach (var service in loader)
{
string clazzName = service.Name;
string name = null;
foreach (string suffix in suffixes)
{
if (clazzName.EndsWith(suffix, StringComparison.Ordinal))
{
name = clazzName.Substring(0, clazzName.Length - suffix.Length).ToLowerInvariant();
break;
}
}
if (name == null)
{
throw new InvalidOperationException("The class name " + service.Name +
" has wrong suffix, allowed are: " + Arrays.ToString(suffixes));
}
// only add the first one for each name, later services will be ignored
// this allows to place services before others in classpath to make
// them used instead of others
//
// LUCENETODO: Should we disallow duplicate names here?
// Allowing it may get confusing on collisions, as different packages
// could contain same factory class, which is a naming bug!
// When changing this be careful to allow reload()!
if (!services.ContainsKey(name))
{
services.Add(name, service);
}
}
this.services = services.AsReadOnly();
}
}
public S NewInstance(string name, IDictionary<string, string> args)
{
Type service = LookupClass(name);
try
{
return (S)Activator.CreateInstance(service, new object[] { args });
}
catch (Exception e)
{
throw new ArgumentException("SPI class of type " + clazz.Name + " with name '" + name + "' cannot be instantiated. " +
"This is likely due to a missing reference of the .NET Assembly containing the class '" + service.Name + "' in your project or AppDomain: ", e);
}
}
public Type LookupClass(string name)
{
if (this.services.TryGetValue(name.ToLowerInvariant(), out Type service))
{
return service;
}
else
{
throw new ArgumentException("A SPI class of type " + clazz.Name + " with name '" + name + "' does not exist. " +
"You need to add the corresponding reference supporting this SPI to your project or AppDomain. " +
"The current classpath supports the following names: " + string.Format(J2N.Text.StringFormatter.InvariantCulture, "{0}", AvailableServices));
}
}
public ICollection<string> AvailableServices => services.Keys;
}
}