23 changed files with 1085 additions and 9 deletions
@ -0,0 +1,164 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.Collections.Specialized; |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="IModelCollection{T}"/> that works by concatening multiple
|
||||||
|
/// other model collections.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ConcatModelCollection<T> : IModelCollection<T> |
||||||
|
{ |
||||||
|
sealed class InputCollection : Collection<IModelCollection<T>> |
||||||
|
{ |
||||||
|
readonly ConcatModelCollection<T> owner; |
||||||
|
|
||||||
|
public InputCollection(ConcatModelCollection<T> owner) |
||||||
|
{ |
||||||
|
this.owner = owner; |
||||||
|
} |
||||||
|
|
||||||
|
protected override void ClearItems() |
||||||
|
{ |
||||||
|
if (owner.collectionChanged != null) { |
||||||
|
foreach (var input in Items) { |
||||||
|
input.CollectionChanged -= owner.OnInputCollectionChanged; |
||||||
|
} |
||||||
|
} |
||||||
|
base.ClearItems(); |
||||||
|
owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void InsertItem(int index, IModelCollection<T> item) |
||||||
|
{ |
||||||
|
if (owner.collectionChanged != null) |
||||||
|
item.CollectionChanged += owner.OnInputCollectionChanged; |
||||||
|
base.InsertItem(index, item); |
||||||
|
owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs( |
||||||
|
NotifyCollectionChangedAction.Add, item.ToArray(), owner.GetCount(index))); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void RemoveItem(int index) |
||||||
|
{ |
||||||
|
if (owner.collectionChanged != null) |
||||||
|
Items[index].CollectionChanged -= owner.OnInputCollectionChanged; |
||||||
|
var oldItems = Items[index].ToArray(); |
||||||
|
base.RemoveItem(index); |
||||||
|
owner.RaiseCollectionChanged(new NotifyCollectionChangedEventArgs( |
||||||
|
NotifyCollectionChangedAction.Remove, oldItems, owner.GetCount(index))); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void SetItem(int index, IModelCollection<T> item) |
||||||
|
{ |
||||||
|
RemoveItem(index); |
||||||
|
InsertItem(index, item); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
InputCollection inputs; |
||||||
|
|
||||||
|
public ConcatModelCollection() |
||||||
|
{ |
||||||
|
this.inputs = new InputCollection(this); |
||||||
|
} |
||||||
|
|
||||||
|
public IList<IModelCollection<T>> Inputs { |
||||||
|
get { return inputs; } |
||||||
|
} |
||||||
|
|
||||||
|
NotifyCollectionChangedEventHandler collectionChanged; |
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged { |
||||||
|
add { |
||||||
|
var oldEventHandlers = collectionChanged; |
||||||
|
collectionChanged += value; |
||||||
|
if (oldEventHandlers == null && collectionChanged != null) { |
||||||
|
foreach (var input in inputs) |
||||||
|
input.CollectionChanged += OnInputCollectionChanged; |
||||||
|
} |
||||||
|
} |
||||||
|
remove { |
||||||
|
var oldEventHandlers = collectionChanged; |
||||||
|
collectionChanged -= value; |
||||||
|
if (oldEventHandlers != null && collectionChanged == null) { |
||||||
|
foreach (var input in inputs) |
||||||
|
input.CollectionChanged -= OnInputCollectionChanged; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e) |
||||||
|
{ |
||||||
|
if (collectionChanged != null) |
||||||
|
collectionChanged(this, e); |
||||||
|
} |
||||||
|
|
||||||
|
void OnInputCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) |
||||||
|
{ |
||||||
|
int inputIndex = inputs.IndexOf((IModelCollection<T>)sender); |
||||||
|
int startIndex = GetCount(inputIndex); |
||||||
|
NotifyCollectionChangedEventArgs newEventArgs; |
||||||
|
switch (e.Action) { |
||||||
|
case NotifyCollectionChangedAction.Add: |
||||||
|
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.NewItems, startIndex + e.NewStartingIndex); |
||||||
|
break; |
||||||
|
case NotifyCollectionChangedAction.Remove: |
||||||
|
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex); |
||||||
|
break; |
||||||
|
case NotifyCollectionChangedAction.Replace: |
||||||
|
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, e.NewItems, startIndex + e.OldStartingIndex); |
||||||
|
break; |
||||||
|
case NotifyCollectionChangedAction.Move: |
||||||
|
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action, e.OldItems, startIndex + e.OldStartingIndex, startIndex + e.NewStartingIndex); |
||||||
|
break; |
||||||
|
case NotifyCollectionChangedAction.Reset: |
||||||
|
newEventArgs = new NotifyCollectionChangedEventArgs(e.Action); |
||||||
|
break; |
||||||
|
default: |
||||||
|
throw new NotSupportedException("Invalid value for NotifyCollectionChangedAction"); |
||||||
|
} |
||||||
|
collectionChanged(this, newEventArgs); |
||||||
|
} |
||||||
|
|
||||||
|
public int Count { |
||||||
|
get { return GetCount(inputs.Count); } |
||||||
|
} |
||||||
|
|
||||||
|
int GetCount(int inputIndex) |
||||||
|
{ |
||||||
|
int count = 0; |
||||||
|
for (int i = 0; i < inputIndex; i++) { |
||||||
|
count += inputs[i].Count; |
||||||
|
} |
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() |
||||||
|
{ |
||||||
|
return inputs.SelectMany(i => i).GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
public T this[int index] { |
||||||
|
get { |
||||||
|
int inputIndex = 0; |
||||||
|
while (index >= inputs[inputIndex].Count) { |
||||||
|
index -= inputs[inputIndex].Count; |
||||||
|
inputIndex++; |
||||||
|
} |
||||||
|
return inputs[inputIndex][index]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.ComponentModel; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// An NRefactory entity as a model.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEntityModel : INotifyPropertyChanged |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent project that contains this entity.
|
||||||
|
/// May return null if the entity is not part of a project.
|
||||||
|
/// </summary>
|
||||||
|
IProject ParentProject { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the region where this entity is defined.
|
||||||
|
/// </summary>
|
||||||
|
DomRegion Region { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the entity in the current solution snapshot.
|
||||||
|
/// Returns null if the entity could not be resolved.
|
||||||
|
/// </summary>
|
||||||
|
IEntity Resolve(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the entity in the specified solution snapshot.
|
||||||
|
/// Returns null if the entity could not be resolved.
|
||||||
|
/// </summary>
|
||||||
|
IEntity Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// The context for an entity model.
|
||||||
|
/// This may be a reference to a project, or a compilation provider for a single stand-alone code file.
|
||||||
|
/// </summary>
|
||||||
|
public interface IEntityModelContext |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Used for <see cref="IEntityModel.ParentProject"/>.
|
||||||
|
/// </summary>
|
||||||
|
IProject Project { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for <see cref="IEntityModel.Resolve()"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="solutionSnapshot">
|
||||||
|
/// The solution snapshot provided to <see cref="IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping)"/>,
|
||||||
|
/// or null if the <see cref="IEntityModel.Resolve()"/> overload was used.
|
||||||
|
/// </param>
|
||||||
|
ICompilation GetCompilation(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if part1 is considered a better candidate for the primary part than part2.
|
||||||
|
/// </summary>
|
||||||
|
bool IsBetterPart(IUnresolvedTypeDefinition part1, IUnresolvedTypeDefinition part2); |
||||||
|
} |
||||||
|
|
||||||
|
public class ProjectEntityModelContext : IEntityModelContext |
||||||
|
{ |
||||||
|
readonly IProject project; |
||||||
|
readonly string primaryCodeFileExtension; |
||||||
|
|
||||||
|
public ProjectEntityModelContext(IProject project, string primaryCodeFileExtension) |
||||||
|
{ |
||||||
|
if (project == null) |
||||||
|
throw new ArgumentNullException("project"); |
||||||
|
this.project = project; |
||||||
|
this.primaryCodeFileExtension = primaryCodeFileExtension; |
||||||
|
} |
||||||
|
|
||||||
|
public IProject Project { |
||||||
|
get { return project; } |
||||||
|
} |
||||||
|
|
||||||
|
public ICompilation GetCompilation(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||||
|
{ |
||||||
|
if (solutionSnapshot != null) |
||||||
|
return solutionSnapshot.GetCompilation(project); |
||||||
|
else |
||||||
|
return SD.ParserService.GetCompilation(project); |
||||||
|
} |
||||||
|
|
||||||
|
public bool IsBetterPart(IUnresolvedTypeDefinition part1, IUnresolvedTypeDefinition part2) |
||||||
|
{ |
||||||
|
IUnresolvedFile file1 = part1.UnresolvedFile; |
||||||
|
IUnresolvedFile file2 = part2.UnresolvedFile; |
||||||
|
if (file1 != null && file2 == null) |
||||||
|
return true; |
||||||
|
if (file1 == null) |
||||||
|
return false; |
||||||
|
bool file1HasExtension = file1.FileName.EndsWith(primaryCodeFileExtension, StringComparison.OrdinalIgnoreCase); |
||||||
|
bool file2HasExtension = file2.FileName.EndsWith(primaryCodeFileExtension, StringComparison.OrdinalIgnoreCase); |
||||||
|
if (file1HasExtension && !file2HasExtension) |
||||||
|
return true; |
||||||
|
if (!file1HasExtension && file2HasExtension) |
||||||
|
return false; |
||||||
|
return file1.FileName.Length < file2.FileName.Length; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Observable model for a member.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMemberModel : IEntityModel |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Resolves the member in the current solution snapshot.
|
||||||
|
/// Returns null if the member could not be resolved.
|
||||||
|
/// </summary>
|
||||||
|
IMember Resolve(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the member in the specified solution snapshot.
|
||||||
|
/// Returns null if the member could not be resolved.
|
||||||
|
/// </summary>
|
||||||
|
IMember Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,15 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Service that enables lookup of model objects from NRefactory objects.
|
||||||
|
/// </summary>
|
||||||
|
public interface IModelService |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Observable model for a type definition.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITypeDefinitionModel : IEntityModel |
||||||
|
{ |
||||||
|
FullTypeName FullTypeName { get; } |
||||||
|
IModelCollection<ITypeDefinitionModel> NestedTypes { get; } |
||||||
|
IModelCollection<IMemberModel> Members { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the type definition in the current solution snapshot.
|
||||||
|
/// Returns null if the type definition could not be resolved.
|
||||||
|
/// </summary>
|
||||||
|
new ITypeDefinition Resolve(); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolves the type definition in the specified solution snapshot.
|
||||||
|
/// Returns null if the type definition could not be resolved.
|
||||||
|
/// </summary>
|
||||||
|
new ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.Specialized; |
||||||
|
using System.Linq; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A collection of type definition models with the ability to look them up by name.
|
||||||
|
/// </summary>
|
||||||
|
public interface ITypeDefinitionModelCollection : IModelCollection<ITypeDefinitionModel> |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Gets the type definition model with the specified type name.
|
||||||
|
/// Returns null if no such model object exists.
|
||||||
|
/// </summary>
|
||||||
|
ITypeDefinitionModel this[FullTypeName fullTypeName] { get; } |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the type definition model with the specified type name.
|
||||||
|
/// Returns null if no such model object exists.
|
||||||
|
/// </summary>
|
||||||
|
ITypeDefinitionModel this[TopLevelTypeName topLevelTypeName] { get; } |
||||||
|
} |
||||||
|
|
||||||
|
public sealed class EmptyTypeDefinitionModelCollection : ITypeDefinitionModelCollection |
||||||
|
{ |
||||||
|
public static readonly EmptyTypeDefinitionModelCollection Instance = new EmptyTypeDefinitionModelCollection(); |
||||||
|
|
||||||
|
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged { |
||||||
|
add { } |
||||||
|
remove { } |
||||||
|
} |
||||||
|
|
||||||
|
ITypeDefinitionModel ITypeDefinitionModelCollection.this[FullTypeName name] { |
||||||
|
get { return null; } |
||||||
|
} |
||||||
|
|
||||||
|
ITypeDefinitionModel ITypeDefinitionModelCollection.this[TopLevelTypeName name] { |
||||||
|
get { return null; } |
||||||
|
} |
||||||
|
|
||||||
|
ITypeDefinitionModel IReadOnlyList<ITypeDefinitionModel>.this[int index] { |
||||||
|
get { |
||||||
|
throw new ArgumentOutOfRangeException(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int IReadOnlyCollection<ITypeDefinitionModel>.Count { |
||||||
|
get { return 0; } |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerator<ITypeDefinitionModel> IEnumerable<ITypeDefinitionModel>.GetEnumerator() |
||||||
|
{ |
||||||
|
return Enumerable.Empty<ITypeDefinitionModel>().GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return Enumerable.Empty<ITypeDefinitionModel>().GetEnumerator(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.Collections.Specialized; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Observable KeyedCollection.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class KeyedModelCollection<TKey, TItem> : KeyedCollection<TKey, TItem>, IModelCollection<TItem> |
||||||
|
{ |
||||||
|
protected override void ClearItems() |
||||||
|
{ |
||||||
|
base.ClearItems(); |
||||||
|
if (CollectionChanged != null) |
||||||
|
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void InsertItem(int index, TItem item) |
||||||
|
{ |
||||||
|
base.InsertItem(index, item); |
||||||
|
if (CollectionChanged != null) |
||||||
|
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void RemoveItem(int index) |
||||||
|
{ |
||||||
|
var oldItem = Items[index]; |
||||||
|
base.RemoveItem(index); |
||||||
|
if (CollectionChanged != null) |
||||||
|
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void SetItem(int index, TItem item) |
||||||
|
{ |
||||||
|
var oldItem = Items[index]; |
||||||
|
base.SetItem(index, item); |
||||||
|
if (CollectionChanged != null) |
||||||
|
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); |
||||||
|
} |
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,82 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.CSharp; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
using NUnit.Framework; |
||||||
|
using Rhino.Mocks; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class CSharpModelTests |
||||||
|
{ |
||||||
|
IProject project; |
||||||
|
IProjectContent projectContent; |
||||||
|
IEntityModelContext context; |
||||||
|
TopLevelTypeDefinitionModelCollection topLevelTypeModels; |
||||||
|
|
||||||
|
#region SetUp and other helper methods
|
||||||
|
[SetUp] |
||||||
|
public virtual void SetUp() |
||||||
|
{ |
||||||
|
SD.InitializeForUnitTests(); |
||||||
|
SD.Services.AddStrictMockService<IParserService>(); |
||||||
|
project = MockRepository.GenerateStrictMock<IProject>(); |
||||||
|
projectContent = new CSharpProjectContent().AddAssemblyReferences(AssemblyLoader.Corlib); |
||||||
|
context = new ProjectEntityModelContext(project, ".cs"); |
||||||
|
topLevelTypeModels = new TopLevelTypeDefinitionModelCollection(context); |
||||||
|
|
||||||
|
SD.ParserService.Stub(p => p.GetCompilation(project)).WhenCalled(c => c.ReturnValue = projectContent.CreateCompilation()); |
||||||
|
} |
||||||
|
|
||||||
|
[TearDown] |
||||||
|
public virtual void TearDown() |
||||||
|
{ |
||||||
|
SD.TearDownForUnitTests(); |
||||||
|
} |
||||||
|
|
||||||
|
protected void AddCodeFile(string fileName, string code) |
||||||
|
{ |
||||||
|
var oldFile = projectContent.GetFile(fileName); |
||||||
|
Assert.IsNull(oldFile); |
||||||
|
var newFile = Parse(fileName, code); |
||||||
|
projectContent = projectContent.AddOrUpdateFiles(newFile); |
||||||
|
topLevelTypeModels.NotifyParseInformationChanged(oldFile, newFile); |
||||||
|
} |
||||||
|
|
||||||
|
IUnresolvedFile Parse(string fileName, string code) |
||||||
|
{ |
||||||
|
var parser = new CSharpParser(); |
||||||
|
var syntaxTree = parser.Parse(code, fileName); |
||||||
|
Assert.IsFalse(parser.HasErrors); |
||||||
|
return syntaxTree.ToTypeSystem(); |
||||||
|
} |
||||||
|
|
||||||
|
protected void UpdateCodeFile(string fileName, string code) |
||||||
|
{ |
||||||
|
var oldFile = projectContent.GetFile(fileName); |
||||||
|
Assert.IsNotNull(oldFile); |
||||||
|
var newFile = Parse(fileName, code); |
||||||
|
projectContent = projectContent.AddOrUpdateFiles(newFile); |
||||||
|
topLevelTypeModels.NotifyParseInformationChanged(oldFile, newFile); |
||||||
|
} |
||||||
|
|
||||||
|
protected void RemoveCodeFile(string fileName) |
||||||
|
{ |
||||||
|
var oldFile = projectContent.GetFile(fileName); |
||||||
|
projectContent = projectContent.RemoveFiles(fileName); |
||||||
|
topLevelTypeModels.NotifyParseInformationChanged(oldFile, null); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
[Test] |
||||||
|
public void EmptyProject() |
||||||
|
{ |
||||||
|
Assert.AreEqual(0, topLevelTypeModels.Count); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,42 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
using NUnit.Framework; |
||||||
|
using Rhino.Mocks; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class ProjectEntityModelContextTests |
||||||
|
{ |
||||||
|
IUnresolvedTypeDefinition CreateMockTypeDefinition(string fileName) |
||||||
|
{ |
||||||
|
var typeDefinition = MockRepository.GenerateStrictMock<IUnresolvedTypeDefinition>(); |
||||||
|
var file = MockRepository.GenerateStrictMock<IUnresolvedFile>(); |
||||||
|
file.Stub(f => f.FileName).Return(fileName); |
||||||
|
typeDefinition.Stub(td => td.UnresolvedFile).Return(file); |
||||||
|
return typeDefinition; |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void XamlCodeBehindIsBetterThanXaml() |
||||||
|
{ |
||||||
|
var context = new ProjectEntityModelContext(MockRepository.GenerateStrictMock<IProject>(), ".cs"); |
||||||
|
Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml.cs"), CreateMockTypeDefinition("Window.xaml"))); |
||||||
|
Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Window.cs"), CreateMockTypeDefinition("Window.xaml"))); |
||||||
|
Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml"), CreateMockTypeDefinition("Window.xaml.cs"))); |
||||||
|
Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Window.xaml"), CreateMockTypeDefinition("Window.cs"))); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void MainPartIsBetterThanDesigner() |
||||||
|
{ |
||||||
|
var context = new ProjectEntityModelContext(MockRepository.GenerateStrictMock<IProject>(), ".cs"); |
||||||
|
Assert.IsTrue(context.IsBetterPart(CreateMockTypeDefinition("Form.cs"), CreateMockTypeDefinition("Form.Designer.cs"))); |
||||||
|
Assert.IsFalse(context.IsBetterPart(CreateMockTypeDefinition("Form.Designer.cs"), CreateMockTypeDefinition("Form.cs"))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop |
||||||
|
{ |
||||||
|
public static class AssemblyLoader |
||||||
|
{ |
||||||
|
public static readonly IUnresolvedAssembly Corlib = new CecilLoader { LazyLoad = true }.LoadAssemblyFile(typeof(object).Assembly.Location); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,105 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.ComponentModel; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A mutable class that can track a member as the solution is being changed.
|
||||||
|
/// </summary>
|
||||||
|
sealed class MemberModel : IMemberModel |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A strong reference to the parent TypeDefinitionModel.Members collection.
|
||||||
|
/// This is necessary to prevent the garbage collector from
|
||||||
|
/// freeing the weak reference to the collection while one of the member models
|
||||||
|
/// is still in use.
|
||||||
|
/// If we don't prevent this, the type definition model might create multiple
|
||||||
|
/// MemberModels for the same member.
|
||||||
|
/// </summary>
|
||||||
|
internal IModelCollection<MemberModel> strongParentCollectionReference; |
||||||
|
|
||||||
|
readonly IEntityModelContext context; |
||||||
|
IUnresolvedMember member; |
||||||
|
|
||||||
|
public MemberModel(IEntityModelContext context, IUnresolvedMember member) |
||||||
|
{ |
||||||
|
if (context == null) |
||||||
|
throw new ArgumentNullException("context"); |
||||||
|
if (member == null) |
||||||
|
throw new ArgumentNullException("member"); |
||||||
|
this.context = context; |
||||||
|
this.member = member; |
||||||
|
} |
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged { add {} remove {} } |
||||||
|
|
||||||
|
public void Update(IUnresolvedMember newMember) |
||||||
|
{ |
||||||
|
if (newMember == null) |
||||||
|
throw new ArgumentNullException("newMember"); |
||||||
|
this.member = newMember; |
||||||
|
} |
||||||
|
|
||||||
|
public IProject ParentProject { |
||||||
|
get { return context.Project; } |
||||||
|
} |
||||||
|
|
||||||
|
public IUnresolvedMember UnresolvedMember { |
||||||
|
get { return member; } |
||||||
|
} |
||||||
|
|
||||||
|
public EntityType EntityType { |
||||||
|
get { return member.EntityType; } |
||||||
|
} |
||||||
|
|
||||||
|
public DomRegion Region { |
||||||
|
get { return member.Region; } |
||||||
|
} |
||||||
|
|
||||||
|
public string Name { |
||||||
|
get { return member.Name; } |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full type name of the type that declares this member.
|
||||||
|
/// </summary>
|
||||||
|
public FullTypeName DeclaringTypeName { |
||||||
|
get { |
||||||
|
if (member.DeclaringTypeDefinition != null) |
||||||
|
return member.DeclaringTypeDefinition.FullTypeName; |
||||||
|
else |
||||||
|
return new TopLevelTypeName(string.Empty, string.Empty); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#region Resolve
|
||||||
|
public IMember Resolve() |
||||||
|
{ |
||||||
|
var compilation = context.GetCompilation(null); |
||||||
|
return member.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)); |
||||||
|
} |
||||||
|
|
||||||
|
public IMember Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||||
|
{ |
||||||
|
var compilation = context.GetCompilation(solutionSnapshot); |
||||||
|
return member.Resolve(new SimpleTypeResolveContext(compilation.MainAssembly)); |
||||||
|
} |
||||||
|
|
||||||
|
IEntity IEntityModel.Resolve() |
||||||
|
{ |
||||||
|
return Resolve(); |
||||||
|
} |
||||||
|
|
||||||
|
IEntity IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||||
|
{ |
||||||
|
return Resolve(solutionSnapshot); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.ObjectModel; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.Specialized; |
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A TypeDefinitionModel-collection that holds models for all top-level types in a project content.
|
||||||
|
/// </summary>
|
||||||
|
sealed class TopLevelTypeDefinitionModelCollection : KeyedModelCollection<TopLevelTypeName, TypeDefinitionModel> |
||||||
|
{ |
||||||
|
readonly IEntityModelContext context; |
||||||
|
|
||||||
|
public TopLevelTypeDefinitionModelCollection(IEntityModelContext context) |
||||||
|
{ |
||||||
|
if (context == null) |
||||||
|
throw new ArgumentNullException("context"); |
||||||
|
this.context = context; |
||||||
|
} |
||||||
|
|
||||||
|
public TypeDefinitionModel this[FullTypeName fullTypeName] { |
||||||
|
get { |
||||||
|
TypeDefinitionModel model = base[fullTypeName.TopLevelTypeName]; |
||||||
|
for (int i = 0; i < fullTypeName.NestingLevel; i++) { |
||||||
|
throw new NotImplementedException(); |
||||||
|
} |
||||||
|
return model; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the parse information.
|
||||||
|
/// </summary>
|
||||||
|
public void NotifyParseInformationChanged(IUnresolvedFile oldFile, IUnresolvedFile newFile) |
||||||
|
{ |
||||||
|
if (oldFile != null) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected override TopLevelTypeName GetKeyForItem(TypeDefinitionModel item) |
||||||
|
{ |
||||||
|
return item.FullTypeName.TopLevelTypeName; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,264 @@ |
|||||||
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||||
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.Collections; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Collections.Specialized; |
||||||
|
using System.ComponentModel; |
||||||
|
using System.Linq; |
||||||
|
|
||||||
|
using ICSharpCode.NRefactory.TypeSystem; |
||||||
|
using ICSharpCode.SharpDevelop.Parser; |
||||||
|
using ICSharpCode.SharpDevelop.Project; |
||||||
|
|
||||||
|
namespace ICSharpCode.SharpDevelop.Dom |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// A mutable class that can track a type definition as the solution is being changed.
|
||||||
|
/// </summary>
|
||||||
|
sealed class TypeDefinitionModel : ITypeDefinitionModel |
||||||
|
{ |
||||||
|
readonly IEntityModelContext context; |
||||||
|
readonly FullTypeName fullTypeName; |
||||||
|
List<IUnresolvedTypeDefinition> parts; |
||||||
|
|
||||||
|
public TypeDefinitionModel(IEntityModelContext context, params IUnresolvedTypeDefinition[] parts) |
||||||
|
{ |
||||||
|
if (context == null) |
||||||
|
throw new ArgumentNullException("context"); |
||||||
|
if (parts.Length == 0) |
||||||
|
throw new ArgumentException("Number of parts must not be zero"); |
||||||
|
this.context = context; |
||||||
|
this.parts = new List<IUnresolvedTypeDefinition>(parts); |
||||||
|
MovePrimaryPartToFront(); |
||||||
|
this.fullTypeName = parts[0].FullTypeName; |
||||||
|
} |
||||||
|
|
||||||
|
void MovePrimaryPartToFront() |
||||||
|
{ |
||||||
|
int bestPartIndex = 0; |
||||||
|
for (int i = 1; i < parts.Count; i++) { |
||||||
|
if (context.IsBetterPart(parts[i], parts[bestPartIndex])) |
||||||
|
bestPartIndex = i; |
||||||
|
} |
||||||
|
IUnresolvedTypeDefinition primaryPart = parts[bestPartIndex]; |
||||||
|
for (int i = bestPartIndex; i > 0; i--) { |
||||||
|
parts[i] = parts[i - 1]; |
||||||
|
} |
||||||
|
parts[0] = primaryPart; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
/// <summary>
|
||||||
|
/// Updates the type definition model by removing the old parts and adding the new ones.
|
||||||
|
/// </summary>
|
||||||
|
public void Update(IReadOnlyList<IUnresolvedTypeDefinition> removedParts, IReadOnlyList<IUnresolvedTypeDefinition> newParts) |
||||||
|
{ |
||||||
|
SD.MainThread.VerifyAccess(); |
||||||
|
if (removedParts != null) |
||||||
|
foreach (var p in removedParts) |
||||||
|
parts.Remove(p); |
||||||
|
if (newParts != null) |
||||||
|
parts.AddRange(newParts); |
||||||
|
|
||||||
|
MemberModelCollection members; |
||||||
|
if (membersWeakReference.TryGetTarget(out members)) { |
||||||
|
members.Update(parts); |
||||||
|
} |
||||||
|
}*/ |
||||||
|
|
||||||
|
public IProject ParentProject { |
||||||
|
get { return context.Project; } |
||||||
|
} |
||||||
|
|
||||||
|
public FullTypeName FullTypeName { |
||||||
|
get { return fullTypeName; } |
||||||
|
} |
||||||
|
|
||||||
|
public DomRegion Region { |
||||||
|
get { return parts[0].Region; } |
||||||
|
} |
||||||
|
|
||||||
|
public string Name { |
||||||
|
get { return fullTypeName.Name; } |
||||||
|
} |
||||||
|
|
||||||
|
#region Resolve
|
||||||
|
public ITypeDefinition Resolve() |
||||||
|
{ |
||||||
|
var compilation = context.GetCompilation(null); |
||||||
|
return compilation.MainAssembly.GetTypeDefinition(fullTypeName); |
||||||
|
} |
||||||
|
|
||||||
|
public ITypeDefinition Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||||
|
{ |
||||||
|
var compilation = context.GetCompilation(solutionSnapshot); |
||||||
|
return compilation.MainAssembly.GetTypeDefinition(fullTypeName); |
||||||
|
} |
||||||
|
|
||||||
|
IEntity IEntityModel.Resolve() |
||||||
|
{ |
||||||
|
return Resolve(); |
||||||
|
} |
||||||
|
|
||||||
|
IEntity IEntityModel.Resolve(ISolutionSnapshotWithProjectMapping solutionSnapshot) |
||||||
|
{ |
||||||
|
return Resolve(solutionSnapshot); |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged { add {} remove {} } |
||||||
|
|
||||||
|
#region Members collection
|
||||||
|
sealed class MemberCollection : IModelCollection<MemberModel> |
||||||
|
{ |
||||||
|
readonly TypeDefinitionModel parent; |
||||||
|
List<List<MemberModel>> lists = new List<List<MemberModel>>(); |
||||||
|
|
||||||
|
public MemberCollection(TypeDefinitionModel parent) |
||||||
|
{ |
||||||
|
this.parent = parent; |
||||||
|
} |
||||||
|
|
||||||
|
public void Insert(int partIndex, IUnresolvedTypeDefinition newPart) |
||||||
|
{ |
||||||
|
List<MemberModel> newItems = new List<MemberModel>(newPart.Members.Count); |
||||||
|
foreach (var newMember in newPart.Members) { |
||||||
|
newItems.Add(new MemberModel(parent.context, newMember) { strongParentCollectionReference = this }); |
||||||
|
} |
||||||
|
lists.Insert(partIndex, newItems); |
||||||
|
if (collectionChanged != null) |
||||||
|
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, GetCount(partIndex))); |
||||||
|
} |
||||||
|
|
||||||
|
public void Remove(int partIndex) |
||||||
|
{ |
||||||
|
var oldItems = lists[partIndex]; |
||||||
|
lists.RemoveAt(partIndex); |
||||||
|
if (collectionChanged != null) |
||||||
|
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, GetCount(partIndex))); |
||||||
|
} |
||||||
|
|
||||||
|
public void Update(int partIndex, IUnresolvedTypeDefinition newPart) |
||||||
|
{ |
||||||
|
List<MemberModel> list = lists[partIndex]; |
||||||
|
var newMembers = newPart.Members; |
||||||
|
int startPos = 0; |
||||||
|
// Look at the initial members and update them if they're matching
|
||||||
|
while (startPos < list.Count && startPos < newMembers.Count && IsMatch(list[startPos], newMembers[startPos])) { |
||||||
|
list[startPos].Update(newMembers[startPos]); |
||||||
|
startPos++; |
||||||
|
} |
||||||
|
// Look at the final members
|
||||||
|
int endPosOld = list.Count - 1; |
||||||
|
int endPosNew = newMembers.Count - 1; |
||||||
|
while (endPosOld >= startPos && endPosNew >= startPos && IsMatch(list[endPosOld], newMembers[endPosNew])) { |
||||||
|
list[endPosOld--].Update(newMembers[endPosNew--]); |
||||||
|
} |
||||||
|
// [startPos, endPos] is the middle portion that contains all the changes
|
||||||
|
// Add one to endPos so that it's the exclusive end of the middle portion:
|
||||||
|
endPosOld++; |
||||||
|
endPosNew++; |
||||||
|
// [startPos, endPos)
|
||||||
|
|
||||||
|
// Now we still need to update the members in between.
|
||||||
|
// We might try to be clever here and find a LCS so that we only update the members that were actually changed,
|
||||||
|
// or we might consider moving members around (INotifyCollectionChanged supports moves)
|
||||||
|
// However, the easiest solution by far is to just remove + readd the whole middle portion.
|
||||||
|
var oldItems = collectionChanged != null ? list.GetRange(startPos, endPosOld - startPos) : null; |
||||||
|
list.RemoveRange(startPos, endPosOld - startPos); |
||||||
|
var newItems = new MemberModel[endPosNew - startPos]; |
||||||
|
for (int i = 0; i < newItems.Length; i++) { |
||||||
|
newItems[i] = new MemberModel(parent.context, newMembers[startPos + i]); |
||||||
|
newItems[i].strongParentCollectionReference = this; |
||||||
|
} |
||||||
|
list.InsertRange(startPos, newItems); |
||||||
|
if (collectionChanged != null) |
||||||
|
collectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, GetCount(partIndex) + startPos)); |
||||||
|
} |
||||||
|
|
||||||
|
static bool IsMatch(MemberModel memberModel, IUnresolvedMember newMember) |
||||||
|
{ |
||||||
|
return memberModel.EntityType == newMember.EntityType && memberModel.Name == newMember.Name; |
||||||
|
} |
||||||
|
|
||||||
|
NotifyCollectionChangedEventHandler collectionChanged; |
||||||
|
|
||||||
|
public event NotifyCollectionChangedEventHandler CollectionChanged { |
||||||
|
add { |
||||||
|
collectionChanged += value; |
||||||
|
// Set strong reference to collection while there are event listeners
|
||||||
|
if (collectionChanged != null) |
||||||
|
parent.membersStrongReference = this; |
||||||
|
} |
||||||
|
remove { |
||||||
|
collectionChanged -= value; |
||||||
|
if (collectionChanged == null) |
||||||
|
parent.membersStrongReference = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
int GetCount(int partIndex) |
||||||
|
{ |
||||||
|
int count = 0; |
||||||
|
for (int i = 0; i < partIndex; i++) { |
||||||
|
count += lists[i].Count; |
||||||
|
} |
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
public int Count { |
||||||
|
get { return GetCount(lists.Count); } |
||||||
|
} |
||||||
|
|
||||||
|
public IEnumerator<MemberModel> GetEnumerator() |
||||||
|
{ |
||||||
|
return lists.SelectMany(i => i).GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() |
||||||
|
{ |
||||||
|
return GetEnumerator(); |
||||||
|
} |
||||||
|
|
||||||
|
public MemberModel this[int index] { |
||||||
|
get { |
||||||
|
int inputIndex = 0; |
||||||
|
while (index >= lists[inputIndex].Count) { |
||||||
|
index -= lists[inputIndex].Count; |
||||||
|
inputIndex++; |
||||||
|
} |
||||||
|
return lists[inputIndex][index]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
WeakReference<MemberCollection> membersWeakReference = new WeakReference<MemberCollection>(null); |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to keep the member model collection alive while there are event listeners.
|
||||||
|
/// This is necessary to prevent us from creating multiple member models for the same member.
|
||||||
|
/// </summary>
|
||||||
|
MemberCollection membersStrongReference; |
||||||
|
|
||||||
|
public IModelCollection<IMemberModel> Members { |
||||||
|
get { |
||||||
|
SD.MainThread.VerifyAccess(); |
||||||
|
MemberCollection members; |
||||||
|
if (!membersWeakReference.TryGetTarget(out members)) { |
||||||
|
members = new MemberCollection(this); |
||||||
|
membersWeakReference.SetTarget(members); |
||||||
|
} |
||||||
|
return members; |
||||||
|
} |
||||||
|
} |
||||||
|
#endregion
|
||||||
|
|
||||||
|
public IModelCollection<ITypeDefinitionModel> NestedTypes { |
||||||
|
get { |
||||||
|
throw new NotImplementedException(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue