- UID
- 658062
- 积分
- 2147
- 精华
- 贡献
-
- 威望
-
- 活跃度
-
- D豆
-
- 在线时间
- 小时
- 注册时间
- 2008-10-22
- 最后登录
- 1970-1-1
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
/// ObjectIdFilter.cs Copyright (c) 2012 Tony Tanzillo
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using System.Collections;
/// <summary>
///
/// ObjectIdFilter class
///
/// Used to efficiently do simple and complex filtering
/// of ObjectIds based on associated managed wrapper types.
///
/// This class helps to simplify Linq code that performs
/// complex filtering of ObjectIds based on the associated
/// managed wrapper type of each ObjectId.
///
/// An example:
///
/// BlockTableRecord btr = //.. (open some BlockTableRecord)
///
/// // Get a filter that includes only entities
/// // whose managed wrappers are derived from
/// // AutoDesk.AutoCAD.DatabaseServices.Curve:
///
/// ObjectIdFilter curveFilter = new ObjectIdFilter<Curve>();
///
/// // Get an object that enumerates the ObjectIds
/// // of all Curve objects in the BlockTableRecord:
///
/// var curveIds = btr.Cast<ObjectId>().Where( curveFilter.Predicate );
///
/// // In AutoCAD 2013 or later, the above line can
/// // be reduced to:
///
/// var curveIds = btr.Where( curveFilter.Predicate );
///
/// // Additionally, in all AutoCAD releases, the
/// // GetObjectIds() method of the ObjectIdFilter
/// // class can be used to do the equivalent:
///
/// var curveIds = curveFilter.GetObjectIds( btr );
///
/// Complex Filtering Criteria:
///
/// The ObjectIdFilter class as used above offers little
/// advantages over explicit testing of each ObjectId's
/// ObjectClass property with standard Linq methods, but
/// the ObjectIdFilter was actually created to simplify
/// a more-complicated problem, which involves filtering
/// against multiple types, both related and unrelated;
/// filtering against types derived from multiple types;
/// and being more-selective, by explicitly including or
/// excluding certain specified types individually.
///
/// For example, the following filter will include only
/// the ObjectIds of Lines, Arcs, and Polylines:
///
/// var curveSubSetFilter = new ObjectIdFilter(
/// typeof( Line ),
/// typeof( Arc ),
/// typeof( Polyline )
/// );
///
/// Overriding Criteria:
///
/// The ObjectIdFilter allows the programmer to override
/// criteria that includes or excludes all types derived
/// from a given type, to specifically include or exclude
/// one or more derived types, on a type-by-type basis.
///
/// This example includes all types of Dimensions except
/// for OrdinateDimensions using an overriding criteria:
///
/// // Define an ObjectIdFilter with the sole criteria
/// // that includes all types derived from Dimension:
///
/// ObjectIdFilter dimfilter = new ObjectIdFilter<Dimension>();
///
/// // Override the criteria that includes all types
/// // derived from Dimension, to exclude Ordinate
/// // dimensions:
///
/// dimfilter[typeof(OrdinateDimension)] = false;
///
/// Overriding criteria can also override other
/// overriding criteria.
///
/// The next example uses an override on a filter that
/// includes all Curve types, to exclude Splines and all
/// types derived from Spline, and then overrides that
/// criteria to specifically include Helixes, which are
/// derived from Spline:
///
/// var myFilter = new ObjectIdFilter<Curve>();
///
/// // allow/disallow types derived from those
/// // types that are explicitly-included or
/// // excluded:
///
/// myFilter.ExactMatch = false;
///
/// // Add an override that excludes Splines and
/// // all types derived from Spline:
///
/// myFilter[typeof(Spline)] = false;
///
/// // we then override the above override that excludes
/// // Spline and all derived types, to specifically
/// // include Helixes (which are derived from Spline):
///
/// myFilter[typeof(Helix)] = true;
///
/// The the above example defines a filter that
/// includes all Curves, including Helixes, but
/// not Splines or any other type derived from
/// Spline. and is functionally-equivalent to
/// this delegate:
///
/// bool IsCurveOrNotSplineExceptHelix( ObjectId id )
/// {
/// RXClass curveClass = RXClass.GetClass(typeof(Curve));
/// RXClass splineClass = RXClass.GetClass(typeof(Spline));
/// RXClass helixClass = RXClass.GetClass(typeof(Helix));
///
/// RXClass objectClass = id.ObjectClass;
///
/// return objectClass.IsDerivedFrom( curveClass )
/// && ( ! objectClass.IsDerivedFrom( splineClass )
/// || objectClass.IsDerivedFrom( helixClass ) );
/// }
///
/// As unrealistic as it is, the above example serves to
/// demonstrate the degree of granularity that can easily
/// be achieved using the ObjectIdFilter with overriding
/// criteria.
///
/// This next example uses two overriding criteria to
/// include all Curve objects EXCEPT XLines and Rays:
///
/// var myFilter = new ObjectIdFilter<Curve>();
/// myFilter[typeof(XLine)] = false;
/// myFilter[typeof(Ray)] = false;
///
/// The predicate generated by ObjectIdFilter in the
/// above example does what this delegate does:
///
/// bool IsCurveExceptXLineOrRay( ObjectId id )
/// {
/// RXClass curveClass = RXClass.GetClass(typeof(Curve));
/// RXClass rayClass = RXClass.GetClass(typeof(Ray));
/// RXClass xlineClass = RXClass.GetClass(typeof(XLine));
///
/// RXClass objectClass = id.ObjectClass;
/// return objectClass.IsDerivedFrom( curveClass )
/// && objectClass != rayClass
/// && objectClass != xlineClass;
/// }
///
/// Note that the equivalent predicates are relatively-complex
/// in contrast to the ObjectIdFilter, and yet ObjectIdFilter
/// is as fast, and in some cases faster than the direct use
/// of hand-coded predicates when used with the Linq Where()
/// function.
///
/// Performance:
///
/// The performance of ObjectIdFilter is comparable to the
/// performance of explicit, hand-coded predicates in most
/// use cases, and can be faster in some, mainly those that
/// involve expensive predicates. The basic purpose of this
/// class is not to out-perform other methods of filtering,
/// but more for the purpose of simplifying the writing of
/// code that defines the criteria, and to support dynamic
/// composition of filters based on criteria that is unknown
/// at compile-time, and would otherwise require complicated
/// means to achieve.
///
/// </summary>
namespace Autodesk.AutoCAD.DatabaseServices
{
public class ObjectIdFilter<T> : ObjectIdFilter
where T : DBObject
{
public ObjectIdFilter( bool exact = false )
: base( exact, typeof( T ) )
{
}
}
public class ObjectIdFilter : ICollection<Type> // IDictionary<Type, bool> (work in progress)
{
bool exactMatch = false;
Dictionary<Type, bool> types = null;
Dictionary<IntPtr, Type> typemap = null;
Dictionary<IntPtr, bool> cache = new Dictionary<IntPtr, bool>();
RXClass rxclass = null;
IntPtr rxclassPtr = IntPtr.Zero;
IntPtr rootClass = RXClass.GetClass( typeof( DBObject ) ).MyParent.UnmanagedObject;
bool validated = false;
/// If only one Type is provided, exactMatch defaults
/// to false. Otherwise, it defaults to true.
public ObjectIdFilter( params Type[] items )
: this( defaultExactMatch( items ), items )
{
}
/// <summary>
/// If exactMatch is true, types derived from any of
/// the specified types do not match.
/// </summary>
public ObjectIdFilter( bool exactMatch, params Type[] types )
{
if( types == null || types.Length == 0 )
types = new Type[0];
else
{
if( !types.All( type => type.IsSubclassOf( typeof( DBObject ) ) ) )
throw new ArgumentException( "Type must derive from DBObject" );
}
this.types = types.ToDictionary( t => t, t => true );
Invalidate();
}
Func<ObjectId, bool> predicate = null;
public Func<ObjectId, bool> Predicate
{
get
{
Update();
return this.predicate;
}
}
/// <summary>
///
/// Indicates if types derived from types contained
/// in the match set are treated the same as the base
/// types in the match set.
///
/// If true, derived types inherit the criteria of then
/// nearest base type contained in the match set. If
/// multiple base types of a derived type are present in
/// the match set, the derived type inherits the critera
/// of the most-immediate base type.
///
/// If false, derived types are treated as distinct types
/// that must have an entry in the match set in order for
/// the filter to explicitly include or exclude them.
///
/// </summary>
public bool ExactMatch
{
get
{
return exactMatch;
}
set
{
if( value != exactMatch )
{
exactMatch = value;
Invalidate();
}
}
}
/// <summary>
///
/// Adds a managed wrapper type to the filter
/// that specifies that managed wrappers of the
/// given type match the filter.
///
/// If the ExactMatch property is false, all
/// managed wrappers whose type is derived from
/// the given type also match the filter.
///
/// The given type must be a type derived from
/// DBObject.
///
/// </summary>
public void Add( Type type )
{
Add( type, true );
}
/// <summary>
///
/// Adds a managed wrapper type to the filter
/// and a value that specifies if managed wrappers
/// of the given type match the filter.
///
/// If the value of the include argument is true,
/// managed wrappers of the specified type match
/// the filter.
///
/// If the value of the include argument is false,
/// managed wrappers of the specified type do not
/// match the filter.
///
/// If the ExactMatch property is false, the include
/// argument applies to all managed wrappers whose
/// runtime type is the given type, or any type that
/// is derived from the given type.
///
/// The given type must be a type derived from DBObject.
///
/// </summary>
public void Add( Type type, bool include )
{
if( type == null )
throw new ArgumentNullException( "type" );
if( types.ContainsKey( type ) )
throw new ArgumentException( "Specified type is already present" );
if( !type.IsSubclassOf( typeof( DBObject ) ) )
throw new ArgumentException( "Specified type must be derived from DBObject" );
types.Add( type, include );
Invalidate();
}
/// <summary>
///
/// Removes the specied type from the filter set,
/// if it contains the type.
///
/// </summary>
public bool Remove( Type type )
{
if( type == null )
throw new ArgumentNullException( "type" );
if( types.Remove( type ) )
{
Invalidate();
return true;
}
return false;
}
/// <summary>
///
/// Associates a value with a type key, indicating if
/// ObjectIds whose managed wrapper types are instances
/// of the given type (or a derived type if ExactMatch
/// is false) match the filter.
///
/// Pass true/false to include/exclude managed wrappers of
/// the given type (and all derived types, if ExactMatch
/// is false).
///
/// If ExactMatch is false, ObjectIds whose managed
/// wrapper types are derived from the specified type
/// implicitly inherit the assigned value.
///
/// To exclude a given type (and optionally, all derived
/// types if ExactMatch = false) from the filter, pass
/// false with the type.
///
/// </summary>
public bool this[Type type]
{
get
{
bool result = false;
types.TryGetValue( type, out result );
return result;
}
set
{
bool current = false;
if( !types.TryGetValue( type, out current ) || current != value )
{
types[type] = value;
Invalidate();
}
}
}
/// <summary>
///
/// Returns an object that enumerates all matching ObjectIds
/// in the given BlockTableRecord.
///
/// Note:
///
/// In AutoCAD 2013 or later, this method results in an ambiguous
/// match error in the compiler because in AutoCAD 2013 or later,
/// the BlockTableRecord supports IEnumerable<ObjectId>, for which
/// there is an overload of GetObjectIds() included below.
///
/// So, this method can be commented out or removed when targeting
/// AutoCAD 2013 or later. If you leave the method in you can avoid
/// the compiler error by using Cast<ObjectId>().
///
/// </summary>
public IEnumerable<ObjectId> GetObjectIds( BlockTableRecord btr )
{
Func<ObjectId, bool> func = this.Predicate;
foreach( ObjectId id in btr )
{
if( func( id ) )
yield return id;
}
}
/// <summary>
///
/// Returns an object that enumerates all matching
/// ObjectIds in the given sequence.
///
/// </summary>
public IEnumerable<ObjectId> GetObjectIds( IEnumerable<ObjectId> source )
{
if( source == null )
throw new ArgumentNullException( "source" );
return source.Where( this.Predicate );
}
/// <summary>
///
/// Enumerate the matching ObjectIds contained in all
/// BlockTableRecords whose ObjectIds are passed in the
/// arguments. All BlockTableRecord ObjectIds must be
/// from the same database.
///
/// Requires an active transaction in the database
/// which the BlockTableRecords are from.
///
/// </summary>
public IEnumerable<ObjectId> GetObjectIds( params ObjectId[] blockTableRecordIds )
{
if( blockTableRecordIds == null )
throw new ArgumentNullException( "blockTableRecordIds" );
RXClass btrClass = RXClass.GetClass( typeof( BlockTableRecord ) );
if( !blockTableRecordIds.All( id => id.ObjectClass.IsDerivedFrom( btrClass ) ) )
throw new ArgumentException( "Requires the ObjectId of one or more BlockTableRecords" );
if( blockTableRecordIds.Length > 0 )
{
Transaction tr = blockTableRecordIds[0].Database.TransactionManager.TopTransaction;
if( tr == null )
throw new Autodesk.AutoCAD.Runtime.Exception( ErrorStatus.NoActiveTransactions );
Func<ObjectId, bool> func = this.Predicate;
foreach( var btrId in blockTableRecordIds )
{
BlockTableRecord btr = (BlockTableRecord) tr.GetObject( btrId, OpenMode.ForRead );
foreach( ObjectId id in btr )
{
if( func( id ) )
yield return id;
}
}
}
}
/// <summary>
/// Debugging-only APIs
/// </summary>
[Flags]
public enum MatchFlags
{
Subclass = 0,
Exact = 1,
Multiple = 2,
Negated = 4
}
public int CacheCount
{
get
{
return cache != null ? cache.Count : 0;
}
}
public MatchFlags MatchType
{
get;
protected set;
}
////////////////////////////////////////////////////////
/// Dynamic Predicate Selection:
///
/// One of the following five methods is returned as
/// the value of the Predicate property, and is used
/// to determine if an ObjectId matches the filter,
/// depending on the number of types being matched
/// against, and whether exact matching is used.
///////////////////////////////////////////////////////////
/// Case 1: Used when only one Type is present in the
/// match set, and exact matching is in use.
///
/// Exact matching is used if it has been explicitly
/// specified, or if the type(s) being filtered are
/// not abstract, and there are no runtime classes
/// derived from their associated runtime class.
bool matchExact( ObjectId id )
{
return id.ObjectClass.UnmanagedObject == rxclassPtr;
}
///////////////////////////////////////////////////////////
/// Case 2: Used when only one Type is present in the
/// match set, exact matching is in use, and the type
/// is specified to be excluded (e.g., everything but
/// the specified type matches the filter).
bool negatedMatchExact( ObjectId id )
{
return id.ObjectClass.UnmanagedObject != rxclassPtr;
}
///////////////////////////////////////////////////////////
/// Case 3: Used when only one Type is present in the
/// match set, and exact matching is not being used.
bool match( ObjectId id )
{
RXClass objclass = id.ObjectClass;
bool result;
if( !cache.TryGetValue( objclass.UnmanagedObject, out result ) )
result = cache[objclass.UnmanagedObject] = objclass.IsDerivedFrom( rxclass );
return result;
}
///////////////////////////////////////////////////////////
/// Case 4: Used when the match set contains multiple types
/// and exact matching is used. A managed wrapper matches the
/// filter if its runtime class is in the match set, and that
/// type is specified to be included. Managed wrappers whose
/// types are derived from types in the match set do not match.
bool matchManyExact( ObjectId id )
{
bool result = false;
cache.TryGetValue( id.ObjectClass.UnmanagedObject, out result );
return result;
}
///////////////////////////////////////////////////////////
/// Case 5: Used in the most-complex (and least-common)
/// scenario: Matching aginst multiple types, and all
/// of their derived types.
///
/// In this scenario, a managed wrapper matches if its
/// runtime type or any of its base types are contained
/// in the match set and the type is included.
///
/// While this code is relatively-expensive, it only
/// needs to be called once for each distinct runtime
/// class encountered in the set of ObjectIds that are
/// being filtered. The result is cached and used for
/// all subsequently-encountered instances of the same
/// runtime class.
///
/// This predicate can be significantly-faster than an
/// equivalent hand-coded predicate, largely due to
/// caching and reusing the result with all instances
/// of the same runtime class.
bool matchMany( ObjectId id )
{
bool result;
if( !cache.TryGetValue( id.ObjectClass.UnmanagedObject, out result ) )
{
RXClass objectClass = id.ObjectClass;
/// Look in the match set for the nearest parent
/// runtime class, and if found, use its value:
RXClass parent = objectClass.MyParent;
IntPtr ptr = parent.UnmanagedObject;
while( ptr != rootClass && !typemap.ContainsKey( ptr ) )
{
parent = parent.MyParent;
ptr = parent.UnmanagedObject;
}
if( ptr != rootClass )
result = cache[ptr];
cache[objectClass.UnmanagedObject] = result;
}
return result;
}
/// <summary>
///
/// Assigned to the predicate when the instance has been
/// invalidated. The use of the predicate will cause the
/// cache to be rebuilt, and the match predicate to be
/// recomputed and reassigned to the predicate.
///
/// </summary>
bool Invalid( ObjectId id )
{
Update();
if( predicate == null || predicate == this.Invalid )
throw new InvalidOperationException( "Somthing is terribly wrong" );
return predicate( id );
}
/// <summary>
///
/// Invalidates the filter and with that, any previously-
/// obtained references to the Predicate property. This
/// method generally does not have to be called by the
/// user, because any change that is made to the filter
/// will implicitly invalidate it.
///
/// </summary>
public void Invalidate()
{
predicate = Invalid;
validated = false;
}
/// <summary>
///
/// Updates the filter and invalidates the current
/// predicate. If this method is called, any previously-
/// obtained reference to the Predicate property is no
/// longer valid and should be discarded or reassigned
/// to the current value of that property after the call
/// to this method returns.
///
/// </summary>
public void Update()
{
if( validated )
return;
validated = true;
MatchType = 0;
if( types.Count == 0 )
{
predicate = id => false;
return;
}
typemap = types.Keys.ToDictionary( t => GetRXClassPtr( t ), t => t );
cache = types.ToDictionary( p => GetRXClassPtr( p.Key ), p => p.Value );
bool exact = GetMatchExact();
if( types.Count == 1 )
{
var pair = types.Single();
rxclass = GetRXClass( pair.Key );
rxclassPtr = rxclass.UnmanagedObject;
if( exact )
{
if( pair.Value )
{
predicate = matchExact;
}
else
{
predicate = negatedMatchExact;
MatchType |= MatchFlags.Negated;
}
}
else
{
predicate = match;
}
}
else
{
if( exact )
{
predicate = matchManyExact;
}
else
{
predicate = matchMany;
}
MatchType |= MatchFlags.Multiple;
}
if( exact )
MatchType |= MatchFlags.Exact;
}
/// <summary>
/// Returns true if the given type can use exact matching
/// </summary>
bool GetMatchExact( Type type )
{
if( type.IsAbstract )
return false;
IntPtr impobj = GetRXClass( type ).UnmanagedObject;
foreach( DictionaryEntry e in SystemObjects.ClassDictionary )
{
RXClass entry = ( (RXClass) e.Value ).MyParent;
if( entry != null && impobj == entry.UnmanagedObject )
return false;
}
return true;
}
/// <summary>
/// Returns true if all Types can use exact matching.
/// </summary>
bool GetMatchExact()
{
if( types.Count == 0 )
return false;
if( exactMatch )
return true;
if( types.Count == 1 )
return GetMatchExact( types.Single().Key );
if( types.Keys.Any( type => type.IsAbstract ) )
return false;
foreach( DictionaryEntry e in SystemObjects.ClassDictionary )
{
RXClass entry = ( (RXClass) e.Value ).MyParent;
if( entry != null && typemap.ContainsKey( entry.UnmanagedObject ) )
return false;
}
return true;
}
static IntPtr GetRXClassPtr( Type type, bool check = true )
{
return GetRXClass( type, check ).UnmanagedObject;
}
static RXClass GetRXClass( Type type, bool check = true )
{
if( type == null )
throw new ArgumentNullException( "type" );
if( !typeof( DBObject ).IsAssignableFrom( type ) )
{
if( check )
throw new ArgumentException( "Requires a type derived from DBObject" );
return null;
}
RXClass result = RXClass.GetClass( type );
if( result == null && check )
throw new InvalidOperationException( "No runtime class found for type " + type.Name );
return result;
}
static bool defaultExactMatch( Type[] types )
{
if( types == null )
return true;
else
return types.Length != 1;
}
/// ICollection<Type>
public void Clear()
{
types.Clear();
Invalidate();
}
public bool Contains( Type item )
{
return types.ContainsKey( item );
}
public void CopyTo( Type[] array, int arrayIndex )
{
types.Keys.CopyTo( array, arrayIndex );
}
public int Count
{
get
{
return types.Count;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public IEnumerator<Type> GetEnumerator()
{
return types.Keys.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public static implicit operator Func<ObjectId, bool>( ObjectIdFilter filter )
{
if( filter == null )
throw new ArgumentNullException( "right operand" );
return filter.Predicate;
}
}
}
|
|