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