Code: 
                         / 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / DataWeb / Server / System / Data / Services / Serializers / Serializer.cs / 1305376 / Serializer.cs
                        
                        
                            //---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
//  
//  
//      An abstract base serializer for various serializers
//   
// 
// @owner  [....]
//--------------------------------------------------------------------- 
namespace System.Data.Services.Serializers
{
    #region Namespaces. 
    using System; 
    using System.Collections; 
    using System.Collections.Generic;
    using System.Data.Services; 
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Text;
    using System.Data.Services.Internal; 
    #endregion Namespaces. 
 
    /// Abstract base class for all serializers. 
    internal abstract class Serializer : IExceptionWriter 
    {
        #region Private fields.
        /// Maximum recursion limit on serializers.  
        private const int RecursionLimit = 100;
 
        ///  
        /// These query parameters can be copied for each next page link.
        /// Don't need to copy $skiptoken, $skip and $top because they are calculated every time. 
        ///  
        private static readonly String[] NextPageQueryParametersToCopy =
        {
            XmlConstants.HttpQueryStringFilter, 
            XmlConstants.HttpQueryStringExpand,
            XmlConstants.HttpQueryStringOrderBy, 
            XmlConstants.HttpQueryStringInlineCount, 
            XmlConstants.HttpQueryStringSelect
        }; 
        /// Base URI from which resources should be resolved. 
        private readonly Uri absoluteServiceUri;
 
        /// Data provider from which metadata should be gathered. 
        private readonly string httpETagHeaderValue; 
 
        /// Data provider from which metadata should be gathered. 
        private readonly IDataService service; 
        /// Description for the requested results. 
        private readonly RequestDescription requestDescription;
 
        /// Collection of complex types, used for cycle detection. 
        private HashSet complexTypeCollection; 
 
        /// Resolved segment containers. 
        private List segmentContainers; 
        /// Segment names. 
        private List segmentNames;
 
        /// Result counts for segments. 
        private List segmentResultCounts; 
 
        /// Depth of recursion. 
        private int recursionDepth; 
        /// Current skip token object for custom paging. 
        private object[] currentSkipTokenForCustomPaging;
 
        #endregion Private fields.
 
        /// Initializes a new base Serializer, ready to write out a description.  
        /// Container for the resource being serialized (possibly null).  
        protected ResourceSetWrapper CurrentContainer 
        {
            get 
            {
                if (this.segmentContainers == null || this.segmentContainers.Count == 0)
                {
                    return this.requestDescription.LastSegmentInfo.TargetContainer; 
                }
                else 
                { 
                    return this.segmentContainers[this.segmentContainers.Count - 1];
                } 
            }
        }
        /// Is current container the root container.  
        protected bool IsRootContainer
        { 
            get 
            {
                return (this.segmentContainers == null || this.segmentContainers.Count == 1); 
            }
        }
        ///  
        /// Gets the Data provider from which metadata should be gathered.
        ///   
        protected DataServiceProviderWrapper Provider 
        {
            [DebuggerStepThrough] 
            get { return this.service.Provider; }
        }
        ///  
        /// Gets the Data service from which metadata should be gathered.
        ///   
        protected IDataService Service 
        {
            [DebuggerStepThrough] 
            get { return this.service; }
        }
        /// Gets the absolute URI to the service.  
        protected Uri AbsoluteServiceUri
        { 
            [DebuggerStepThrough] 
            get { return this.absoluteServiceUri; }
        } 
        /// 
        /// Gets the RequestDescription for the request that is getting serialized.
        ///   
        protected RequestDescription RequestDescription
        { 
            [DebuggerStepThrough] 
            get
            { 
                return this.requestDescription;
            }
        }
 
        /// Are we using custom paging? 
        protected bool IsCustomPaged 
        { 
            get
            { 
                return this.service.PagingProvider.IsCustomPagedForSerialization;
            }
        }
 
        /// Serializes exception information. 
        ///  
        /// Gets the uri given the list of resource properties. This logic must be the same across all
        /// serializers. Hence putting this in a util class
        ///  
        /// uri for the given resource 
        internal static Uri GetUri(object resource, DataServiceProviderWrapper provider, ResourceSetWrapper container, Uri absoluteServiceUri) 
        {
            Debug.Assert(container != null, "container != null");
            string objectKey = GetObjectKey(resource, provider, container.Name);
            return RequestUriProcessor.AppendEscapedSegment(absoluteServiceUri, objectKey); 
        }
 
        ///  
        /// Appends the given entry to the given uri
        ///   
        /// new uri with the entry appended to the given uri 
        internal static Uri AppendEntryToUri(Uri currentUri, string entry) 
        {
            return RequestUriProcessor.AppendUnescapedSegment(currentUri, entry); 
        } 
        ///  
        /// Handles the complete serialization for the specified  
        /// 
        ///  
        internal void WriteRequest(IEnumerator queryResults, bool hasMoved)
        { 
            Debug.Assert(this.requestDescription.RequestEnumerable != null, "this.requestDescription.RequestEnumerable != null");
            Debug.Assert(queryResults != null, "queryResults != null"); 
            IExpandedResult expanded = queryResults as IExpandedResult; 
            if (this.requestDescription.LinkUri) 
            {
                bool needPop = this.PushSegmentForRoot();
                if (this.requestDescription.IsSingleResult)
                { 
                    this.WriteLink(queryResults.Current);
                    if (queryResults.MoveNext()) 
                    { 
                        throw new InvalidOperationException(Strings.SingleResourceExpected);
                    } 
                }
                else
                {
                    this.WriteLinkCollection(queryResults, hasMoved); 
                }
 
                this.PopSegmentName(needPop); 
            }
            else if (this.requestDescription.IsSingleResult) 
            {
                Debug.Assert(hasMoved == true, "hasMoved == true");
                this.WriteTopLevelElement(expanded, queryResults.Current);
                if (queryResults.MoveNext()) 
                {
                    throw new InvalidOperationException(Strings.SingleResourceExpected); 
                } 
            }
            else 
            {
                this.WriteTopLevelElements(expanded, queryResults, hasMoved);
            }
 
            this.Flush();
        } 
 
        /// Gets the expandable value for the specified object. 
        /// The property value.  
        protected static object GetExpandedProperty(DataServiceProviderWrapper provider, IExpandedResult expanded, object customObject, ResourceProperty property)
        { 
            Debug.Assert(property != null, "property != null"); 
            if (expanded == null)
            { 
                return WebUtil.GetPropertyValue(provider, customObject, null, property, null);
            }
            else
            { 
                // We may end up projecting null as a value of ResourceSetReference property. This can in theory break
                //   the serializers as they expect a non-null (possibly empty) IEnumerable instead. But note that 
                //   if we project null into the expanded property, we also project null into the ExpandedElement property 
                //   and thus the serializers should recognize this value as null and don't try to expand its properties.
                Debug.Assert( 
                    expanded.ExpandedElement != null,
                    "We should not be accessing expanded properties on null resource.");
                return expanded.GetExpandedPropertyValue(property.Name);
            } 
        }
 
        /// Gets the expanded element for the specified expanded result.  
        /// The expanded element.  
        protected static object GetExpandedElement(IExpandedResult expanded)
        {
            Debug.Assert(expanded != null, "expanded != null");
            return expanded.ExpandedElement; 
        }
 
        /// Flushes the writer to the underlying stream.  
        protected abstract void Flush();
 
        /// Writes a single top-level element. 
        /// Writes multiple top-level elements, possibly none.  
        /// 
        /// Write out the entry count 
        ///  
        protected abstract void WriteRowCount(); 
 
        /// 
        /// Write out the uri for the given element 
        ///  
        /// 
        /// Write out the uri for the given elements 
        ///   
        /// 
        /// Adds the given object instance to complex type collection 
        ///  
        /// true, if it got added successfully  
        protected bool AddToComplexTypeCollection(object complexTypeInstance)
        { 
            if (this.complexTypeCollection == null)
            {
                this.complexTypeCollection = new HashSet(ReferenceEqualityComparer.Instance);
            } 
            return this.complexTypeCollection.Add(complexTypeInstance); 
        } 
        ///  
        /// Gets the skip token object contained in the expanded result for standard paging.
        ///  
        /// Skip token object if any.  
        protected IExpandedResult GetSkipToken(IExpandedResult expanded)
        { 
            if (expanded != null && !this.IsCustomPaged && !this.RequestDescription.IsRequestForEnumServiceOperation) 
            {
                return expanded.GetExpandedPropertyValue(XmlConstants.HttpQueryStringSkipToken) as IExpandedResult; 
            }
            return null;
        } 
        ///  
        /// Obtains the URI for the link for next page in string format 
        ///  
        /// URI for the link for next page 
        protected String GetNextLinkUri(object lastObject, IExpandedResult skipTokenExpandedResult, Uri absoluteUri) 
        {
            UriBuilder builder = new UriBuilder(absoluteUri); 
            SkipTokenBuilder skipTokenBuilder = null; 
            if (this.IsRootContainer) 
            {
                if (!this.IsCustomPaged)
                {
                    if (skipTokenExpandedResult != null) 
                    {
                        skipTokenBuilder = new SkipTokenBuilderFromExpandedResult(skipTokenExpandedResult, this.RequestDescription.SkipTokenExpressionCount); 
                    } 
                    else
                    { 
                        Debug.Assert(this.RequestDescription.SkipTokenProperties != null, "Must have skip token properties collection");
                        Debug.Assert(this.RequestDescription.SkipTokenProperties.Count > 0, "Must have some valid ordered properties in the skip token properties collection");
                        skipTokenBuilder = new SkipTokenBuilderFromProperties(lastObject, this.Provider, this.RequestDescription.SkipTokenProperties);
                    } 
                }
                else 
                { 
                    Debug.Assert(this.currentSkipTokenForCustomPaging != null, "Must have obtained the skip token for custom paging.");
                    skipTokenBuilder = new SkipTokenBuilderFromCustomPaging(this.currentSkipTokenForCustomPaging); 
                }
                builder.Query = this.GetNextPageQueryParametersForRootContainer().Append(skipTokenBuilder.GetSkipToken()).ToString();
            } 
            else
            { 
                if (!this.IsCustomPaged) 
                {
                    // Internal results 
                    skipTokenBuilder = new SkipTokenBuilderFromProperties(lastObject, this.Provider, this.CurrentContainer.ResourceType.KeyProperties);
                }
                else
                { 
                    Debug.Assert(this.currentSkipTokenForCustomPaging != null, "Must have obtained the skip token for custom paging.");
                    skipTokenBuilder = new SkipTokenBuilderFromCustomPaging(this.currentSkipTokenForCustomPaging); 
                } 
                builder.Query = this.GetNextPageQueryParametersForExpandedContainer().Append(skipTokenBuilder.GetSkipToken()).ToString(); 
            }
            return builder.Uri.AbsoluteUri;
        } 
        /// Is next page link needs to be appended to the feed  
        /// true if the feed must have a next page link 
        protected bool NeedNextPageLink(IEnumerator enumerator) 
        {
            // For open types, current container could be null
            if (this.CurrentContainer != null && !this.RequestDescription.IsRequestForEnumServiceOperation)
            { 
                if (this.IsCustomPaged)
                { 
                    this.currentSkipTokenForCustomPaging = 
                        this.service.PagingProvider.PagingProviderInterface.GetContinuationToken(BasicExpandProvider.ExpandedEnumerator.UnwrapEnumerator(enumerator));
                    Debug.Assert( 
                            this.RequestDescription.ResponseVersion != RequestDescription.DataServiceDefaultResponseVersion,
                            "If custom paging is enabled, our response should be 2.0 and beyond.");
                    return this.currentSkipTokenForCustomPaging != null && this.currentSkipTokenForCustomPaging.Length > 0; 
                }
                else 
                { 
                    int pageSize = this.CurrentContainer.PageSize;
 
                    if (pageSize != 0 && this.RequestDescription.ResponseVersion != RequestDescription.DataServiceDefaultResponseVersion)
                    {
                        // For the root segment, if the $top parameter value is less than or equal to page size then we
                        // don't need to send the next page link. 
                        if (this.segmentResultCounts.Count == 1)
                        { 
                            int? topQueryParameter = this.GetTopQueryParameter(); 
                            if (topQueryParameter.HasValue) 
                            {
                                Debug.Assert(topQueryParameter.Value >= this.segmentResultCounts[this.segmentResultCounts.Count - 1], "$top must be the upper limits of the number of results returned.");
                                if (topQueryParameter.Value <= pageSize)
                                { 
                                    return false;
                                } 
                            } 
                        }
 
                        return this.segmentResultCounts[this.segmentResultCounts.Count - 1] == pageSize;
                    }
                }
            } 
            return false; 
        } 
        /// Pushes a segment for the root of the tree being written out.  
        /// Calls to this method should be balanced with calls to PopSegmentName. 
        /// true if segment was pushed, false otherwise  
        protected bool PushSegmentForOpenProperty(string propertyName, ResourceType propertyResourceType)
        { 
            Debug.Assert(propertyName != null, "propertyName != null"); 
            Debug.Assert(propertyResourceType != null, "propertyResourceType != null");
            ResourceSetWrapper container = null; 
            if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
            {
                // Open navigation properties are not supported on OpenTypes.
                throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName)); 
            }
 
            return this.PushSegment(propertyName, container); 
        }
 
        /// Increments the result count for the current segment, throws if exceeds the limit. 
        protected void IncrementSegmentResultCount()
        {
            if (this.segmentResultCounts != null) 
            {
                Debug.Assert(this.segmentResultCounts.Count > 0, "this.segmentResultCounts.Count > 0 -- otherwise we didn't PushSegmentForRoot"); 
                int max = this.service.Configuration.MaxResultsPerCollection; 
                if (!this.IsCustomPaged) 
                {
                    // For Open types, current container could be null, even though MaxResultsPerCollection has been set
                    // set we need to check for container before we try to assume page size, also open types do not have
                    // page sizes so we can safely ignore this check here 
                    if (this.CurrentContainer != null && this.CurrentContainer.PageSize != 0)
                    { 
                        Debug.Assert(max == Int32.MaxValue, "Either page size or max result count can be set, but not both"); 
                        max = this.CurrentContainer.PageSize;
                    } 
                }
                if (max != Int32.MaxValue)
                { 
                    int count = this.segmentResultCounts[this.segmentResultCounts.Count - 1];
                    checked 
                    { 
                        count++;
                    } 
                    if (count > max)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.Serializer_ResultsExceedMax(max)); 
                    }
 
                    this.segmentResultCounts[this.segmentResultCounts.Count - 1] = count; 
                }
            } 
        }
        /// Pushes a segment from the stack of names being written. 
        /// Calls to this method should be balanced with calls to PopSegmentName. 
        /// true if a segment was pushed, false otherwise  
        protected bool PushSegmentForProperty(ResourceProperty property) 
        {
            Debug.Assert(property != null, "property != null"); 
            ResourceSetWrapper current = null;
            if ((property.Kind & (ResourcePropertyKind.ResourceReference | ResourcePropertyKind.ResourceSetReference)) != 0)
            {
                current = this.CurrentContainer; 
                if (current != null)
                { 
                    current = this.service.Provider.GetContainer(current, current.ResourceType, property); 
                }
            } 
            return this.PushSegment(property.Name, current);
        }
 
        /// Pushes a segment for the root of the tree being written out. 
        /// Calls to this method should be balanced with calls to PopSegmentName.  
        /// true if the segment was pushed, false otherwise  
        protected bool PushSegmentForRoot()
        { 
            return this.PushSegment(this.RequestDescription.ContainerName, this.CurrentContainer);
        }
        /// Pops a segment name from the stack of names being written.  
        /// Calls to this method should be balanced with previous calls to PushSegmentName.  
        protected void PopSegmentName(bool needPop) 
        {
            if (this.segmentNames != null && needPop) 
            {
                Debug.Assert(this.segmentNames.Count > 0, "this.segmentNames.Count > 0");
                this.segmentNames.RemoveAt(this.segmentNames.Count - 1);
                this.segmentContainers.RemoveAt(this.segmentContainers.Count - 1); 
                this.segmentResultCounts.RemoveAt(this.segmentResultCounts.Count - 1);
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentNames.Count, 
                    "this.segmentContainers.Count == this.segmentNames.Count -- should always be one-to-one");
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentResultCounts.Count,
                    "this.segmentContainers.Count == this.segmentResultCounts.Count -- should always be one-to-one");
            }
        } 
        /// Marks the fact that a recursive method was entered, and checks that the depth is allowed.  
        protected void RecurseEnter() 
        {
            WebUtil.RecurseEnter(RecursionLimit, ref this.recursionDepth); 
        }
        /// Marks the fact that a recursive method is leaving. 
        protected void RecurseLeave() 
        {
            WebUtil.RecurseLeave(ref this.recursionDepth); 
        } 
        /// Returns a clone of the segment names and containers trackd at this moment.  
        /// A clone of the segment names and containers tracked at this moment; possibly null. 
        protected object SaveSegmentNames()
        {
            if (this.segmentNames == null) 
            {
                return null; 
            } 
            else
            { 
                return new object[3]
                {
                    new List(this.segmentNames),
                    new List(this.segmentContainers), 
                    new List(this.segmentResultCounts)
                }; 
            } 
        }
 
        /// Restores the segment names saved through  
        /// )savedLists[0];
                this.segmentContainers = (List)savedLists[1]; 
                this.segmentResultCounts = (List)savedLists[2]; 
            }
        } 
        /// 
        /// Remove the given object instance from the complex type collection
        ///   
        /// Checks whether the property with the specified name should be expanded in-line. 
        /// true if the segment should be expanded; false otherwise.  
        protected bool ShouldExpandSegment(string name)
        { 
            Debug.Assert(name != null, "name != null");
            if (this.segmentNames == null)
            { 
                return false;
            } 
 
            if (this.requestDescription.RootProjectionNode != null)
            { 
                if (this.requestDescription.RootProjectionNode.UseExpandPathsForSerialization &&
                    this.requestDescription.RootProjectionNode.ExpandPaths != null)
                {
                    // We need to use the old ExpandPaths to determine which segments to expand 
                    //   since the IExpandProvider might have modified this collection.
                    for (int i = 0; i < this.requestDescription.RootProjectionNode.ExpandPaths.Count; i++) 
                    { 
                        List expandPath = this.requestDescription.RootProjectionNode.ExpandPaths[i];
                        if (expandPath.Count < this.segmentNames.Count) 
                        {
                            continue;
                        }
 
                        // We start off at '1' for segment names because the first one is the
                        // "this" in the query (/Customers?$expand=Orders doesn't include "Customers"). 
                        bool matchFound = true; 
                        for (int j = 1; j < this.segmentNames.Count; j++)
                        { 
                            if (expandPath[j - 1].Name != this.segmentNames[j])
                            {
                                matchFound = false;
                                break; 
                            }
                        } 
 
                        if (matchFound && expandPath[this.segmentNames.Count - 1].Name == name)
                        { 
                            return true;
                        }
                    }
                } 
                else
                { 
                    // We can use the new tree of expanded nodes to determine the expansions. 
                    // So find the expanded node on which we are now.
                    ExpandedProjectionNode expandedNode = this.GetCurrentExpandedProjectionNode(); 
                    if (expandedNode != null)
                    {
                        // And then if that node contains a child node of the specified name
                        //   and that child is also an expanded node, we should expand it. 
                        ProjectionNode lastNode = expandedNode.FindNode(name);
                        if (lastNode != null && lastNode is ExpandedProjectionNode) 
                        { 
                            return true;
                        } 
                    }
                }
            }
 
            return false;
        } 
 
        /// Returns a list of projection segments defined for the current segment. 
        /// List of  
        protected IEnumerable GetProjections() 
        {
            ExpandedProjectionNode expandedProjectionNode = this.GetCurrentExpandedProjectionNode(); 
            if (expandedProjectionNode == null || expandedProjectionNode.ProjectAllProperties) 
            {
                return null; 
            }
            else
            {
                return expandedProjectionNode.Nodes; 
            }
        } 
 
        /// 
        /// Returns the ETag value from the host response header 
        ///  
        /// returns the etag value for the given resource 
        protected string GetETagValue(object resource) 
        {
            // this.httpETagHeaderValue is the etag value which got computed for writing the etag in the response 
            // headers. The etag response header only gets written out in certain scenarios, whereas we always 
            // write etag in the response payload, if the type has etag properties. So just checking here is the
            // etag has already been computed, and if yes, returning that, otherwise computing the etag. 
            if (!String.IsNullOrEmpty(this.httpETagHeaderValue))
            {
                return this.httpETagHeaderValue;
            } 
            else
            { 
                Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null"); 
                return WebUtil.GetETagValue(this.service, resource, this.CurrentContainer);
            } 
        }
        /// Returns the key for the given resource. 
        /// Key for the given resource, with values encoded for use in a URI.  
        private static string GetObjectKey(object resource, DataServiceProviderWrapper provider, string containerName)
        { 
            Debug.Assert(resource != null, "resource != null");
            Debug.Assert(provider != null, "provider != null");
            Debug.Assert(!String.IsNullOrEmpty(containerName), "container name must be specified");
 
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append(containerName); 
            resultBuilder.Append('('); 
            ResourceType resourceType = WebUtil.GetNonPrimitiveResourceType(provider, resource);
            Debug.Assert(resourceType != null, "resourceType != null"); 
            IList keyProperties = resourceType.KeyProperties;
            Debug.Assert(keyProperties.Count != 0, "every resource type must have a key");
            for (int i = 0; i < keyProperties.Count; i++)
            { 
                ResourceProperty property = keyProperties[i];
                Debug.Assert(property.IsOfKind(ResourcePropertyKind.Key), "must be key property"); 
 
                object keyValue = WebUtil.GetPropertyValue(provider, resource, resourceType, property, null);
                if (keyValue == null) 
                {
                    // null keys not supported.
                    throw new InvalidOperationException(Strings.Serializer_NullKeysAreNotSupported(property.Name));
                } 
                if (i == 0) 
                { 
                    if (keyProperties.Count != 1)
                    { 
                        resultBuilder.Append(property.Name);
                        resultBuilder.Append('=');
                    }
                } 
                else
                { 
                    resultBuilder.Append(','); 
                    resultBuilder.Append(property.Name);
                    resultBuilder.Append('='); 
                }
                string keyValueText;
                if (!System.Data.Services.Parsing.WebConvert.TryKeyPrimitiveToString(keyValue, out keyValueText)) 
                {
                    throw new InvalidOperationException(Strings.Serializer_CannotConvertValue(keyValue)); 
                } 
                Debug.Assert(keyValueText != null, "keyValueText != null - otherwise TryKeyPrimitiveToString returned true and null value"); 
                resultBuilder.Append(System.Uri.EscapeDataString(keyValueText));
            }
            resultBuilder.Append(')'); 
            return resultBuilder.ToString();
        } 
 
        /// Helper method to append a path to the $expand or $select path list. 
        ///  parentPathSegments, string lastPathSegment)
        { 
            if (pathsBuilder.Length != 0)
            { 
                pathsBuilder.Append(','); 
            }
 
            foreach (string parentPathSegment in parentPathSegments)
            {
                pathsBuilder.Append(parentPathSegment).Append('/');
            } 
            pathsBuilder.Append(lastPathSegment); 
        } 
        /// Finds the   
        /// The  
        private ExpandedProjectionNode GetCurrentExpandedProjectionNode()
        { 
            ExpandedProjectionNode expandedProjectionNode = this.RequestDescription.RootProjectionNode;
            if (expandedProjectionNode == null) 
            { 
                return null;
            } 
            if (this.segmentNames != null)
            {
                // We start off at '1' for segment names because the first one is the 
                // "this" in the query and projection segments don't have that (the root is the "this")
                for (int i = 1; i < this.segmentNames.Count; i++) 
                { 
                    ProjectionNode projectionNode = expandedProjectionNode.FindNode(this.segmentNames[i]);
                    if (projectionNode == null) 
                    {
                        // If we don't have a projection node, report everything (complex types for example).
                        return null;
                    } 
                    Debug.Assert( 
                        projectionNode is ExpandedProjectionNode, 
                        "We have a pushed segment on the serialization stack which is not backed by expanded node in the query definition.");
                    expandedProjectionNode = (ExpandedProjectionNode)projectionNode; 
                }
            }
            return expandedProjectionNode; 
        }
 
        ///  
        /// Builds the string corresponding to query parameters for top level results to be put in next page link.
        ///   
        /// StringBuilder which has the query parameters in the URI query parameter format. 
        private StringBuilder GetNextPageQueryParametersForRootContainer()
        {
            StringBuilder queryParametersBuilder = new StringBuilder(); 
            // Handle service operation parameters 
            if (this.RequestDescription.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation) 
            {
                foreach (var parameter in this.RequestDescription.SegmentInfos[0].Operation.Parameters) 
                {
                    if (queryParametersBuilder.Length > 0)
                    {
                        queryParametersBuilder.Append('&'); 
                    }
 
                    queryParametersBuilder.Append(parameter.Name).Append('='); 
                    queryParametersBuilder.Append(this.service.OperationContext.Host.GetQueryStringItem(parameter.Name));
                } 
            }
            foreach (String queryParameter in Serializer.NextPageQueryParametersToCopy)
            { 
                String value = this.service.OperationContext.Host.GetQueryStringItem(queryParameter);
                if (!String.IsNullOrEmpty(value)) 
                { 
                    if (queryParametersBuilder.Length > 0)
                    { 
                        queryParametersBuilder.Append('&');
                    }
                    queryParametersBuilder.Append(queryParameter).Append('=').Append(value); 
                }
            } 
 
            int? topQueryParameter = this.GetTopQueryParameter();
            if (topQueryParameter.HasValue) 
            {
                int remainingResults = topQueryParameter.Value;
                // We don't touch the top count in case of custom paging. 
                if (!this.IsCustomPaged)
                { 
                    remainingResults = topQueryParameter.Value - this.CurrentContainer.PageSize; 
                }
 
                if (remainingResults > 0)
                {
                    if (queryParametersBuilder.Length > 0)
                    { 
                        queryParametersBuilder.Append('&');
                    } 
 
                    queryParametersBuilder.Append(XmlConstants.HttpQueryStringTop).Append('=').Append(remainingResults);
                } 
            }
            if (queryParametersBuilder.Length > 0)
            { 
                queryParametersBuilder.Append('&');
            } 
 
            return queryParametersBuilder;
        } 
        /// Recursive method which builds the $expand and $select paths for the specified node. 
        ///  parentPathSegments,
            StringBuilder projectionPaths, 
            StringBuilder expansionPaths,
            ExpandedProjectionNode expandedNode, 
            out bool foundProjections, 
            out bool foundExpansions)
        { 
            foundProjections = false;
            foundExpansions = false;
            bool foundExpansionChild = false; 
            bool foundProjectionChild = false;
            List expandedChildrenNeededToBeProjected = new List(); 
            foreach (ProjectionNode childNode in expandedNode.Nodes) 
            {
                ExpandedProjectionNode expandedChildNode = childNode as ExpandedProjectionNode; 
                if (expandedChildNode == null)
                {
                    // Explicitely project the property mentioned in this node
                    AppendProjectionOrExpansionPath(projectionPaths, parentPathSegments, childNode.PropertyName); 
                    foundProjections = true;
                } 
                else 
                {
                    foundExpansions = true; 
                    parentPathSegments.Add(expandedChildNode.PropertyName);
                    this.BuildProjectionAndExpansionPathsForNode(
                        parentPathSegments, 
                        projectionPaths,
                        expansionPaths, 
                        expandedChildNode, 
                        out foundProjectionChild,
                        out foundExpansionChild); 
                    parentPathSegments.RemoveAt(parentPathSegments.Count - 1);
                    // Add projection paths for this node if all its properties should be projected
                    if (expandedChildNode.ProjectAllProperties) 
                    {
                        if (foundProjectionChild) 
                        { 
                            // There were some projections in our children, but this node requires all properties -> project *
                            AppendProjectionOrExpansionPath(projectionPaths, parentPathSegments, childNode.PropertyName + "/*"); 
                        }
                        else
                        {
                            // There were no projections underneath this node, so we need to "project" this node 
                            // we just don't know yet if we need to project this one explicitly or if some parent will do it for us implicitly.
                            expandedChildrenNeededToBeProjected.Add(expandedChildNode); 
                        } 
                    }
 
                    foundProjections |= foundProjectionChild;
                    if (!foundExpansionChild)
                    { 
                        // If there were no expansions in children, we need to add this node to expansion list
                        AppendProjectionOrExpansionPath(expansionPaths, parentPathSegments, childNode.PropertyName); 
                    } 
                }
            } 
            if (!expandedNode.ProjectAllProperties || foundProjections)
            {
                // If we already projected some properties explicitely or this node does not want to project all properties 
                // and we have some expanded children which were not projected yet
                // we need to project those explicitely (as the other projections disable the "include all" for this node 
                // or we don't really want the "include all" anyway) 
                foreach (ExpandedProjectionNode childToProject in expandedChildrenNeededToBeProjected)
                { 
                    AppendProjectionOrExpansionPath(projectionPaths, parentPathSegments, childToProject.PropertyName);
                    // And since we're adding an explicit projection, mark us as using explicit projections
                    foundProjections = true; 
                }
            } 
        } 
        ///  
        /// Builds the string corresponding to query parameters for top level results to be put in next page link.
        ///  
        /// StringBuilder which has the query parameters in the URI query parameter format. 
        private StringBuilder GetNextPageQueryParametersForExpandedContainer() 
        {
            StringBuilder queryParametersBuilder = new StringBuilder(); 
 
            ExpandedProjectionNode expandedProjectionNode = this.GetCurrentExpandedProjectionNode();
            if (expandedProjectionNode != null) 
            {
                // Build a string containing all the $expand and $select for the current node and children
                List pathSegments = new List();
                StringBuilder projectionPaths = new StringBuilder(); 
                StringBuilder expansionPaths = new StringBuilder();
                bool foundExpansions = false; 
                bool foundProjections = false; 
                this.BuildProjectionAndExpansionPathsForNode(
                    pathSegments, 
                    projectionPaths,
                    expansionPaths,
                    expandedProjectionNode,
                    out foundProjections, 
                    out foundExpansions);
 
                // In most cases the root level of the query is projected by default 
                // The only exception is if there were projections in some children
                // and we need all the properties of the root -> then project * 
                if (foundProjections && expandedProjectionNode.ProjectAllProperties)
                {
                    AppendProjectionOrExpansionPath(projectionPaths, pathSegments, "*");
                } 
                if (projectionPaths.Length > 0) 
                { 
                    if (queryParametersBuilder.Length > 0)
                    { 
                        queryParametersBuilder.Append('&');
                    }
                    queryParametersBuilder.Append("$select=").Append(projectionPaths.ToString()); 
                }
 
                if (expansionPaths.Length > 0) 
                {
                    if (queryParametersBuilder.Length > 0) 
                    {
                        queryParametersBuilder.Append('&');
                    }
 
                    queryParametersBuilder.Append("$expand=").Append(expansionPaths.ToString());
                } 
            } 
            if (queryParametersBuilder.Length > 0) 
            {
                queryParametersBuilder.Append('&');
            }
 
            return queryParametersBuilder;
        } 
 
        /// Pushes a segment from the stack of names being written. 
        /// Calls to this method should be balanced with calls to PopSegmentName. 
        /// true if a segment was push, false otherwise 
        private bool PushSegment(string name, ResourceSetWrapper container) 
        {
            if (this.service.Configuration.MaxResultsPerCollection != Int32.MaxValue || 
                (container != null && container.PageSize != 0) || // Complex types have null container , paging should force a push 
                (this.requestDescription.RootProjectionNode != null &&
                 this.requestDescription.RootProjectionNode.ExpansionsSpecified)) 
            {
                if (this.segmentNames == null)
                {
                    this.segmentNames = new List(); 
                    this.segmentContainers = new List();
                    this.segmentResultCounts = new List(); 
                } 
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentNames.Count,
                    "this.segmentContainers.Count == this.segmentNames.Count -- should always be one-to-one");
                Debug.Assert(
                    this.segmentContainers.Count == this.segmentResultCounts.Count, 
                    "this.segmentContainers.Count == this.segmentResultCounts.Count -- should always be one-to-one");
                this.segmentNames.Add(name); 
                this.segmentContainers.Add(container); 
                this.segmentResultCounts.Add(0);
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentNames.Count,
                    "this.segmentContainers.Count == this.segmentNames.Count -- should always be one-to-one");
                Debug.Assert(
                    this.segmentContainers.Count == this.segmentResultCounts.Count, 
                    "this.segmentContainers.Count == this.segmentResultCounts.Count -- should always be one-to-one");
                return true; 
            } 
            return false; 
        }
        /// 
        /// Obtains the $top query parameter value. 
        ///  
        /// Integer value for $top or null otherwise.  
        private int? GetTopQueryParameter() 
        {
            String topQueryItem = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringTop); 
            if (!String.IsNullOrEmpty(topQueryItem))
            {
                return Int32.Parse(topQueryItem, System.Globalization.CultureInfo.InvariantCulture);
            } 
            else
            { 
                return null; 
            }
        } 
        /// 
        /// Builds the $skiptoken=[value,value] representation for appending to the next page link URI.
        ///   
        private abstract class SkipTokenBuilder
        { 
            /// Skip token string representation.  
            private StringBuilder skipToken;
 
            /// Constructor. 
            public SkipTokenBuilder()
            {
                this.skipToken = new StringBuilder(); 
                this.skipToken.Append(XmlConstants.HttpQueryStringSkipToken).Append('=');
            } 
 
            /// Returns the string representation for $skiptoken query parameter. 
            /// String representation for $skiptoken query parameter.  
            public StringBuilder GetSkipToken()
            {
                object[] skipTokenProperties = this.GetSkipTokenProperties();
 
                bool first = true;
                for (int i = 0; i < skipTokenProperties.Length; i++) 
                { 
                    object value = skipTokenProperties[i];
                    string stringValue; 
                    if (value == null)
                    {
                        stringValue = Parsing.ExpressionConstants.KeywordNull; 
                    }
                    else 
                    if (!System.Data.Services.Parsing.WebConvert.TryKeyPrimitiveToString(value, out stringValue)) 
                    {
                        throw new InvalidOperationException(Strings.Serializer_CannotConvertValue(value)); 
                    }
                    if (!first)
                    { 
                        this.skipToken.Append(',');
                    } 
 
                    this.skipToken.Append(System.Uri.EscapeDataString(stringValue));
                    first = false; 
                }
                return this.skipToken;
            } 
            /// Derived classes override this to provide the collection of values for skip token.  
            /// Array of primitive values that comprise the skip token.  
            protected abstract object[] GetSkipTokenProperties();
        } 
        /// Obtains the skip token from IExpandedResult values. 
        private class SkipTokenBuilderFromExpandedResult : SkipTokenBuilder
        { 
            /// IExpandedResult to lookup for skip token values. 
            private IExpandedResult skipTokenExpandedResult; 
 
            /// Number of values in skip token. 
            private int skipTokenExpressionCount; 
            /// Constructor. 
            /// Obtains skip token values by looking up IExpandedResult. 
            /// Array of primitive values that comprise the skip token.  
            protected override object[] GetSkipTokenProperties()
            { 
                object[] values = new object[this.skipTokenExpressionCount]; 
                for (int i = 0; i < this.skipTokenExpressionCount; i++) 
                {
                    String keyName = XmlConstants.SkipTokenPropertyPrefix + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
                    object value = this.skipTokenExpandedResult.GetExpandedPropertyValue(keyName);
                    if (value == DBNull.Value) 
                    {
                        value = null; 
                    } 
                    values[i] = value; 
                }
                return values;
            } 
        }
 
        /// Obtains the skip token by reading properties directly from an object.  
        private class SkipTokenBuilderFromProperties : SkipTokenBuilder
        { 
            /// Object to read skip token values from. 
            private object element;
            /// Collection of properties that comprise the skip token.  
            private ICollection properties;
 
            /// Current provider.  
            private DataServiceProviderWrapper provider;
 
            /// Constructor. 
            ///  properties)
                : base() 
            { 
                this.element = element;
                this.provider = provider; 
                this.properties = properties;
            }
            /// Obtains skip token values by reading properties directly from the last object.  
            /// Array of primitive values that comprise the skip token. 
            protected override object[] GetSkipTokenProperties() 
            { 
                object[] values = new object[this.properties.Count];
 
                int propertyIndex = 0;
                foreach (ResourceProperty property in this.properties)
                {
                    object value = WebUtil.GetPropertyValue(this.provider, this.element, null, property, null); 
                    if (value == DBNull.Value)
                    { 
                        value = null; 
                    }
 
                    values[propertyIndex++] = value;
                }
                return values; 
            }
        } 
 
        /// Provides the skip token obtained from the custom paging provider. 
        private class SkipTokenBuilderFromCustomPaging : SkipTokenBuilder 
        {
            /// Skip token obtained from custom paging provider. 
            private object[] lastTokenValue;
 
            /// Constructor. 
            /// Provides the skip token values that were obtained from custom paging provider.  
            /// Array of primitive values that comprise the skip token. 
            protected override object[] GetSkipTokenProperties() 
            { 
                return this.lastTokenValue;
            } 
        }
    }
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//---------------------------------------------------------------------- 
// 
//      Copyright (c) Microsoft Corporation.  All rights reserved.
//  
//  
//      An abstract base serializer for various serializers
//   
// 
// @owner  [....]
//--------------------------------------------------------------------- 
namespace System.Data.Services.Serializers
{
    #region Namespaces. 
    using System; 
    using System.Collections; 
    using System.Collections.Generic;
    using System.Data.Services; 
    using System.Data.Services.Providers;
    using System.Diagnostics;
    using System.Text;
    using System.Data.Services.Internal; 
    #endregion Namespaces. 
 
    /// Abstract base class for all serializers. 
    internal abstract class Serializer : IExceptionWriter 
    {
        #region Private fields.
        /// Maximum recursion limit on serializers.  
        private const int RecursionLimit = 100;
 
        ///  
        /// These query parameters can be copied for each next page link.
        /// Don't need to copy $skiptoken, $skip and $top because they are calculated every time. 
        ///  
        private static readonly String[] NextPageQueryParametersToCopy =
        {
            XmlConstants.HttpQueryStringFilter, 
            XmlConstants.HttpQueryStringExpand,
            XmlConstants.HttpQueryStringOrderBy, 
            XmlConstants.HttpQueryStringInlineCount, 
            XmlConstants.HttpQueryStringSelect
        }; 
        /// Base URI from which resources should be resolved. 
        private readonly Uri absoluteServiceUri;
 
        /// Data provider from which metadata should be gathered. 
        private readonly string httpETagHeaderValue; 
 
        /// Data provider from which metadata should be gathered. 
        private readonly IDataService service; 
        /// Description for the requested results. 
        private readonly RequestDescription requestDescription;
 
        /// Collection of complex types, used for cycle detection. 
        private HashSet complexTypeCollection; 
 
        /// Resolved segment containers. 
        private List segmentContainers; 
        /// Segment names. 
        private List segmentNames;
 
        /// Result counts for segments. 
        private List segmentResultCounts; 
 
        /// Depth of recursion. 
        private int recursionDepth; 
        /// Current skip token object for custom paging. 
        private object[] currentSkipTokenForCustomPaging;
 
        #endregion Private fields.
 
        /// Initializes a new base Serializer, ready to write out a description.  
        /// Container for the resource being serialized (possibly null).  
        protected ResourceSetWrapper CurrentContainer 
        {
            get 
            {
                if (this.segmentContainers == null || this.segmentContainers.Count == 0)
                {
                    return this.requestDescription.LastSegmentInfo.TargetContainer; 
                }
                else 
                { 
                    return this.segmentContainers[this.segmentContainers.Count - 1];
                } 
            }
        }
        /// Is current container the root container.  
        protected bool IsRootContainer
        { 
            get 
            {
                return (this.segmentContainers == null || this.segmentContainers.Count == 1); 
            }
        }
        ///  
        /// Gets the Data provider from which metadata should be gathered.
        ///   
        protected DataServiceProviderWrapper Provider 
        {
            [DebuggerStepThrough] 
            get { return this.service.Provider; }
        }
        ///  
        /// Gets the Data service from which metadata should be gathered.
        ///   
        protected IDataService Service 
        {
            [DebuggerStepThrough] 
            get { return this.service; }
        }
        /// Gets the absolute URI to the service.  
        protected Uri AbsoluteServiceUri
        { 
            [DebuggerStepThrough] 
            get { return this.absoluteServiceUri; }
        } 
        /// 
        /// Gets the RequestDescription for the request that is getting serialized.
        ///   
        protected RequestDescription RequestDescription
        { 
            [DebuggerStepThrough] 
            get
            { 
                return this.requestDescription;
            }
        }
 
        /// Are we using custom paging? 
        protected bool IsCustomPaged 
        { 
            get
            { 
                return this.service.PagingProvider.IsCustomPagedForSerialization;
            }
        }
 
        /// Serializes exception information. 
        ///  
        /// Gets the uri given the list of resource properties. This logic must be the same across all
        /// serializers. Hence putting this in a util class
        ///  
        /// uri for the given resource 
        internal static Uri GetUri(object resource, DataServiceProviderWrapper provider, ResourceSetWrapper container, Uri absoluteServiceUri) 
        {
            Debug.Assert(container != null, "container != null");
            string objectKey = GetObjectKey(resource, provider, container.Name);
            return RequestUriProcessor.AppendEscapedSegment(absoluteServiceUri, objectKey); 
        }
 
        ///  
        /// Appends the given entry to the given uri
        ///   
        /// new uri with the entry appended to the given uri 
        internal static Uri AppendEntryToUri(Uri currentUri, string entry) 
        {
            return RequestUriProcessor.AppendUnescapedSegment(currentUri, entry); 
        } 
        ///  
        /// Handles the complete serialization for the specified  
        /// 
        ///  
        internal void WriteRequest(IEnumerator queryResults, bool hasMoved)
        { 
            Debug.Assert(this.requestDescription.RequestEnumerable != null, "this.requestDescription.RequestEnumerable != null");
            Debug.Assert(queryResults != null, "queryResults != null"); 
            IExpandedResult expanded = queryResults as IExpandedResult; 
            if (this.requestDescription.LinkUri) 
            {
                bool needPop = this.PushSegmentForRoot();
                if (this.requestDescription.IsSingleResult)
                { 
                    this.WriteLink(queryResults.Current);
                    if (queryResults.MoveNext()) 
                    { 
                        throw new InvalidOperationException(Strings.SingleResourceExpected);
                    } 
                }
                else
                {
                    this.WriteLinkCollection(queryResults, hasMoved); 
                }
 
                this.PopSegmentName(needPop); 
            }
            else if (this.requestDescription.IsSingleResult) 
            {
                Debug.Assert(hasMoved == true, "hasMoved == true");
                this.WriteTopLevelElement(expanded, queryResults.Current);
                if (queryResults.MoveNext()) 
                {
                    throw new InvalidOperationException(Strings.SingleResourceExpected); 
                } 
            }
            else 
            {
                this.WriteTopLevelElements(expanded, queryResults, hasMoved);
            }
 
            this.Flush();
        } 
 
        /// Gets the expandable value for the specified object. 
        /// The property value.  
        protected static object GetExpandedProperty(DataServiceProviderWrapper provider, IExpandedResult expanded, object customObject, ResourceProperty property)
        { 
            Debug.Assert(property != null, "property != null"); 
            if (expanded == null)
            { 
                return WebUtil.GetPropertyValue(provider, customObject, null, property, null);
            }
            else
            { 
                // We may end up projecting null as a value of ResourceSetReference property. This can in theory break
                //   the serializers as they expect a non-null (possibly empty) IEnumerable instead. But note that 
                //   if we project null into the expanded property, we also project null into the ExpandedElement property 
                //   and thus the serializers should recognize this value as null and don't try to expand its properties.
                Debug.Assert( 
                    expanded.ExpandedElement != null,
                    "We should not be accessing expanded properties on null resource.");
                return expanded.GetExpandedPropertyValue(property.Name);
            } 
        }
 
        /// Gets the expanded element for the specified expanded result.  
        /// The expanded element.  
        protected static object GetExpandedElement(IExpandedResult expanded)
        {
            Debug.Assert(expanded != null, "expanded != null");
            return expanded.ExpandedElement; 
        }
 
        /// Flushes the writer to the underlying stream.  
        protected abstract void Flush();
 
        /// Writes a single top-level element. 
        /// Writes multiple top-level elements, possibly none.  
        /// 
        /// Write out the entry count 
        ///  
        protected abstract void WriteRowCount(); 
 
        /// 
        /// Write out the uri for the given element 
        ///  
        /// 
        /// Write out the uri for the given elements 
        ///   
        /// 
        /// Adds the given object instance to complex type collection 
        ///  
        /// true, if it got added successfully  
        protected bool AddToComplexTypeCollection(object complexTypeInstance)
        { 
            if (this.complexTypeCollection == null)
            {
                this.complexTypeCollection = new HashSet(ReferenceEqualityComparer.Instance);
            } 
            return this.complexTypeCollection.Add(complexTypeInstance); 
        } 
        ///  
        /// Gets the skip token object contained in the expanded result for standard paging.
        ///  
        /// Skip token object if any.  
        protected IExpandedResult GetSkipToken(IExpandedResult expanded)
        { 
            if (expanded != null && !this.IsCustomPaged && !this.RequestDescription.IsRequestForEnumServiceOperation) 
            {
                return expanded.GetExpandedPropertyValue(XmlConstants.HttpQueryStringSkipToken) as IExpandedResult; 
            }
            return null;
        } 
        ///  
        /// Obtains the URI for the link for next page in string format 
        ///  
        /// URI for the link for next page 
        protected String GetNextLinkUri(object lastObject, IExpandedResult skipTokenExpandedResult, Uri absoluteUri) 
        {
            UriBuilder builder = new UriBuilder(absoluteUri); 
            SkipTokenBuilder skipTokenBuilder = null; 
            if (this.IsRootContainer) 
            {
                if (!this.IsCustomPaged)
                {
                    if (skipTokenExpandedResult != null) 
                    {
                        skipTokenBuilder = new SkipTokenBuilderFromExpandedResult(skipTokenExpandedResult, this.RequestDescription.SkipTokenExpressionCount); 
                    } 
                    else
                    { 
                        Debug.Assert(this.RequestDescription.SkipTokenProperties != null, "Must have skip token properties collection");
                        Debug.Assert(this.RequestDescription.SkipTokenProperties.Count > 0, "Must have some valid ordered properties in the skip token properties collection");
                        skipTokenBuilder = new SkipTokenBuilderFromProperties(lastObject, this.Provider, this.RequestDescription.SkipTokenProperties);
                    } 
                }
                else 
                { 
                    Debug.Assert(this.currentSkipTokenForCustomPaging != null, "Must have obtained the skip token for custom paging.");
                    skipTokenBuilder = new SkipTokenBuilderFromCustomPaging(this.currentSkipTokenForCustomPaging); 
                }
                builder.Query = this.GetNextPageQueryParametersForRootContainer().Append(skipTokenBuilder.GetSkipToken()).ToString();
            } 
            else
            { 
                if (!this.IsCustomPaged) 
                {
                    // Internal results 
                    skipTokenBuilder = new SkipTokenBuilderFromProperties(lastObject, this.Provider, this.CurrentContainer.ResourceType.KeyProperties);
                }
                else
                { 
                    Debug.Assert(this.currentSkipTokenForCustomPaging != null, "Must have obtained the skip token for custom paging.");
                    skipTokenBuilder = new SkipTokenBuilderFromCustomPaging(this.currentSkipTokenForCustomPaging); 
                } 
                builder.Query = this.GetNextPageQueryParametersForExpandedContainer().Append(skipTokenBuilder.GetSkipToken()).ToString(); 
            }
            return builder.Uri.AbsoluteUri;
        } 
        /// Is next page link needs to be appended to the feed  
        /// true if the feed must have a next page link 
        protected bool NeedNextPageLink(IEnumerator enumerator) 
        {
            // For open types, current container could be null
            if (this.CurrentContainer != null && !this.RequestDescription.IsRequestForEnumServiceOperation)
            { 
                if (this.IsCustomPaged)
                { 
                    this.currentSkipTokenForCustomPaging = 
                        this.service.PagingProvider.PagingProviderInterface.GetContinuationToken(BasicExpandProvider.ExpandedEnumerator.UnwrapEnumerator(enumerator));
                    Debug.Assert( 
                            this.RequestDescription.ResponseVersion != RequestDescription.DataServiceDefaultResponseVersion,
                            "If custom paging is enabled, our response should be 2.0 and beyond.");
                    return this.currentSkipTokenForCustomPaging != null && this.currentSkipTokenForCustomPaging.Length > 0; 
                }
                else 
                { 
                    int pageSize = this.CurrentContainer.PageSize;
 
                    if (pageSize != 0 && this.RequestDescription.ResponseVersion != RequestDescription.DataServiceDefaultResponseVersion)
                    {
                        // For the root segment, if the $top parameter value is less than or equal to page size then we
                        // don't need to send the next page link. 
                        if (this.segmentResultCounts.Count == 1)
                        { 
                            int? topQueryParameter = this.GetTopQueryParameter(); 
                            if (topQueryParameter.HasValue) 
                            {
                                Debug.Assert(topQueryParameter.Value >= this.segmentResultCounts[this.segmentResultCounts.Count - 1], "$top must be the upper limits of the number of results returned.");
                                if (topQueryParameter.Value <= pageSize)
                                { 
                                    return false;
                                } 
                            } 
                        }
 
                        return this.segmentResultCounts[this.segmentResultCounts.Count - 1] == pageSize;
                    }
                }
            } 
            return false; 
        } 
        /// Pushes a segment for the root of the tree being written out.  
        /// Calls to this method should be balanced with calls to PopSegmentName. 
        /// true if segment was pushed, false otherwise  
        protected bool PushSegmentForOpenProperty(string propertyName, ResourceType propertyResourceType)
        { 
            Debug.Assert(propertyName != null, "propertyName != null"); 
            Debug.Assert(propertyResourceType != null, "propertyResourceType != null");
            ResourceSetWrapper container = null; 
            if (propertyResourceType.ResourceTypeKind == ResourceTypeKind.EntityType)
            {
                // Open navigation properties are not supported on OpenTypes.
                throw DataServiceException.CreateBadRequestError(Strings.OpenNavigationPropertiesNotSupportedOnOpenTypes(propertyName)); 
            }
 
            return this.PushSegment(propertyName, container); 
        }
 
        /// Increments the result count for the current segment, throws if exceeds the limit. 
        protected void IncrementSegmentResultCount()
        {
            if (this.segmentResultCounts != null) 
            {
                Debug.Assert(this.segmentResultCounts.Count > 0, "this.segmentResultCounts.Count > 0 -- otherwise we didn't PushSegmentForRoot"); 
                int max = this.service.Configuration.MaxResultsPerCollection; 
                if (!this.IsCustomPaged) 
                {
                    // For Open types, current container could be null, even though MaxResultsPerCollection has been set
                    // set we need to check for container before we try to assume page size, also open types do not have
                    // page sizes so we can safely ignore this check here 
                    if (this.CurrentContainer != null && this.CurrentContainer.PageSize != 0)
                    { 
                        Debug.Assert(max == Int32.MaxValue, "Either page size or max result count can be set, but not both"); 
                        max = this.CurrentContainer.PageSize;
                    } 
                }
                if (max != Int32.MaxValue)
                { 
                    int count = this.segmentResultCounts[this.segmentResultCounts.Count - 1];
                    checked 
                    { 
                        count++;
                    } 
                    if (count > max)
                    {
                        throw DataServiceException.CreateBadRequestError(Strings.Serializer_ResultsExceedMax(max)); 
                    }
 
                    this.segmentResultCounts[this.segmentResultCounts.Count - 1] = count; 
                }
            } 
        }
        /// Pushes a segment from the stack of names being written. 
        /// Calls to this method should be balanced with calls to PopSegmentName. 
        /// true if a segment was pushed, false otherwise  
        protected bool PushSegmentForProperty(ResourceProperty property) 
        {
            Debug.Assert(property != null, "property != null"); 
            ResourceSetWrapper current = null;
            if ((property.Kind & (ResourcePropertyKind.ResourceReference | ResourcePropertyKind.ResourceSetReference)) != 0)
            {
                current = this.CurrentContainer; 
                if (current != null)
                { 
                    current = this.service.Provider.GetContainer(current, current.ResourceType, property); 
                }
            } 
            return this.PushSegment(property.Name, current);
        }
 
        /// Pushes a segment for the root of the tree being written out. 
        /// Calls to this method should be balanced with calls to PopSegmentName.  
        /// true if the segment was pushed, false otherwise  
        protected bool PushSegmentForRoot()
        { 
            return this.PushSegment(this.RequestDescription.ContainerName, this.CurrentContainer);
        }
        /// Pops a segment name from the stack of names being written.  
        /// Calls to this method should be balanced with previous calls to PushSegmentName.  
        protected void PopSegmentName(bool needPop) 
        {
            if (this.segmentNames != null && needPop) 
            {
                Debug.Assert(this.segmentNames.Count > 0, "this.segmentNames.Count > 0");
                this.segmentNames.RemoveAt(this.segmentNames.Count - 1);
                this.segmentContainers.RemoveAt(this.segmentContainers.Count - 1); 
                this.segmentResultCounts.RemoveAt(this.segmentResultCounts.Count - 1);
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentNames.Count, 
                    "this.segmentContainers.Count == this.segmentNames.Count -- should always be one-to-one");
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentResultCounts.Count,
                    "this.segmentContainers.Count == this.segmentResultCounts.Count -- should always be one-to-one");
            }
        } 
        /// Marks the fact that a recursive method was entered, and checks that the depth is allowed.  
        protected void RecurseEnter() 
        {
            WebUtil.RecurseEnter(RecursionLimit, ref this.recursionDepth); 
        }
        /// Marks the fact that a recursive method is leaving. 
        protected void RecurseLeave() 
        {
            WebUtil.RecurseLeave(ref this.recursionDepth); 
        } 
        /// Returns a clone of the segment names and containers trackd at this moment.  
        /// A clone of the segment names and containers tracked at this moment; possibly null. 
        protected object SaveSegmentNames()
        {
            if (this.segmentNames == null) 
            {
                return null; 
            } 
            else
            { 
                return new object[3]
                {
                    new List(this.segmentNames),
                    new List(this.segmentContainers), 
                    new List(this.segmentResultCounts)
                }; 
            } 
        }
 
        /// Restores the segment names saved through  
        /// )savedLists[0];
                this.segmentContainers = (List)savedLists[1]; 
                this.segmentResultCounts = (List)savedLists[2]; 
            }
        } 
        /// 
        /// Remove the given object instance from the complex type collection
        ///   
        /// Checks whether the property with the specified name should be expanded in-line. 
        /// true if the segment should be expanded; false otherwise.  
        protected bool ShouldExpandSegment(string name)
        { 
            Debug.Assert(name != null, "name != null");
            if (this.segmentNames == null)
            { 
                return false;
            } 
 
            if (this.requestDescription.RootProjectionNode != null)
            { 
                if (this.requestDescription.RootProjectionNode.UseExpandPathsForSerialization &&
                    this.requestDescription.RootProjectionNode.ExpandPaths != null)
                {
                    // We need to use the old ExpandPaths to determine which segments to expand 
                    //   since the IExpandProvider might have modified this collection.
                    for (int i = 0; i < this.requestDescription.RootProjectionNode.ExpandPaths.Count; i++) 
                    { 
                        List expandPath = this.requestDescription.RootProjectionNode.ExpandPaths[i];
                        if (expandPath.Count < this.segmentNames.Count) 
                        {
                            continue;
                        }
 
                        // We start off at '1' for segment names because the first one is the
                        // "this" in the query (/Customers?$expand=Orders doesn't include "Customers"). 
                        bool matchFound = true; 
                        for (int j = 1; j < this.segmentNames.Count; j++)
                        { 
                            if (expandPath[j - 1].Name != this.segmentNames[j])
                            {
                                matchFound = false;
                                break; 
                            }
                        } 
 
                        if (matchFound && expandPath[this.segmentNames.Count - 1].Name == name)
                        { 
                            return true;
                        }
                    }
                } 
                else
                { 
                    // We can use the new tree of expanded nodes to determine the expansions. 
                    // So find the expanded node on which we are now.
                    ExpandedProjectionNode expandedNode = this.GetCurrentExpandedProjectionNode(); 
                    if (expandedNode != null)
                    {
                        // And then if that node contains a child node of the specified name
                        //   and that child is also an expanded node, we should expand it. 
                        ProjectionNode lastNode = expandedNode.FindNode(name);
                        if (lastNode != null && lastNode is ExpandedProjectionNode) 
                        { 
                            return true;
                        } 
                    }
                }
            }
 
            return false;
        } 
 
        /// Returns a list of projection segments defined for the current segment. 
        /// List of  
        protected IEnumerable GetProjections() 
        {
            ExpandedProjectionNode expandedProjectionNode = this.GetCurrentExpandedProjectionNode(); 
            if (expandedProjectionNode == null || expandedProjectionNode.ProjectAllProperties) 
            {
                return null; 
            }
            else
            {
                return expandedProjectionNode.Nodes; 
            }
        } 
 
        /// 
        /// Returns the ETag value from the host response header 
        ///  
        /// returns the etag value for the given resource 
        protected string GetETagValue(object resource) 
        {
            // this.httpETagHeaderValue is the etag value which got computed for writing the etag in the response 
            // headers. The etag response header only gets written out in certain scenarios, whereas we always 
            // write etag in the response payload, if the type has etag properties. So just checking here is the
            // etag has already been computed, and if yes, returning that, otherwise computing the etag. 
            if (!String.IsNullOrEmpty(this.httpETagHeaderValue))
            {
                return this.httpETagHeaderValue;
            } 
            else
            { 
                Debug.Assert(this.CurrentContainer != null, "this.CurrentContainer != null"); 
                return WebUtil.GetETagValue(this.service, resource, this.CurrentContainer);
            } 
        }
        /// Returns the key for the given resource. 
        /// Key for the given resource, with values encoded for use in a URI.  
        private static string GetObjectKey(object resource, DataServiceProviderWrapper provider, string containerName)
        { 
            Debug.Assert(resource != null, "resource != null");
            Debug.Assert(provider != null, "provider != null");
            Debug.Assert(!String.IsNullOrEmpty(containerName), "container name must be specified");
 
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.Append(containerName); 
            resultBuilder.Append('('); 
            ResourceType resourceType = WebUtil.GetNonPrimitiveResourceType(provider, resource);
            Debug.Assert(resourceType != null, "resourceType != null"); 
            IList keyProperties = resourceType.KeyProperties;
            Debug.Assert(keyProperties.Count != 0, "every resource type must have a key");
            for (int i = 0; i < keyProperties.Count; i++)
            { 
                ResourceProperty property = keyProperties[i];
                Debug.Assert(property.IsOfKind(ResourcePropertyKind.Key), "must be key property"); 
 
                object keyValue = WebUtil.GetPropertyValue(provider, resource, resourceType, property, null);
                if (keyValue == null) 
                {
                    // null keys not supported.
                    throw new InvalidOperationException(Strings.Serializer_NullKeysAreNotSupported(property.Name));
                } 
                if (i == 0) 
                { 
                    if (keyProperties.Count != 1)
                    { 
                        resultBuilder.Append(property.Name);
                        resultBuilder.Append('=');
                    }
                } 
                else
                { 
                    resultBuilder.Append(','); 
                    resultBuilder.Append(property.Name);
                    resultBuilder.Append('='); 
                }
                string keyValueText;
                if (!System.Data.Services.Parsing.WebConvert.TryKeyPrimitiveToString(keyValue, out keyValueText)) 
                {
                    throw new InvalidOperationException(Strings.Serializer_CannotConvertValue(keyValue)); 
                } 
                Debug.Assert(keyValueText != null, "keyValueText != null - otherwise TryKeyPrimitiveToString returned true and null value"); 
                resultBuilder.Append(System.Uri.EscapeDataString(keyValueText));
            }
            resultBuilder.Append(')'); 
            return resultBuilder.ToString();
        } 
 
        /// Helper method to append a path to the $expand or $select path list. 
        ///  parentPathSegments, string lastPathSegment)
        { 
            if (pathsBuilder.Length != 0)
            { 
                pathsBuilder.Append(','); 
            }
 
            foreach (string parentPathSegment in parentPathSegments)
            {
                pathsBuilder.Append(parentPathSegment).Append('/');
            } 
            pathsBuilder.Append(lastPathSegment); 
        } 
        /// Finds the   
        /// The  
        private ExpandedProjectionNode GetCurrentExpandedProjectionNode()
        { 
            ExpandedProjectionNode expandedProjectionNode = this.RequestDescription.RootProjectionNode;
            if (expandedProjectionNode == null) 
            { 
                return null;
            } 
            if (this.segmentNames != null)
            {
                // We start off at '1' for segment names because the first one is the 
                // "this" in the query and projection segments don't have that (the root is the "this")
                for (int i = 1; i < this.segmentNames.Count; i++) 
                { 
                    ProjectionNode projectionNode = expandedProjectionNode.FindNode(this.segmentNames[i]);
                    if (projectionNode == null) 
                    {
                        // If we don't have a projection node, report everything (complex types for example).
                        return null;
                    } 
                    Debug.Assert( 
                        projectionNode is ExpandedProjectionNode, 
                        "We have a pushed segment on the serialization stack which is not backed by expanded node in the query definition.");
                    expandedProjectionNode = (ExpandedProjectionNode)projectionNode; 
                }
            }
            return expandedProjectionNode; 
        }
 
        ///  
        /// Builds the string corresponding to query parameters for top level results to be put in next page link.
        ///   
        /// StringBuilder which has the query parameters in the URI query parameter format. 
        private StringBuilder GetNextPageQueryParametersForRootContainer()
        {
            StringBuilder queryParametersBuilder = new StringBuilder(); 
            // Handle service operation parameters 
            if (this.RequestDescription.SegmentInfos[0].TargetSource == RequestTargetSource.ServiceOperation) 
            {
                foreach (var parameter in this.RequestDescription.SegmentInfos[0].Operation.Parameters) 
                {
                    if (queryParametersBuilder.Length > 0)
                    {
                        queryParametersBuilder.Append('&'); 
                    }
 
                    queryParametersBuilder.Append(parameter.Name).Append('='); 
                    queryParametersBuilder.Append(this.service.OperationContext.Host.GetQueryStringItem(parameter.Name));
                } 
            }
            foreach (String queryParameter in Serializer.NextPageQueryParametersToCopy)
            { 
                String value = this.service.OperationContext.Host.GetQueryStringItem(queryParameter);
                if (!String.IsNullOrEmpty(value)) 
                { 
                    if (queryParametersBuilder.Length > 0)
                    { 
                        queryParametersBuilder.Append('&');
                    }
                    queryParametersBuilder.Append(queryParameter).Append('=').Append(value); 
                }
            } 
 
            int? topQueryParameter = this.GetTopQueryParameter();
            if (topQueryParameter.HasValue) 
            {
                int remainingResults = topQueryParameter.Value;
                // We don't touch the top count in case of custom paging. 
                if (!this.IsCustomPaged)
                { 
                    remainingResults = topQueryParameter.Value - this.CurrentContainer.PageSize; 
                }
 
                if (remainingResults > 0)
                {
                    if (queryParametersBuilder.Length > 0)
                    { 
                        queryParametersBuilder.Append('&');
                    } 
 
                    queryParametersBuilder.Append(XmlConstants.HttpQueryStringTop).Append('=').Append(remainingResults);
                } 
            }
            if (queryParametersBuilder.Length > 0)
            { 
                queryParametersBuilder.Append('&');
            } 
 
            return queryParametersBuilder;
        } 
        /// Recursive method which builds the $expand and $select paths for the specified node. 
        ///  parentPathSegments,
            StringBuilder projectionPaths, 
            StringBuilder expansionPaths,
            ExpandedProjectionNode expandedNode, 
            out bool foundProjections, 
            out bool foundExpansions)
        { 
            foundProjections = false;
            foundExpansions = false;
            bool foundExpansionChild = false; 
            bool foundProjectionChild = false;
            List expandedChildrenNeededToBeProjected = new List(); 
            foreach (ProjectionNode childNode in expandedNode.Nodes) 
            {
                ExpandedProjectionNode expandedChildNode = childNode as ExpandedProjectionNode; 
                if (expandedChildNode == null)
                {
                    // Explicitely project the property mentioned in this node
                    AppendProjectionOrExpansionPath(projectionPaths, parentPathSegments, childNode.PropertyName); 
                    foundProjections = true;
                } 
                else 
                {
                    foundExpansions = true; 
                    parentPathSegments.Add(expandedChildNode.PropertyName);
                    this.BuildProjectionAndExpansionPathsForNode(
                        parentPathSegments, 
                        projectionPaths,
                        expansionPaths, 
                        expandedChildNode, 
                        out foundProjectionChild,
                        out foundExpansionChild); 
                    parentPathSegments.RemoveAt(parentPathSegments.Count - 1);
                    // Add projection paths for this node if all its properties should be projected
                    if (expandedChildNode.ProjectAllProperties) 
                    {
                        if (foundProjectionChild) 
                        { 
                            // There were some projections in our children, but this node requires all properties -> project *
                            AppendProjectionOrExpansionPath(projectionPaths, parentPathSegments, childNode.PropertyName + "/*"); 
                        }
                        else
                        {
                            // There were no projections underneath this node, so we need to "project" this node 
                            // we just don't know yet if we need to project this one explicitly or if some parent will do it for us implicitly.
                            expandedChildrenNeededToBeProjected.Add(expandedChildNode); 
                        } 
                    }
 
                    foundProjections |= foundProjectionChild;
                    if (!foundExpansionChild)
                    { 
                        // If there were no expansions in children, we need to add this node to expansion list
                        AppendProjectionOrExpansionPath(expansionPaths, parentPathSegments, childNode.PropertyName); 
                    } 
                }
            } 
            if (!expandedNode.ProjectAllProperties || foundProjections)
            {
                // If we already projected some properties explicitely or this node does not want to project all properties 
                // and we have some expanded children which were not projected yet
                // we need to project those explicitely (as the other projections disable the "include all" for this node 
                // or we don't really want the "include all" anyway) 
                foreach (ExpandedProjectionNode childToProject in expandedChildrenNeededToBeProjected)
                { 
                    AppendProjectionOrExpansionPath(projectionPaths, parentPathSegments, childToProject.PropertyName);
                    // And since we're adding an explicit projection, mark us as using explicit projections
                    foundProjections = true; 
                }
            } 
        } 
        ///  
        /// Builds the string corresponding to query parameters for top level results to be put in next page link.
        ///  
        /// StringBuilder which has the query parameters in the URI query parameter format. 
        private StringBuilder GetNextPageQueryParametersForExpandedContainer() 
        {
            StringBuilder queryParametersBuilder = new StringBuilder(); 
 
            ExpandedProjectionNode expandedProjectionNode = this.GetCurrentExpandedProjectionNode();
            if (expandedProjectionNode != null) 
            {
                // Build a string containing all the $expand and $select for the current node and children
                List pathSegments = new List();
                StringBuilder projectionPaths = new StringBuilder(); 
                StringBuilder expansionPaths = new StringBuilder();
                bool foundExpansions = false; 
                bool foundProjections = false; 
                this.BuildProjectionAndExpansionPathsForNode(
                    pathSegments, 
                    projectionPaths,
                    expansionPaths,
                    expandedProjectionNode,
                    out foundProjections, 
                    out foundExpansions);
 
                // In most cases the root level of the query is projected by default 
                // The only exception is if there were projections in some children
                // and we need all the properties of the root -> then project * 
                if (foundProjections && expandedProjectionNode.ProjectAllProperties)
                {
                    AppendProjectionOrExpansionPath(projectionPaths, pathSegments, "*");
                } 
                if (projectionPaths.Length > 0) 
                { 
                    if (queryParametersBuilder.Length > 0)
                    { 
                        queryParametersBuilder.Append('&');
                    }
                    queryParametersBuilder.Append("$select=").Append(projectionPaths.ToString()); 
                }
 
                if (expansionPaths.Length > 0) 
                {
                    if (queryParametersBuilder.Length > 0) 
                    {
                        queryParametersBuilder.Append('&');
                    }
 
                    queryParametersBuilder.Append("$expand=").Append(expansionPaths.ToString());
                } 
            } 
            if (queryParametersBuilder.Length > 0) 
            {
                queryParametersBuilder.Append('&');
            }
 
            return queryParametersBuilder;
        } 
 
        /// Pushes a segment from the stack of names being written. 
        /// Calls to this method should be balanced with calls to PopSegmentName. 
        /// true if a segment was push, false otherwise 
        private bool PushSegment(string name, ResourceSetWrapper container) 
        {
            if (this.service.Configuration.MaxResultsPerCollection != Int32.MaxValue || 
                (container != null && container.PageSize != 0) || // Complex types have null container , paging should force a push 
                (this.requestDescription.RootProjectionNode != null &&
                 this.requestDescription.RootProjectionNode.ExpansionsSpecified)) 
            {
                if (this.segmentNames == null)
                {
                    this.segmentNames = new List(); 
                    this.segmentContainers = new List();
                    this.segmentResultCounts = new List(); 
                } 
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentNames.Count,
                    "this.segmentContainers.Count == this.segmentNames.Count -- should always be one-to-one");
                Debug.Assert(
                    this.segmentContainers.Count == this.segmentResultCounts.Count, 
                    "this.segmentContainers.Count == this.segmentResultCounts.Count -- should always be one-to-one");
                this.segmentNames.Add(name); 
                this.segmentContainers.Add(container); 
                this.segmentResultCounts.Add(0);
                Debug.Assert( 
                    this.segmentContainers.Count == this.segmentNames.Count,
                    "this.segmentContainers.Count == this.segmentNames.Count -- should always be one-to-one");
                Debug.Assert(
                    this.segmentContainers.Count == this.segmentResultCounts.Count, 
                    "this.segmentContainers.Count == this.segmentResultCounts.Count -- should always be one-to-one");
                return true; 
            } 
            return false; 
        }
        /// 
        /// Obtains the $top query parameter value. 
        ///  
        /// Integer value for $top or null otherwise.  
        private int? GetTopQueryParameter() 
        {
            String topQueryItem = this.service.OperationContext.Host.GetQueryStringItem(XmlConstants.HttpQueryStringTop); 
            if (!String.IsNullOrEmpty(topQueryItem))
            {
                return Int32.Parse(topQueryItem, System.Globalization.CultureInfo.InvariantCulture);
            } 
            else
            { 
                return null; 
            }
        } 
        /// 
        /// Builds the $skiptoken=[value,value] representation for appending to the next page link URI.
        ///   
        private abstract class SkipTokenBuilder
        { 
            /// Skip token string representation.  
            private StringBuilder skipToken;
 
            /// Constructor. 
            public SkipTokenBuilder()
            {
                this.skipToken = new StringBuilder(); 
                this.skipToken.Append(XmlConstants.HttpQueryStringSkipToken).Append('=');
            } 
 
            /// Returns the string representation for $skiptoken query parameter. 
            /// String representation for $skiptoken query parameter.  
            public StringBuilder GetSkipToken()
            {
                object[] skipTokenProperties = this.GetSkipTokenProperties();
 
                bool first = true;
                for (int i = 0; i < skipTokenProperties.Length; i++) 
                { 
                    object value = skipTokenProperties[i];
                    string stringValue; 
                    if (value == null)
                    {
                        stringValue = Parsing.ExpressionConstants.KeywordNull; 
                    }
                    else 
                    if (!System.Data.Services.Parsing.WebConvert.TryKeyPrimitiveToString(value, out stringValue)) 
                    {
                        throw new InvalidOperationException(Strings.Serializer_CannotConvertValue(value)); 
                    }
                    if (!first)
                    { 
                        this.skipToken.Append(',');
                    } 
 
                    this.skipToken.Append(System.Uri.EscapeDataString(stringValue));
                    first = false; 
                }
                return this.skipToken;
            } 
            /// Derived classes override this to provide the collection of values for skip token.  
            /// Array of primitive values that comprise the skip token.  
            protected abstract object[] GetSkipTokenProperties();
        } 
        /// Obtains the skip token from IExpandedResult values. 
        private class SkipTokenBuilderFromExpandedResult : SkipTokenBuilder
        { 
            /// IExpandedResult to lookup for skip token values. 
            private IExpandedResult skipTokenExpandedResult; 
 
            /// Number of values in skip token. 
            private int skipTokenExpressionCount; 
            /// Constructor. 
            /// Obtains skip token values by looking up IExpandedResult. 
            /// Array of primitive values that comprise the skip token.  
            protected override object[] GetSkipTokenProperties()
            { 
                object[] values = new object[this.skipTokenExpressionCount]; 
                for (int i = 0; i < this.skipTokenExpressionCount; i++) 
                {
                    String keyName = XmlConstants.SkipTokenPropertyPrefix + i.ToString(System.Globalization.CultureInfo.InvariantCulture);
                    object value = this.skipTokenExpandedResult.GetExpandedPropertyValue(keyName);
                    if (value == DBNull.Value) 
                    {
                        value = null; 
                    } 
                    values[i] = value; 
                }
                return values;
            } 
        }
 
        /// Obtains the skip token by reading properties directly from an object.  
        private class SkipTokenBuilderFromProperties : SkipTokenBuilder
        { 
            /// Object to read skip token values from. 
            private object element;
            /// Collection of properties that comprise the skip token.  
            private ICollection properties;
 
            /// Current provider.  
            private DataServiceProviderWrapper provider;
 
            /// Constructor. 
            ///  properties)
                : base() 
            { 
                this.element = element;
                this.provider = provider; 
                this.properties = properties;
            }
            /// Obtains skip token values by reading properties directly from the last object.  
            /// Array of primitive values that comprise the skip token. 
            protected override object[] GetSkipTokenProperties() 
            { 
                object[] values = new object[this.properties.Count];
 
                int propertyIndex = 0;
                foreach (ResourceProperty property in this.properties)
                {
                    object value = WebUtil.GetPropertyValue(this.provider, this.element, null, property, null); 
                    if (value == DBNull.Value)
                    { 
                        value = null; 
                    }
 
                    values[propertyIndex++] = value;
                }
                return values; 
            }
        } 
 
        /// Provides the skip token obtained from the custom paging provider. 
        private class SkipTokenBuilderFromCustomPaging : SkipTokenBuilder 
        {
            /// Skip token obtained from custom paging provider. 
            private object[] lastTokenValue;
 
            /// Constructor. 
            /// Provides the skip token values that were obtained from custom paging provider.  
            /// Array of primitive values that comprise the skip token. 
            protected override object[] GetSkipTokenProperties() 
            { 
                return this.lastTokenValue;
            } 
        }
    }
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.