//---------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Metadata.Edm
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common.Utils;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
///
/// Class for representing a collection of items.
/// Most of the implemetation for actual maintainance of the collection is
/// done by MetadataCollection
///
[CLSCompliant(false)]
public abstract class ItemCollection : ReadOnlyMetadataCollection
{
#region Constructors
///
/// The default constructor for ItemCollection
///
internal ItemCollection(DataSpace dataspace)
: base(new MetadataCollection())
{
_space = dataspace;
}
#endregion
#region Fields
private readonly DataSpace _space;
private Dictionary> _functionLookUpTable;
private Memoizer _itemsCache;
private int _itemCount;
#endregion
#region Properties
///
/// Dataspace associated with ItemCollection
///
public DataSpace DataSpace
{
get
{
return this._space;
}
}
///
/// Return the function lookUpTable
///
internal Dictionary> FunctionLookUpTable
{
get
{
if (_functionLookUpTable == null)
{
Dictionary> functionLookUpTable = PopulateFunctionLookUpTable(this);
Interlocked.CompareExchange(ref _functionLookUpTable, functionLookUpTable, null);
}
return _functionLookUpTable;
}
}
#endregion
#region Methods
///
/// Adds an item to the collection
///
/// The item to add to the list
/// Thrown if item argument is null
/// Thrown if the item passed in or the collection itself instance is in ReadOnly state
/// Thrown if the item that is being added already belongs to another ItemCollection
/// Thrown if the ItemCollection already contains an item with the same identity
internal void AddInternal(GlobalItem item)
{
Debug.Assert(item.IsReadOnly, "The item is not readonly, it should be by the time it is added to the item collection");
Debug.Assert(item.DataSpace == this.DataSpace);
base.Source.Add(item);
}
///
/// Adds a collection of items to the collection
///
/// The items to add to the list
/// Thrown if item argument is null
/// Thrown if the item passed in or the collection itself instance is in ReadOnly state
/// Thrown if the item that is being added already belongs to another ItemCollection
/// Thrown if the ItemCollection already contains an item with the same identity
internal bool AtomicAddRange(List items)
{
#if DEBUG
// We failed to add, so undo the setting of the ItemCollection reference
foreach (GlobalItem item in items)
{
Debug.Assert(item.IsReadOnly, "The item is not readonly, it should be by the time it is added to the item collection");
Debug.Assert(item.DataSpace == this.DataSpace);
}
#endif
if (base.Source.AtomicAddRange(items))
{
return true;
}
return false;
}
///
/// Returns strongly typed MetadataItem from the collection that has
/// the passed in identity.
///
///
/// Identity of the item to look up for
/// returns the item if a match is found, otherwise throwns an exception
/// Thrown if identity argument passed in is null
/// Thrown if the Collection does not have an item with the given identity
public T GetItem(string identity) where T : GlobalItem
{
return this.GetItem(identity, false /*ignoreCase*/);
}
///
/// Returns strongly typed MetadataItem from the collection that has
/// the passed in identity.
/// Returns null if the item is not found.
///
///
///
///
///
/// if identity argument is null
public bool TryGetItem(string identity, out T item) where T : GlobalItem
{
return this.TryGetItem(identity, false /*ignorecase*/, out item);
}
///
/// Returns strongly typed MetadataItem from the collection that has
/// the passed in identity.
/// Returns null if the item is not found.
///
///
/// identity of the type to look up for
/// true for case-insensitive lookup
/// item with the given identity if a match is found, otherwise returns null
/// returns true if a match is found, otherwise returns false
/// if identity argument is null
public bool TryGetItem(string identity, bool ignoreCase, out T item) where T : GlobalItem
{
GlobalItem outItem = null;
TryGetValue(identity, ignoreCase, out outItem);
item = outItem as T;
return item != null;
}
///
/// Returns strongly typed MetadataItem from the collection that has
/// the passed in identity with either case sensitive or case insensitive search
///
///
/// identity of the type to look up for
/// true for case-insensitive lookup
/// returns item if a match is found, otherwise returns throws an argument exception
/// Thrown if identity argument passed in is null
/// Thrown if no item is found with the given identity
public T GetItem(string identity, bool ignoreCase) where T : GlobalItem
{
T item;
if (TryGetItem(identity, ignoreCase, out item))
{
return item;
}
throw EntityUtil.ItemInvalidIdentity(identity, "identity");
}
///
/// Returns ReadOnlyCollection of the Items of the given type
/// in the item collection.
///
///
///
public virtual System.Collections.ObjectModel.ReadOnlyCollection GetItems() where T : GlobalItem
{
Memoizer currentValueForItemCache = _itemsCache;
// initialize the memoizer, update the _itemCache and _itemCount
if (_itemsCache == null || this._itemCount != this.Count)
{
Memoizer itemsCache =
new Memoizer(InternalGetItems, null);
Interlocked.CompareExchange(ref _itemsCache, itemsCache, currentValueForItemCache);
this._itemCount = this.Count;
}
Debug.Assert(_itemsCache != null, "check the initialization of the Memoizer");
// use memoizer so that it won't create a new list every time this method get called
ICollection items = this._itemsCache.Evaluate(typeof(T));
System.Collections.ObjectModel.ReadOnlyCollection returnItems = items as System.Collections.ObjectModel.ReadOnlyCollection;
return returnItems;
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
internal ICollection InternalGetItems(Type type)
{
MethodInfo mi = typeof(ItemCollection).GetMethod("GenericGetItems", BindingFlags.NonPublic | BindingFlags.Static);
MethodInfo genericMi = mi.MakeGenericMethod(type);
return genericMi.Invoke(null, new object[] { this }) as ICollection;
}
private static System.Collections.ObjectModel.ReadOnlyCollection GenericGetItems(ItemCollection collection) where TItem : GlobalItem
{
List list = new List();
foreach (GlobalItem item in collection)
{
TItem stronglyTypedItem = item as TItem;
if (stronglyTypedItem != null)
{
list.Add(stronglyTypedItem);
}
}
return list.AsReadOnly();
}
///
/// Search for a type metadata with the specified name and namespace name in the given space.
///
/// name of the type
/// namespace of the type
/// Returns null if no match found.
/// Thrown if name or namespaceName arguments passed in are null
/// Thrown if the ItemCollection for this space does not have a type with the given name and namespaceName
public EdmType GetType(string name, string namespaceName)
{
return this.GetType(name, namespaceName, false /*ignoreCase*/);
}
///
/// Search for a type metadata with the specified name and namespace name in the given space.
///
/// name of the type
/// namespace of the type
/// The type that needs to be filled with the return value
/// Returns null if no match found.
/// if name or namespaceName argument is null
public bool TryGetType(string name, string namespaceName, out EdmType type)
{
return this.TryGetType(name, namespaceName, false /*ignoreCase*/, out type);
}
///
/// Search for a type metadata with the specified key.
///
/// name of the type
/// namespace of the type
/// true for case-insensitive lookup
/// Returns null if no match found.
/// Thrown if name or namespaceName arguments passed in are null
public EdmType GetType(string name, string namespaceName, bool ignoreCase)
{
EntityUtil.GenericCheckArgumentNull(name, "name");
EntityUtil.GenericCheckArgumentNull(namespaceName, "namespaceName");
return GetItem(EdmType.CreateEdmTypeIdentity(namespaceName, name), ignoreCase);
}
///
/// Search for a type metadata with the specified name and namespace name in the given space.
///
/// name of the type
/// namespace of the type
/// true for case-insensitive lookup
/// The type that needs to be filled with the return value
/// Returns null if no match found.
/// if name or namespaceName argument is null
public bool TryGetType(string name, string namespaceName, bool ignoreCase, out EdmType type)
{
EntityUtil.GenericCheckArgumentNull(name, "name");
EntityUtil.GenericCheckArgumentNull(namespaceName, "namespaceName");
GlobalItem item = null;
TryGetValue(EdmType.CreateEdmTypeIdentity(namespaceName, name), ignoreCase, out item);
type = item as EdmType;
return type != null;
}
///
/// Get all the overloads of the function with the given name
///
/// The full name of the function
/// A collection of all the functions with the given name in the given data space
/// Thrown if functionaName argument passed in is null
public System.Collections.ObjectModel.ReadOnlyCollection GetFunctions(string functionName)
{
return this.GetFunctions(functionName, false /*ignoreCase*/);
}
///
/// Get all the overloads of the function with the given name
///
/// The full name of the function
/// true for case-insensitive lookup
/// A collection of all the functions with the given name in the given data space
/// Thrown if functionaName argument passed in is null
public System.Collections.ObjectModel.ReadOnlyCollection GetFunctions(string functionName, bool ignoreCase)
{
return GetFunctions(this.FunctionLookUpTable, functionName, ignoreCase);
}
///
/// Look for the functions in the given collection and
/// returns all the functions with the given name
///
///
///
///
///
protected static System.Collections.ObjectModel.ReadOnlyCollection GetFunctions(
Dictionary> functionCollection,
string functionName, bool ignoreCase)
{
System.Collections.ObjectModel.ReadOnlyCollection functionOverloads;
if (functionCollection.TryGetValue(functionName, out functionOverloads))
{
if (ignoreCase)
{
return functionOverloads;
}
return GetCaseSensitiveFunctions(functionOverloads, functionName);
}
return Helper.EmptyEdmFunctionReadOnlyCollection;
}
internal static System.Collections.ObjectModel.ReadOnlyCollection GetCaseSensitiveFunctions(
System.Collections.ObjectModel.ReadOnlyCollection functionOverloads,
string functionName)
{
// For case-sensitive match, first check if there are anything with a different case
// its very rare to have functions with different case. So optimizing the case where all
// functions are of same case
// Else create a new list with the functions with the exact name
List caseSensitiveFunctionOverloads = new List(functionOverloads.Count);
for (int i = 0; i < functionOverloads.Count; i++)
{
if (functionOverloads[i].FullName == functionName)
{
caseSensitiveFunctionOverloads.Add(functionOverloads[i]);
}
}
// If there are no functions with different case, just return the collection
if (caseSensitiveFunctionOverloads.Count != functionOverloads.Count)
{
functionOverloads = caseSensitiveFunctionOverloads.AsReadOnly();
}
return functionOverloads;
}
///
/// Gets the function as specified by the function key.
/// All parameters are assumed to be .
///
/// Name of the function
/// types of the parameters
/// true for case-insensitive lookup
/// The function that needs to be returned
/// The function as specified in the function key or null
/// if functionName or parameterTypes argument is null
/// if no function is found with the given name or with given input parameters
internal bool TryGetFunction(string functionName, TypeUsage[] parameterTypes, bool ignoreCase, out EdmFunction function)
{
EntityUtil.GenericCheckArgumentNull(functionName, "functionName");
EntityUtil.GenericCheckArgumentNull(parameterTypes, "parameterTypes");
string functionIdentity = EdmFunction.BuildIdentity(functionName, parameterTypes);
GlobalItem item = null;
function = null;
if (TryGetValue(functionIdentity, ignoreCase, out item) && Helper.IsEdmFunction(item))
{
function = (EdmFunction)item;
return true;
}
return false;
}
///
/// Get an entity container based upon the strong name of the container
/// If no entity container is found, returns null, else returns the first one///
/// name of the entity container
/// The EntityContainer
/// Thrown if name argument passed in is null
public EntityContainer GetEntityContainer(string name)
{
EntityUtil.GenericCheckArgumentNull(name, "name");
return this.GetEntityContainer(name, false /*ignoreCase*/);
}
///
/// Get an entity container based upon the strong name of the container
/// If no entity container is found, returns null, else returns the first one///
/// name of the entity container
///
/// if name argument is null
public bool TryGetEntityContainer(string name, out EntityContainer entityContainer)
{
EntityUtil.GenericCheckArgumentNull(name, "name");
return this.TryGetEntityContainer(name, false /*ignoreCase*/, out entityContainer);
}
///
/// Get an entity container based upon the strong name of the container
/// If no entity container is found, returns null, else returns the first one///
/// name of the entity container
/// true for case-insensitive lookup
/// The EntityContainer
/// Thrown if name argument passed in is null
/// Thrown if no entity container with the given name is found
public EntityContainer GetEntityContainer(string name, bool ignoreCase)
{
EntityContainer container = GetValue(name, ignoreCase) as EntityContainer;
if (null != container)
{
return container;
}
throw EntityUtil.ItemInvalidIdentity(name, "name");
}
///
/// Get an entity container based upon the strong name of the container
/// If no entity container is found, returns null, else returns the first one///
/// name of the entity container
/// true for case-insensitive lookup
///
/// if name argument is null
public bool TryGetEntityContainer(string name, bool ignoreCase, out EntityContainer entityContainer)
{
EntityUtil.GenericCheckArgumentNull(name, "name");
GlobalItem item = null;
if (TryGetValue(name, ignoreCase, out item) && Helper.IsEntityContainer(item))
{
entityContainer = (EntityContainer)item;
return true;
}
entityContainer = null;
return false;
}
///
/// Given the canonical primitive type, get the mapping primitive type in the given dataspace
///
/// canonical primitive type
/// The mapped scalar type
internal virtual PrimitiveType GetMappedPrimitiveType(PrimitiveTypeKind primitiveTypeKind)
{
//The method needs to be overloaded on methods that support this
throw System.Data.Entity.Error.NotSupported();
}
///
/// Determines whether this item collection is equivalent to another. At present, we look only
/// at object reference equivalence. This is a somewhat reasonable approximation when caching
/// is enabled, because collections are identical when their source resources (including
/// provider) are known to be identical.
///
/// Collection to compare.
/// true if the collections are equivalent; false otherwise
internal virtual bool MetadataEquals(ItemCollection other)
{
return Object.ReferenceEquals(this, other);
}
static private Dictionary> PopulateFunctionLookUpTable(ItemCollection itemCollection)
{
var tempFunctionLookUpTable = new Dictionary>(StringComparer.OrdinalIgnoreCase);
foreach (EdmFunction function in itemCollection.GetItems())
{
List functionList;
if (!tempFunctionLookUpTable.TryGetValue(function.FullName, out functionList))
{
functionList = new List();
tempFunctionLookUpTable[function.FullName] = functionList;
}
functionList.Add(function);
}
var functionLookUpTable = new Dictionary>(StringComparer.OrdinalIgnoreCase);
foreach (List functionList in tempFunctionLookUpTable.Values)
{
functionLookUpTable.Add(functionList[0].FullName, new System.Collections.ObjectModel.ReadOnlyCollection(functionList.ToArray()));
}
return functionLookUpTable;
}
#endregion
}//---- ItemCollection
}//----