// -----------------------------------------------------------------------
//
//
// 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.
//
//
// -----------------------------------------------------------------------
namespace Lucene.Net.Util
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.TokenAttributes;
using Support;
///
/// An AttributeSource contains a list of different s,
/// and methods to add and get them. There can only be a single instance
/// of an attribute in the same AttributeSource instance. This is ensured
/// by passing in the actual type of the Attribute to
/// the , which then checks if an instance of
/// that type is already present. If yes, it returns the instance, otherwise
/// it creates a new instance and returns it.
///
///
///
///
/// Java File:
/// lucene/src/java/org/apache/lucene/util/AttributeSource.java
///
///
///
/// C# File:
/// src/Lucene.Net/Util/AttributeSource.cs
///
///
///
/// C# Tests:
/// test/Lucene.Net.Test/Util/AttributeSourceTest.cs
///
///
///
///
public partial class AttributeSource
{
private static readonly object instanceLock = new object();
private static readonly WeakDictionary>> knownAttributeClasses =
new WeakDictionary>>();
private readonly Dictionary interfaceMap = new Dictionary();
private readonly Dictionary attributeMap = new Dictionary();
private AttributeSourceState[] currentState;
///
/// Initializes a new instance of the class.
/// This constructor uses the
///
public AttributeSource()
: this(AttributeFactory.DefaultFactory)
{
}
///
/// Initializes a new instance of the class.
/// This uses another attribute source to create a new one based on the internally
/// store maps of to instances of
///
/// The source.
public AttributeSource(AttributeSource source)
{
this.attributeMap = source.attributeMap;
this.interfaceMap = source.interfaceMap;
this.currentState = source.currentState;
this.Factory = source.Factory;
}
///
/// Initializes a new instance of the class. Creates
/// a new attribute source with the specified factory.
///
/// The factory.
public AttributeSource(AttributeFactory factory)
{
this.attributeMap = new Dictionary();
this.interfaceMap = new Dictionary();
this.currentState = new AttributeSourceState[1];
this.Factory = factory;
}
///
/// Gets the attribute factory.
///
/// The factory.
public AttributeFactory Factory { get; private set; }
///
/// Gets a value indicating whether this instance has attributes.
///
///
/// true if this instance has attributes; otherwise, false.
///
public bool HasAttributes
{
get { return this.attributeMap.Count > 0; }
}
///
/// Gets the attribute interfaces.
///
/// The type.
///
/// an instance of of of
/// that hold the known interfaces that inherit from .
///
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypeMemberSignatures",
Justification = "I considered WeakReference is needed.")]
public static LinkedList> GetAttributeInterfaces(Type type)
{
lock (instanceLock)
{
LinkedList> foundInterfaces = knownAttributeClasses.GetDefaultedValue(type);
if (foundInterfaces == null)
{
knownAttributeClasses[type] = foundInterfaces = new LinkedList>();
Type attributeInterface = typeof(IAttribute);
Type activeType = type;
do
{
foreach (Type currentInterface in activeType.GetInterfaces())
{
if (currentInterface != attributeInterface &&
attributeInterface.IsAssignableFrom(currentInterface))
{
foundInterfaces.AddLast(new WeakReference(currentInterface));
}
}
activeType = activeType.BaseType;
} while (activeType != null);
}
return foundInterfaces;
}
}
///
/// Adds the attribute.
///
/// The attribute.
public void AddAttribute(AttributeBase attribute)
{
if (attribute == null)
throw new ArgumentNullException("attribute");
Type type = attribute.GetType();
if (this.attributeMap.ContainsKey(type))
return;
var foundInterfaces = GetAttributeInterfaces(type);
foreach (var interfaceTypeRef in foundInterfaces)
{
var interfaceType = interfaceTypeRef.Target;
#if DEBUG
System.Diagnostics.Debug.Assert(interfaceType != null, "We have a strong reference on the class holding the interfaces, so they should never get evicted");
#endif
if (!this.interfaceMap.ContainsKey(interfaceType))
{
this.currentState[0] = null;
this.attributeMap.Add(type, attribute);
this.interfaceMap.Add(interfaceType, attribute);
}
}
}
///
/// Adds an attribute.
///
/// The type of attribute that is to be added.
///
/// Thrown when is null.
///
///
/// Thrown when is not a type of interface
/// that inherits from .
///
///
/// The instance of that was created.
///
public AttributeBase AddAttribute(Type attributeType)
{
if (attributeType == null)
throw new ArgumentNullException("attributeType");
AttributeBase instance;
if (this.attributeMap.TryGetValue(attributeType, out instance))
{
if (!attributeType.IsInterface)
throw new ArgumentException("The type must be an interface.");
if (!attributeType.IsSubclassOf(typeof(IAttribute)))
throw new ArgumentException(
"The interface type '{0}' is not a subclass of IAttribute."
.Inject(attributeType.FullName));
this.AddAttribute(instance = this.Factory.CreateAttributeInstance(attributeType));
}
return instance;
}
///
/// Adds the attribute.
///
/// The type of AttributeBase that is to be added.
/// An instance of .
public T AddAttribute() where T : AttributeBase, IAttribute
{
return (T)this.AddAttribute(typeof(T));
}
///
/// Captures the state.
///
/// An instance of .
public AttributeSourceState CaptureState()
{
AttributeSourceState state = this.GetCurrentState();
return state == null ? null : state.Clone();
}
///
/// Clears the attributes.
///
public void ClearAttributes()
{
for (var state = this.GetCurrentState(); state != null; state = state.Next)
state.Attribute.Clear();
}
///
/// Clones the current and injects clones of all of
/// the stored instances into the clone.
///
///
///
/// This method can be use to create another
/// with exactly the same attributes. You can also use this method as a non-performant
/// replacement for for times you wish to modify or peek at
/// the captured state.
///
///
///
/// A new instance of with cloned attributes.
///
public AttributeSource CloneAttributes()
{
var clone = new AttributeSource(this.Factory);
if (this.HasAttributes)
{
this.ForEachState((state) => {
clone.attributeMap.Add(state.Attribute.GetType(), state.Attribute.Clone());
});
foreach (var pair in this.interfaceMap)
clone.interfaceMap.Add(pair.Key, pair.Value.Clone());
}
return clone;
}
///
/// Determines whether the specified type contains attribute.
///
/// The type.
///
/// true if the specified type contains attribute; otherwise, false.
///
public bool ContainsAttribute(Type type)
{
if (type.IsInterface)
return this.interfaceMap.ContainsKey(type);
else
return this.attributeMap.ContainsKey(type);
}
///
/// Copies the contents of this to the specified
/// .
///
///
///
/// The has to provide all s this instance contains.
/// The actual attribute implementations must be identical in both instances;
/// ideally both instances should use the same .
/// You can use this method as a replacement for , if you use
/// instead of .
///
///
/// The source.
///
/// Thrown when the contains an instance
/// of that current
/// object does not contain.
///
public void CopyTo(AttributeSource source)
{
this.ForEachState((state) => {
var attribute = source.attributeMap[state.Attribute.GetType()];
if (attribute == null)
throw new ArgumentException(this.CreateStateExceptionMessage(state), "state");
state.Attribute.CopyTo(attribute);
});
}
///
/// Determines whether the specified is equal to this instance.
///
/// The to compare with this instance.
///
/// true if the specified is equal to this instance; otherwise, false.
///
public override bool Equals(object obj)
{
if (obj == this)
return true;
AttributeSource compare = (AttributeSource)obj;
if (compare == null)
return false;
if (!this.HasAttributes)
return !compare.HasAttributes;
if (!compare.HasAttributes ||
(compare.attributeMap.Count != this.attributeMap.Count))
return false;
var localState = this.GetCurrentState();
var compareState = compare.GetCurrentState();
while (localState != null && compareState != null)
{
// this differs from java-lucene-core:
// .NET's attributes will return false if the types mismatch.
if (!localState.Attribute.Equals(compareState.Attribute))
return false;
localState = localState.Next;
compareState = compareState.Next;
}
return true;
}
///
/// Finds the specified attribute based on the which must
/// a type that implements and .
///
/// The type that
/// An instance of .
///
/// Thrown when the type of could not be found in this instance.
///
public T FindAttribute() where T : IAttribute
{
AttributeBase value;
if (!this.interfaceMap.TryGetValue(typeof(T), out value))
throw new ArgumentException(
"The specified type '{0}' could not be found, try using " +
"ContainsAttribute(Type) first."
.Inject(typeof(T).FullName));
object unbox = value;
return (T)unbox;
}
///
/// Finds the specified attribute based on the which must
/// be a that implements
/// and .
///
/// The of attribute to find.
///
/// An instance of .
///
//// getAttribute(Class attClass)
public AttributeBase FindAttribute(Type type)
{
AttributeBase value;
if (type.IsInterface)
{
if (!this.interfaceMap.TryGetValue(type, out value))
throw new ArgumentException(
"The specified type '{0}' could not be found, try using " +
"ContainsAttribute(Type) first.".Inject(type.FullName));
}
else
{
if (!this.attributeMap.TryGetValue(type, out value))
throw new ArgumentException(
"The specified type '{0}' could not be found, try using " +
"ContainsAttribute(Type) first.".Inject(type.FullName));
}
return value;
}
///
/// Returns the enumerator for stored interface types in the same order
/// they were stored int.
///
///
/// An instance of .
///
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "Due to microsoft's bad design, developers expect the Get[Type]Enumerator name convention. ")]
public IEnumerator GetAttributeTypesEnumerator()
{
return this.interfaceMap.Keys.GetEnumerator();
}
///
/// Creates and returns an that will enumerate
/// throw all available instances of . The enumerator
/// may contain less instances than .
///
///
/// An instance of .
///
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate",
Justification = "Due to microsoft's bad design, developers expect the Get[Type]Enumerator name convention. ")]
public IEnumerator GetAttributeEnumerator()
{
return new AttributeEnumerator(this);
}
///
/// Returns a hash code for this instance.
///
///
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
///
public override int GetHashCode()
{
int code = 0;
for (var state = this.GetCurrentState(); state != null; state = state.Next)
code = (code * 31) + state.Attribute.GetHashCode();
return code;
}
///
/// Restores the attribute source state by copying the values of all
/// attribute instances that the state contains into the target stream.
///
///
///
/// The target stream must contain a corresponding instance for
/// each argument contained in this state. It is not possible
/// to restore the state of an
/// containing a TermAttribute into an
/// using a instance.
///
///
/// This method does not affect attributes of the target stream
/// that are not contained in this state. If the target stream
/// contains an , but this
/// does not, then the value of the
/// remains unchanged.
///
///
/// It might be desirable to reset its value to the default.
/// The caller should first call on
/// the target stream in order to reset its value.
///
///
/// The state.
///
/// Thrown when a state contains ain attribute that is not found
/// within the current instance of
///
public void RestoreState(AttributeSourceState state)
{
if (state == null)
return;
do
{
var attribute = this.attributeMap[state.Attribute.GetType()];
if (attribute == null)
throw new ArgumentException(this.CreateStateExceptionMessage(state));
state.Attribute.CopyTo(attribute);
state = state.Next;
}
while (state != null);
}
///
/// Enumerates over the stored states in the
///
/// The action to invoke on each state.
protected void ForEachState(Action invoke)
{
for (var state = this.GetCurrentState(); state != null; state = state.Next)
invoke(state);
}
private AttributeSourceState GetCurrentState()
{
AttributeSourceState state = this.currentState[0];
if (state != null || !this.HasAttributes)
return state;
AttributeSourceState current = state = this.currentState[0] = new AttributeSourceState();
var enumerator = this.attributeMap.Values.GetEnumerator();
enumerator.MoveNext();
current.Attribute = enumerator.Current;
while (enumerator.MoveNext())
{
current = current.Next = new AttributeSourceState();
current.Attribute = enumerator.Current;
}
return state;
}
private string CreateStateExceptionMessage(AttributeSourceState state)
{
return
"The state contains an attribute of type '{0}' " +
"that is currently not found within this instance of '{1}#{2}'. "
.Inject(state.Attribute.GetType(), this.GetType().Name, this.GetHashCode());
}
///
/// The enumerator for instances stored inside
/// of an instance.
///
public sealed class AttributeEnumerator : IEnumerator
{
private AttributeSourceState state;
private AttributeSource source;
///
/// Initializes a new instance of the class.
///
/// The source.
internal AttributeEnumerator(AttributeSource source)
{
this.source = source;
this.state = source.GetCurrentState();
}
///
/// Gets the current at this position.
///
/// The current.
///
/// Thrown when has already been called on the object.
///
public AttributeBase Current
{
get
{
if (this.source == null)
throw new ObjectDisposedException(this.GetType().Name);
return this.state.Attribute;
}
}
object System.Collections.IEnumerator.Current
{
get { return this.Current; }
}
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
this.state = null;
this.source = null;
}
///
/// Advances the enumerator to the next element of the collection.
///
///
/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
///
/// The collection was modified after the enumerator was created.
///
/// Thrown when has already been called on the object.
///
public bool MoveNext()
{
if (this.source == null)
throw new ObjectDisposedException(this.GetType().Name);
this.state = this.state.Next;
return this.state != null;
}
///
/// Sets the enumerator to its initial position, which is before the first element in the collection.
///
/// The collection was modified after the enumerator was created.
///
/// Thrown when has already been called on the object.
///
public void Reset()
{
if (this.source == null)
throw new ObjectDisposedException(this.GetType().Name);
this.state = this.source.GetCurrentState();
}
}
}
}