blob: f46286e513586a7b438cb791879094bf32fe2b8a [file] [log] [blame]
using Lucene.Net.Support.Threading;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Lucene.Net.Codecs
{
/*
* 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>
/// Implements the default functionality of <see cref="ICodecFactory"/>.
/// <para/>
/// To replace the <see cref="DefaultCodecFactory"/> instance, call
/// <see cref="Codec.SetCodecFactory(ICodecFactory)"/> at application start up.
/// <see cref="DefaultCodecFactory"/> can be subclassed or passed additional parameters to register
/// additional codecs, inject dependencies, or change caching behavior, as shown in the following examples.
/// Alternatively, <see cref="ICodecFactory"/> can be implemented to provide complete control over
/// codec creation and lifetimes.
/// <para/>
/// <h4>Register Additional Codecs</h4>
/// <para/>
/// Additional codecs can be added by initializing the instance of <see cref="DefaultCodecFactory"/> and
/// passing an array of <see cref="Codec"/>-derived types.
/// <code>
/// // Register the factory at application start up.
/// Codec.SetCodecFactory(new DefaultCodecFactory {
/// CustomCodecTypes = new Type[] { typeof(MyCodec), typeof(AnotherCodec) }
/// });
/// </code>
/// <para/>
/// <h4>Only Use Explicitly Defined Codecs</h4>
/// <para/>
/// <see cref="PutCodecType(Type)"/> can be used to explicitly add codec types. In this example,
/// the call to <c>base.Initialize()</c> is excluded to skip the built-in codec registration.
/// Since <c>AnotherCodec</c> doesn't have a default constructor, the <see cref="NewCodec(Type)"/>
/// method is overridden to supply the required parameters.
/// <code>
/// public class ExplicitCodecFactory : DefaultCodecFactory
/// {
/// protected override void Initialize()
/// {
/// // Load specific codecs in a specific order.
/// PutCodecType(typeof(MyCodec));
/// PutCodecType(typeof(AnotherCodec));
/// }
///
/// protected override Codec NewCodec(Type type)
/// {
/// // Special case: AnotherCodec has a required dependency
/// if (typeof(AnotherCodec).Equals(type))
/// return new AnotherCodec(new SomeDependency());
///
/// return base.NewCodec(type);
/// }
/// }
///
/// // Register the factory at application start up.
/// Codec.SetCodecFactory(new ExplicitCodecFactory());
/// </code>
/// See the <see cref="Lucene.Net.Codecs"/> namespace documentation for more examples of how to
/// inject dependencies into <see cref="Codec"/> subclasses.
/// <para/>
/// <h4>Use Reflection to Scan an Assembly for Codecs</h4>
/// <para/>
/// <see cref="ScanForCodecs(Assembly)"/> or <see cref="ScanForCodecs(IEnumerable{Assembly})"/> can be used
/// to scan assemblies using .NET Reflection for codec types and add all subclasses that are found automatically.
/// This example calls <c>base.Initialize()</c> to load the default codecs prior to scanning for additional codecs.
/// <code>
/// public class ScanningCodecFactory : DefaultCodecFactory
/// {
/// protected override void Initialize()
/// {
/// // Load all default codecs
/// base.Initialize();
///
/// // Load all of the codecs inside of the same assembly that MyCodec is defined in
/// ScanForCodecs(typeof(MyCodec).Assembly);
/// }
/// }
///
/// // Register the factory at application start up.
/// Codec.SetCodecFactory(new ScanningCodecFactory());
/// </code>
/// Codecs in the target assemblie(s) can be excluded from the scan by decorating them with
/// the <see cref="ExcludeCodecFromScanAttribute"/>.
/// </summary>
/// <seealso cref="ICodecFactory"/>
/// <seealso cref="IServiceListable"/>
/// <seealso cref="ExcludeCodecFromScanAttribute"/>
// LUCENENET specific
public class DefaultCodecFactory : NamedServiceFactory<Codec>, ICodecFactory, IServiceListable
{
private static readonly Type[] localCodecTypes = new Type[] {
typeof(Lucene46.Lucene46Codec),
#pragma warning disable 612, 618
typeof(Lucene3x.Lucene3xCodec), // Optimize 3.x codec over < 4.6 codecs
typeof(Lucene45.Lucene45Codec),
typeof(Lucene42.Lucene42Codec),
typeof(Lucene41.Lucene41Codec),
typeof(Lucene40.Lucene40Codec),
#pragma warning restore 612, 618
};
// NOTE: The following 2 dictionaries are static, since this instance is stored in a static
// variable in the Codec class.
private readonly IDictionary<string, Type> codecNameToTypeMap;
private readonly IDictionary<Type, Codec> codecInstanceCache;
/// <summary>
/// Creates a new instance of <see cref="DefaultCodecFactory"/>.
/// </summary>
public DefaultCodecFactory()
{
codecNameToTypeMap = new Dictionary<string, Type>();
codecInstanceCache = new Dictionary<Type, Codec>();
}
/// <summary>
/// An array of custom <see cref="Codec"/>-derived types to be registered. This property
/// can be initialized during construction of <see cref="DefaultCodecFactory"/>
/// to make your custom codecs known to Lucene.
/// <para/>
/// These types will be registered after the default Lucene types, so if a custom type has the same
/// name as a Lucene <see cref="Codec"/> (via <see cref="CodecNameAttribute"/>)
/// the custom type will replace the Lucene type with the same name.
/// </summary>
public IEnumerable<Type> CustomCodecTypes { get; set; }
/// <summary>
/// Initializes the codec type cache with the known <see cref="Codec"/> types.
/// Override this method (and optionally call <c>base.Initialize()</c>) to add your
/// own <see cref="Codec"/> types by calling <see cref="PutCodecType(Type)"/>
/// or <see cref="ScanForCodecs(Assembly)"/>.
/// <para/>
/// If two types have the same name by using the <see cref="CodecNameAttribute"/>, the
/// last one registered wins.
/// </summary>
protected override void Initialize()
{
foreach (var codecType in localCodecTypes)
PutCodecTypeImpl(codecType);
ScanForCodecs(this.CodecsAssembly);
if (CustomCodecTypes != null)
{
foreach (var codecType in CustomCodecTypes)
PutCodecType(codecType);
}
}
/// <summary>
/// Scans the given <paramref name="assemblies"/> for subclasses of <see cref="Codec"/>
/// and adds their names to the <see cref="codecNameToTypeMap"/>. Note that names will be
/// automatically overridden if the <see cref="Codec"/> name appears multiple times - the last match wins.
/// </summary>
/// <param name="assemblies">A list of assemblies to scan. The assemblies will be scanned from first to last,
/// and the last match for each <see cref="Codec"/> name wins.</param>
protected virtual void ScanForCodecs(IEnumerable<Assembly> assemblies)
{
foreach (var assembly in assemblies)
{
ScanForCodecs(assembly);
}
}
/// <summary>
/// Scans the given <paramref name="assembly"/> for subclasses of <see cref="Codec"/>
/// and adds their names to the <see cref="codecNameToTypeMap"/>. Note that names will be
/// automatically overridden if the <see cref="Codec"/> name appears multiple times - the last match wins.
/// </summary>
/// <param name="assembly">The assembly to scan.</param>
protected virtual void ScanForCodecs(Assembly assembly)
{
if (assembly is null) return;
foreach (var c in assembly.GetTypes())
{
if (IsServiceType(c))
{
PutCodecTypeImpl(c);
}
}
}
/// <summary>
/// Adds a <see cref="Codec"/> type to the <see cref="codecNameToTypeMap"/>, using
/// the name provided in the <see cref="CodecNameAttribute"/>, if present, or the name
/// of the codec class minus the "Codec" suffix as the name by default.
/// <para/>
/// Note that if a <see cref="Codec"/> with the same name already exists in the map,
/// calling this method will update it to the new type.
/// </summary>
/// <param name="codec">A type that subclasses <see cref="Codec"/>.</param>
protected virtual void PutCodecType(Type codec)
{
if (codec is null)
throw new ArgumentNullException(nameof(codec));
if (!typeof(Codec).IsAssignableFrom(codec))
throw new ArgumentException($"The supplied type {codec.AssemblyQualifiedName} does not subclass {nameof(Codec)}.");
PutCodecTypeImpl(codec);
}
private void PutCodecTypeImpl(Type codec)
{
string name = GetServiceName(codec);
UninterruptableMonitor.Enter(m_initializationLock);
try
{
codecNameToTypeMap[name] = codec;
}
finally
{
UninterruptableMonitor.Exit(m_initializationLock);
}
}
/// <summary>
/// Gets the <see cref="Codec"/> instance from the provided <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the <see cref="Codec"/> instance to retrieve.</param>
/// <returns>The <see cref="Codec"/> instance.</returns>
public virtual Codec GetCodec(string name)
{
EnsureInitialized(); // Safety in case a subclass doesn't call it
UninterruptableMonitor.Enter(m_initializationLock);
try
{
Type codecType = GetCodecType(name);
return GetCodec(codecType);
}
finally
{
UninterruptableMonitor.Exit(m_initializationLock);
}
}
/// <summary>
/// Gets the <see cref="Codec"/> instance from the provided <paramref name="type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> of <see cref="Codec"/> to retrieve.</param>
/// <returns>The <see cref="Codec"/> instance.</returns>
protected virtual Codec GetCodec(Type type)
{
if (type is null)
throw new ArgumentNullException(nameof(type));
if (!codecInstanceCache.TryGetValue(type, out Codec instance))
{
UninterruptableMonitor.Enter(m_initializationLock);
try
{
if (!codecInstanceCache.TryGetValue(type, out instance))
{
instance = NewCodec(type);
codecInstanceCache[type] = instance;
}
}
finally
{
UninterruptableMonitor.Exit(m_initializationLock);
}
}
return instance;
}
/// <summary>
/// Instantiates a <see cref="Codec"/> based on the provided <paramref name="type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> of <see cref="Codec"/> to instantiate.</param>
/// <returns>The new instance.</returns>
protected virtual Codec NewCodec(Type type)
{
return (Codec)Activator.CreateInstance(type, IsFullyTrusted);
}
/// <summary>
/// Gets the <see cref="Codec"/> <see cref="Type"/> from the provided <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the <see cref="Codec"/> <see cref="Type"/> to retrieve.</param>
/// <returns>The <see cref="Codec"/> <see cref="Type"/>.</returns>
protected virtual Type GetCodecType(string name)
{
if (name is null)
throw new ArgumentNullException(nameof(name));
EnsureInitialized();
if (!codecNameToTypeMap.TryGetValue(name, out Type codecType) || codecType is null)
{
throw new ArgumentException($"Codec '{name}' cannot be loaded. If the codec is not " +
$"in a Lucene.Net assembly, you must subclass {typeof(DefaultCodecFactory).FullName}, " +
"override the Initialize() method, and call PutCodecType() or ScanForCodecs() to add " +
$"the type manually. Call {typeof(Codec).FullName}.SetCodecFactory() at application " +
"startup to initialize your custom codec.");
}
return codecType;
}
/// <summary>
/// Gets a list of the available <see cref="Codec"/>s (by name).
/// </summary>
/// <returns>A <see cref="T:ICollection{string}"/> of <see cref="Codec"/> names.</returns>
public virtual ICollection<string> AvailableServices
{
get
{
EnsureInitialized();
return codecNameToTypeMap.Keys;
}
}
}
}