/// <summary>
        /// Validates that a string is either a valid absolute URI, or (if it begins with '#') it is a valid URI fragment.
        /// </summary>
        /// <param name="metadataDocumentUri">The metadata document uri.</param>
        /// <param name="propertyName">The property name to validate.</param>
        internal static void ValidateMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName)
        {
            Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null");
            Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri");
            Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)");

            string uriStringToValidate = propertyName;

            // If it starts with a '#', validate that the rest of the string is a valid Uri fragment.
            if (propertyName[0] == ODataConstants.ContextUriFragmentIndicator)
            {
                // In order to use System.Uri to validate a fragement, we first prepend the metadataDocumentUri
                // so that it becomes an absolute URI which we can validate with Uri.IsWellFormedUriString.
                uriStringToValidate = UriUtils.UriToString(metadataDocumentUri) + UriUtils.EnsureEscapedFragment(propertyName);
            }

            if (!Uri.IsWellFormedUriString(uriStringToValidate, UriKind.Absolute) ||
                !ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) ||
                propertyName[propertyName.Length - 1] == ODataConstants.ContextUriFragmentIndicator)
            {
                throw new ODataException(Strings.ValidationUtils_InvalidMetadataReferenceProperty(propertyName));
            }

            if (IsOpenMetadataReferencePropertyName(metadataDocumentUri, propertyName))
            {
                throw new ODataException(Strings.ODataJsonLightValidationUtils_OpenMetadataReferencePropertyNotSupported(propertyName, UriUtils.UriToString(metadataDocumentUri)));
            }
        }
        /// <summary>
        /// Determines if the specified property name is a name of an open metadata reference property.
        /// </summary>
        /// <param name="metadataDocumentUri">The metadata document uri.</param>
        /// <param name="propertyName">The property name in question.</param>
        /// <returns>true if the specified property name is a name of an open metadata reference property; false otherwise.</returns>
        internal static bool IsOpenMetadataReferencePropertyName(Uri metadataDocumentUri, string propertyName)
        {
            Debug.Assert(metadataDocumentUri != null, "metadataDocumentUri != null");
            Debug.Assert(metadataDocumentUri.IsAbsoluteUri, "metadataDocumentUri.IsAbsoluteUri");
            Debug.Assert(!String.IsNullOrEmpty(propertyName), "!string.IsNullOrEmpty(propertyName)");

            // If a metadata reference property isn't based off of the known metadata document URI (for example, it points to a model on another server),
            // then it must be open. It is based off the known metadata document URI if it either is a fragment (i.e., starts with a hash) or starts with the known absolute URI.
            return(ODataJsonLightUtils.IsMetadataReferenceProperty(propertyName) &&
                   propertyName[0] != ODataConstants.ContextUriFragmentIndicator &&
                   !propertyName.StartsWith(UriUtils.UriToString(metadataDocumentUri), StringComparison.OrdinalIgnoreCase));
        }
        /// <summary>
        /// Reads a property name from the JSON reader and determines if it's a regular property, an instance annotation or a property annotation.
        /// </summary>
        /// <param name="propertyName">The name of the regular property which the reader is positioned on or which a property annotation belongs to.</param>
        /// <param name="annotationName">The name of the instance or property annotation, or null if the reader is on a regular property.</param>
        private void ReadPropertyName(out string propertyName, out string annotationName)
        {
            string jsonPropertyName = this.GetPropertyName();

            Debug.Assert(!string.IsNullOrEmpty(jsonPropertyName), "The JSON reader guarantees that property names are not null or empty.");

            this.ReadInternal();

            if (jsonPropertyName.StartsWith("@", StringComparison.Ordinal))
            {
                // Instance-level annotation for the instance itself; not property name.
                propertyName   = null;
                annotationName = jsonPropertyName.Substring(1);
            }
            else
            {
                int separatorIndex = jsonPropertyName.IndexOf(JsonLightConstants.ODataPropertyAnnotationSeparatorChar);
                if (separatorIndex > 0)
                {
                    // This is a property annotation; compute the property and annotation names
                    propertyName   = jsonPropertyName.Substring(0, separatorIndex);
                    annotationName = jsonPropertyName.Substring(separatorIndex + 1);
                }
                else
                {
                    // This is either a regular data property or an instance-level annotation
                    int dotIndex = jsonPropertyName.IndexOf('.');
                    if (dotIndex < 0)
                    {
                        // Regular property
                        propertyName   = jsonPropertyName;
                        annotationName = null;
                    }
                    else
                    {
                        if (ODataJsonLightUtils.IsMetadataReferenceProperty(jsonPropertyName))
                        {
                            // Metadata reference property
                            propertyName   = null;
                            annotationName = jsonPropertyName;
                        }
                        else
                        {
                            // unexpected instance annotation name
                            throw new ODataException(Microsoft.OData.Core.Strings.JsonReaderExtensions_UnexpectedInstanceAnnotationName(jsonPropertyName));
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Gets the metadata reference fragment from the operation context uri.
        /// i.e. if the operation context uri is {absolute metadata document uri}#{container-qualified-operation-name},
        /// this method will return #{container-qualified-operation-name}.
        /// </summary>
        /// <param name="operation">Operation in question.</param>
        /// <returns>The metadata reference fragment from the operation context uri.</returns>
        private string GetOperationMetadataString(ODataOperation operation)
        {
            Debug.Assert(operation != null && operation.Metadata != null, "operation != null && operation.Metadata != null");

            string operationMetadataString = UriUtils.UriToString(operation.Metadata);

            Debug.Assert(ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString), "ODataJsonLightUtils.IsMetadataReferenceProperty(operationMetadataString)");

            // If we don't have a metadata document URI (which is the case with nometadata mode), just write the string form of the Uri we were given.
            if (this.MetadataDocumentBaseUri == null)
            {
                return(operation.Metadata.Fragment);
            }

            Debug.Assert(
                !ODataJsonLightValidationUtils.IsOpenMetadataReferencePropertyName(this.MetadataDocumentBaseUri, operationMetadataString),
                "Open metadata reference property is not supported, we should have thrown before this point.");

            return(ODataConstants.ContextUriFragmentIndicator + ODataJsonLightUtils.GetUriFragmentFromMetadataReferencePropertyName(this.MetadataDocumentBaseUri, operationMetadataString));
        }
        /// <summary>
        /// Parses JSON object property starting with the current position of the JSON reader.
        /// </summary>
        /// <param name="duplicatePropertyNamesChecker">The duplicate property names checker to use, it will also store the property annotations found.</param>
        /// <param name="readPropertyAnnotationValue">Function called to read property annotation value.</param>
        /// <param name="parsedPropertyName">The name of the property or instance annotation found.</param>
        /// <returns>
        /// PropertyWithValue - a property with value was found. The <paramref name="parsedPropertyName"/> contains the name of the property.
        ///                     The reader is positioned on the property value.
        /// PropertyWithoutValue - a property without a value was found. The <paramref name="parsedPropertyName"/> contains the name of the property.
        ///                        The reader is positioned on the node after property annotations (so either a property or end of object).
        /// ODataInstanceAnnotation - an odata instance annotation was found. The <paramref name="parsedPropertyName"/> contains the name of the annotation.
        ///                      The reader is positioned on the value of the annotation.
        /// CustomInstanceAnnotation - a custom instance annotation was found. The <paramref name="parsedPropertyName"/> contains the name of the annotation.
        ///                      The reader is positioned on the value of the annotation.
        /// MetadataReferenceProperty - a property which is a reference into the metadata was found.
        ///                             The reader is positioned on the value of the property.
        /// EndOfObject - end of the object scope was reached and no properties are to be reported. The <paramref name="parsedPropertyName"/> is null.
        ///               This can only happen if there's a property annotation which is ignored (for example custom one) at the end of the object.
        /// </returns>
        private PropertyParsingResult ParseProperty(
            DuplicatePropertyNamesChecker duplicatePropertyNamesChecker,
            Func <string, object> readPropertyAnnotationValue,
            out string parsedPropertyName)
        {
            Debug.Assert(duplicatePropertyNamesChecker != null, "duplicatePropertyNamesChecker != null");
            Debug.Assert(readPropertyAnnotationValue != null, "readPropertyAnnotationValue != null");
            string lastPropertyAnnotationNameFound = null;

            parsedPropertyName = null;

            while (this.JsonReader.NodeType == JsonNodeType.Property)
            {
                string nameFromReader = this.JsonReader.GetPropertyName();

                string propertyNameFromReader, annotationNameFromReader;
                bool   isPropertyAnnotation = TryParsePropertyAnnotation(nameFromReader, out propertyNameFromReader, out annotationNameFromReader);

                bool isInstanceAnnotation = false;
                if (!isPropertyAnnotation)
                {
                    isInstanceAnnotation   = IsInstanceAnnotation(nameFromReader);
                    propertyNameFromReader = isInstanceAnnotation ? nameFromReader.Substring(1) : nameFromReader;
                }

                // If parsedPropertyName is set and is different from the property name the reader is currently on,
                // we have parsed a property annotation for a different property than the one at the current position.
                if (parsedPropertyName != null && string.CompareOrdinal(parsedPropertyName, propertyNameFromReader) != 0)
                {
                    if (ODataJsonLightReaderUtils.IsAnnotationProperty(parsedPropertyName))
                    {
                        throw new ODataException(OData.Core.Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName));
                    }

                    return(PropertyParsingResult.PropertyWithoutValue);
                }

                if (isPropertyAnnotation)
                {
                    // If this is a unknown odata annotation targeting a property, we skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explaination.
                    // Note that we don't skip over unknown odata annotations targeting another annotation. We don't allow annotations (except odata.type) targeting other annotations,
                    // so this.ProcessPropertyAnnotation() will test and fail for that case.
                    if (!ODataJsonLightReaderUtils.IsAnnotationProperty(propertyNameFromReader) && this.SkippedOverUnknownODataAnnotation(annotationNameFromReader))
                    {
                        continue;
                    }

                    // We have another property annotation for the same property we parsed.
                    parsedPropertyName = propertyNameFromReader;
                    lastPropertyAnnotationNameFound = annotationNameFromReader;

                    this.ProcessPropertyAnnotation(propertyNameFromReader, annotationNameFromReader, duplicatePropertyNamesChecker, readPropertyAnnotationValue);
                    continue;
                }

                // If this is a unknown odata annotation, skip over it. See remark on the method SkippedOverUnknownODataAnnotation() for detailed explaination.
                if (this.SkippedOverUnknownODataAnnotation(propertyNameFromReader))
                {
                    continue;
                }

                // We are encountering the property name for the first time.
                // Read over the property name.
                this.JsonReader.Read();
                parsedPropertyName = propertyNameFromReader;

                if (!isInstanceAnnotation && ODataJsonLightUtils.IsMetadataReferenceProperty(propertyNameFromReader))
                {
                    return(PropertyParsingResult.MetadataReferenceProperty);
                }

                if (!isInstanceAnnotation && !ODataJsonLightReaderUtils.IsAnnotationProperty(propertyNameFromReader))
                {
                    // Normal property
                    return(PropertyParsingResult.PropertyWithValue);
                }

                // Handle 'odata.XXXXX' annotations
                if (isInstanceAnnotation && ODataJsonLightReaderUtils.IsODataAnnotationName(propertyNameFromReader))
                {
                    return(PropertyParsingResult.ODataInstanceAnnotation);
                }

                // Handle custom annotations
                return(PropertyParsingResult.CustomInstanceAnnotation);
            }

            this.AssertJsonCondition(JsonNodeType.EndObject);
            if (parsedPropertyName != null)
            {
                if (ODataJsonLightReaderUtils.IsAnnotationProperty(parsedPropertyName))
                {
                    throw new ODataException(OData.Core.Strings.ODataJsonLightDeserializer_AnnotationTargetingInstanceAnnotationWithoutValue(lastPropertyAnnotationNameFound, parsedPropertyName));
                }

                return(PropertyParsingResult.PropertyWithoutValue);
            }

            return(PropertyParsingResult.EndOfObject);
        }