// Copyright Microsoft Corporation.
// This source file is subject to the Microsoft Permissive License.
// See http://www.microsoft.com/resources/sharedsource/licensingbasics/sharedsourcelicenses.mspx.
// All other rights reserved.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Xml;
using System.Xml.XPath;
using System.IO;
using System.Text;
// still have problems with spaces
namespace Microsoft.Ddue.Tools{
public class PlatformsComponent : BuildComponent
{
private Dictionary<string, Dictionary<string, VersionFilter>> versionFilters = new Dictionary<string, Dictionary<string, VersionFilter>>();
private XPathExpression platformQuery = XPathExpression.Compile("/platforms/platform");
private XPathExpression referenceExpression = XPathExpression.Compile("/document/reference");
private XPathExpression versionNodesExpression = XPathExpression.Compile("versions//version");
private XPathExpression apiGroupExpression = XPathExpression.Compile("string(apidata/@group)");
private XPathExpression topicdataGroupExpression = XPathExpression.Compile("string(topicdata/@group)");
private XPathExpression topicdataSubgroupExpression = XPathExpression.Compile("string(topicdata/@subgroup)");
private XPathExpression listTypeNameExpression = XPathExpression.Compile("string(apidata/@name)");
private XPathExpression apiNamespaceNameExpression = XPathExpression.Compile("string(containers/namespace/apidata/@name)");
private XPathExpression memberTypeNameExpression = XPathExpression.Compile("string(containers/type/apidata/@name)");
private XPathExpression listTopicElementNodesExpression = XPathExpression.Compile("elements//element");
private XPathExpression elementIdExpression = XPathExpression.Compile("string(@api)");
public PlatformsComponent(BuildAssembler assembler, XPathNavigator configuration)
: base(assembler, configuration)
{
// get the filter files
XPathNodeIterator filterNodes = configuration.Select("filter");
foreach (XPathNavigator filterNode in filterNodes)
{
string filterFiles = filterNode.GetAttribute("files", String.Empty);
if ((filterFiles == null) || (filterFiles.Length == 0))
throw new ConfigurationErrorsException("The filter/@files attribute must specify a path.");
ParseDocuments(filterFiles);
}
//WriteMessage(MessageLevel.Info, String.Format("Indexed {0} elements.", index.Count));
}
public void ParseDocuments(string wildcardPath)
{
string filterFiles = Environment.ExpandEnvironmentVariables(wildcardPath);
if ((filterFiles == null) || (filterFiles.Length == 0))
throw new ConfigurationErrorsException("The filter path is an empty string.");
WriteMessage(MessageLevel.Info, String.Format("Searching for files that match '{0}'.", filterFiles));
string directoryPart = Path.GetDirectoryName(filterFiles);
if (String.IsNullOrEmpty(directoryPart))
directoryPart = Environment.CurrentDirectory;
directoryPart = Path.GetFullPath(directoryPart);
string filePart = Path.GetFileName(filterFiles);
string[] files = Directory.GetFiles(directoryPart, filePart);
foreach (string file in files)
ParseDocument(file);
WriteMessage(MessageLevel.Info, String.Format("Found {0} files in {1}.", files.Length, filterFiles));
}
private void AddPlatformVersionFilter(string platformId, string versionId, XPathNavigator platformNode, string file)
{
Dictionary<string, VersionFilter> platformFrameworks;
if (!versionFilters.TryGetValue(platformId, out platformFrameworks))
{
platformFrameworks = new Dictionary<string, VersionFilter>();
versionFilters.Add(platformId, platformFrameworks);
}
try
{
VersionFilter filter;
XmlReader platformReader = platformNode.ReadSubtree();
platformReader.MoveToContent();
if (!platformFrameworks.TryGetValue(versionId, out filter))
{
filter = new VersionFilter(platformReader, versionId, file);
}
else
{
// if the platform already has a filter for this version, add the data from the current platform node
filter.AddPlatformNode(platformReader, file);
}
platformReader.Close();
platformFrameworks.Remove(versionId);
platformFrameworks.Add(versionId, filter);
}
catch (Exception e)
{
WriteMessage(MessageLevel.Error, e.Message);
}
}
private void ParseDocument(string file)
{
try
{
XPathDocument document = new XPathDocument(file);
XPathNodeIterator platformNodes = document.CreateNavigator().Select(platformQuery);
foreach (XPathNavigator platformNode in platformNodes)
{
string platformId = platformNode.GetAttribute("name", String.Empty);
string[] platformIds = platformId.Split(',');
string version = platformNode.GetAttribute("version", String.Empty);
string[] versionIds = version.Split(',');
for (int i = 0; i < versionIds.Length; i++)
{
for (int j = 0; j < platformIds.Length; j++)
{
XPathNavigator platformNodeClone = platformNode.Clone();
AddPlatformVersionFilter(platformIds[j], versionIds[i], platformNodeClone, file);
}
}
}
}
catch (Exception e)
{
WriteMessage(MessageLevel.Error, e.Message);
}
}
public override void Apply(XmlDocument document, string key)
{
XPathNavigator targetDoc = document.CreateNavigator();
XPathNavigator referenceNode = targetDoc.SelectSingleNode(referenceExpression);
string apiGroup = (string)referenceNode.Evaluate(apiGroupExpression);
string topicdataGroup = (string)referenceNode.Evaluate(topicdataGroupExpression);
string topicdataSubgroup = (string)referenceNode.Evaluate(topicdataSubgroupExpression);
// get the namespace and typename of the current type to locate the filter information that applies to the current topic
// For filtering inherited members, the platform filters use the namespace and typename of the inheriting type, not the declaring type,
string topicNamespaceName = (string)referenceNode.Evaluate(apiNamespaceNameExpression);
string topicTypeName = (string)referenceNode.Evaluate(memberTypeNameExpression);
// write platforms info for normal api topics (excluding memberlist and overload list topics
if (topicdataGroup != "list" && topicdataSubgroup != "DerivedTypeList" && (apiGroup == "type" || apiGroup == "member") && versionFilters.Count > 0)
{
WriteApiPlatforms(referenceNode, apiGroup, key, topicTypeName, topicNamespaceName);
}
// write platforms for elements//element nodes (member list and overload topics; not root or namespace)
if ((topicdataGroup == "list" && topicdataSubgroup != "DerivedTypeList") && apiGroup != "root" && versionFilters.Count > 0)
{
if (topicdataSubgroup != "overload")
topicTypeName = (string)referenceNode.Evaluate(listTypeNameExpression);
XPathNodeIterator elementNodes = referenceNode.Select(listTopicElementNodesExpression);
foreach (XPathNavigator elementNode in elementNodes)
{
string elementId = (string)elementNode.Evaluate(elementIdExpression);
WriteApiPlatforms(elementNode, "member", elementId, topicTypeName, topicNamespaceName);
}
}
}
private void WriteApiPlatforms(XPathNavigator referenceNode, string apiGroup, string key, string topicTypeName, string topicNamespaceName)
{
XPathNodeIterator versionNodes = referenceNode.Select(versionNodesExpression);
List<string> supportedPlatforms = new List<string>();
XmlWriter platformsWriter = referenceNode.AppendChild();
foreach (string platformId in versionFilters.Keys)
{
Dictionary<string, VersionFilter> filters = versionFilters[platformId];
bool included = false;
foreach (XPathNavigator versionNode in versionNodes)
{
string versionId = versionNode.GetAttribute("name", string.Empty);
VersionFilter filter;
if (filters.TryGetValue(versionId, out filter))
{
switch (apiGroup)
{
case "type":
included = filter.IsIncludedType(referenceNode, key, topicNamespaceName);
break;
case "member":
included = filter.IsIncludedMember(referenceNode, key, topicTypeName, topicNamespaceName);
break;
}
}
if (included)
break;
}
if (included)
supportedPlatforms.Add(platformId);
}
platformsWriter.WriteStartElement("platforms");
foreach (string platformId in supportedPlatforms)
{
platformsWriter.WriteElementString("platform", platformId);
}
platformsWriter.WriteEndElement();
platformsWriter.Close();
}
}
public abstract class InclusionFilter
{
public InclusionFilter(string file)
{
sourceFiles.Add(file);
}
protected List<string> sourceFiles = new List<string>();
protected static XPathExpression apiNameExpression = XPathExpression.Compile("string(apidata/@name)");
protected static XPathExpression apiParameterNodesExpression = XPathExpression.Compile("parameters/parameter");
protected static XPathExpression apiParameterTypeNameExpression = XPathExpression.Compile("string(.//type/@api)");
protected static XPathExpression apiParameterTemplateNameExpression = XPathExpression.Compile("string(.//template/@name)");
}
public class VersionFilter : InclusionFilter
{
public VersionFilter(XmlReader platformReader, string id, string file)
: base(file)
{
// platform/@version can only list included framework versions; excluding versions is not supported
included = true;
versionId = id;
AddPlatformNode(platformReader, file);
}
public void AddPlatformNode(XmlReader platformReader, string file)
{
XmlReader subtree = platformReader.ReadSubtree();
while (subtree.Read())
{
if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "namespace"))
{
string namespaceName = subtree.GetAttribute("name");
NamespaceFilter namespaceFilter;
if (!namespaceFilters.TryGetValue(namespaceName, out namespaceFilter))
{
namespaceFilter = new NamespaceFilter(subtree, file);
}
else
{
// if the version already has a filter for this namespace, add the data from the current namespace node
// unless the namespace node has a different @include value, in which case log a warning
string nsIncludeAttr = subtree.GetAttribute("include");
bool nsIncluded = Convert.ToBoolean(string.IsNullOrEmpty(nsIncludeAttr) ? "true" : nsIncludeAttr);
if (nsIncluded != namespaceFilter.Included)
{
// write warning message about conflicting filters
// ISSUE: how to invoke WriteMessage from here
Console.Write("");
}
else
{
namespaceFilter.AddNamespaceNode(subtree, file);
}
}
namespaceFilters.Remove(namespaceName);
namespaceFilters.Add(namespaceName, namespaceFilter);
}
}
subtree.Close();
}
private string versionId;
// platform/@version can only list included framework versions; excluding versions is not supported
private bool included;
private Dictionary<string, NamespaceFilter> namespaceFilters = new Dictionary<string, NamespaceFilter>();
public string VersionId
{
get
{
return (versionId);
}
}
public Dictionary<string, NamespaceFilter> NamespaceFilters
{
get
{
return (namespaceFilters);
}
}
/// <summary>
/// If we get here, we know that the platform supports this version, and the api is included in this version.
/// So returns true unless the type or its namespace are explicitly excluded by this version filter.
/// </summary>
/// <param name="referenceNode">The type's reflection data.</param>
/// <returns></returns>
public bool IsIncludedType(XPathNavigator referenceNode, string key, string topicNamespaceName)
{
// if we have a filter for the topic's namespace, check it
NamespaceFilter namespaceFilter;
if (namespaceFilters.TryGetValue(topicNamespaceName, out namespaceFilter))
{
return namespaceFilter.IsIncludedType(referenceNode, key);
}
return included;
}
public bool IsIncludedMember(XPathNavigator referenceNode, string key, string topicTypeName, string topicNamespaceName)
{
// if we have a filter for the topic's namespace, check it
NamespaceFilter namespaceFilter;
if (namespaceFilters.TryGetValue(topicNamespaceName, out namespaceFilter))
{
return namespaceFilter.IsIncludedMember(referenceNode, key, topicTypeName);
}
return included;
}
}
public class NamespaceFilter : InclusionFilter
{
public NamespaceFilter(XmlReader namespaceReader, string file)
: base(file)
{
//name = namespaceReader.GetAttribute("name");
string includeAttr = namespaceReader.GetAttribute("include");
included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
AddNamespaceNode(namespaceReader, file);
}
public void AddNamespaceNode(XmlReader namespaceReader, string file)
{
XmlReader subtree = namespaceReader.ReadSubtree();
while (subtree.Read())
{
if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "type"))
{
string typeName = subtree.GetAttribute("name");
TypeFilter typeFilter;
if (!typeFilters.TryGetValue(typeName, out typeFilter))
{
typeFilter = new TypeFilter(subtree, file);
}
else
{
// if the namespace already has a filter for this type, add the data from the current type node
// unless the type node has a different @include value, in which case log a warning
string typeIncludeAttr = subtree.GetAttribute("include");
bool typeIncluded = Convert.ToBoolean(string.IsNullOrEmpty(typeIncludeAttr) ? "true" : typeIncludeAttr);
if (typeIncluded != typeFilter.Included)
{
// write warning message about conflicting filters
// ISSUE: how to invoke WriteMessage from here
Console.Write("");
}
else
{
typeFilter.AddTypeNode(subtree, file);
}
}
typeFilters.Remove(typeName);
typeFilters.Add(typeName, typeFilter);
}
}
subtree.Close();
}
//private string name;
private bool included;
public bool Included
{
get
{
return (included);
}
}
private Dictionary<string, TypeFilter> typeFilters = new Dictionary<string, TypeFilter>();
public Dictionary<string, TypeFilter> TypeFilters
{
get
{
return (typeFilters);
}
}
public bool IsIncludedType(XPathNavigator referenceNode, string key)
{
// get the type's name
string typeName = (string)referenceNode.Evaluate(apiNameExpression);
// if we have a filter for that type, check it
TypeFilter typeFilter;
if (typeFilters.TryGetValue(typeName, out typeFilter))
{
return typeFilter.Included;
}
return included;
}
public bool IsIncludedMember(XPathNavigator referenceNode, string key, string topicTypeName)
{
// if we have a filter for the type, check it
TypeFilter typeFilter;
if (typeFilters.TryGetValue(topicTypeName, out typeFilter))
{
return typeFilter.IsIncludedMember(referenceNode, key);
}
return included;
}
}
public class TypeFilter : InclusionFilter
{
public TypeFilter(XmlReader typeReader, string file)
: base(file)
{
//name = typeReader.GetAttribute("name");
string includeAttr = typeReader.GetAttribute("include");
included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
AddTypeNode(typeReader, file);
}
public void AddTypeNode(XmlReader typeReader, string file)
{
XmlReader subtree = typeReader.ReadSubtree();
while (subtree.Read())
{
if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "member"))
{
string memberName = subtree.GetAttribute("name");
MemberFilter memberFilter;
if (!memberFilters.TryGetValue(memberName, out memberFilter))
{
memberFilter = new MemberFilter(subtree, file);
}
else
{
// if the type already has a filter for this member, add the data from the current member node
// unless the member node has a different @include value, in which case log a warning
string memberIncludeAttr = subtree.GetAttribute("include");
bool memberIncluded = Convert.ToBoolean(string.IsNullOrEmpty(memberIncludeAttr) ? "true" : memberIncludeAttr);
if (memberIncluded != memberFilter.Included)
{
// write warning message about conflicting filters
// ISSUE: how to invoke WriteMessage from here
Console.Write("");
}
else
{
memberFilter.AddMemberNode(subtree, file);
}
}
memberFilters.Remove(memberName);
memberFilters.Add(memberName, memberFilter);
}
}
subtree.Close();
}
//private string name;
private bool included;
public bool Included
{
get
{
return (included);
}
}
private Dictionary<string, MemberFilter> memberFilters = new Dictionary<string, MemberFilter>();
public Dictionary<string, MemberFilter> MemberFilters
{
get
{
return (memberFilters);
}
}
public bool IsIncludedMember(XPathNavigator referenceNode, string key)
{
// get the member's name
string memberName = (string)referenceNode.Evaluate(apiNameExpression);
// if we have a filter for that member, check it
MemberFilter memberFilter;
if (memberFilters.TryGetValue(memberName, out memberFilter))
{
return memberFilter.IsIncludedMember(referenceNode, key);
}
return included;
}
}
public class MemberFilter : InclusionFilter
{
public MemberFilter(XmlReader memberReader, string file)
: base(file)
{
//name = memberReader.GetAttribute("name");
string includeAttr = memberReader.GetAttribute("include");
included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
AddMemberNode(memberReader, file);
}
public void AddMemberNode(XmlReader memberReader, string file)
{
XmlReader subtree = memberReader.ReadSubtree();
while (subtree.Read())
{
if ((subtree.NodeType == XmlNodeType.Element) && (subtree.Name == "overload"))
{
string overloadId = subtree.GetAttribute("api");
string paramTypes = subtree.GetAttribute("types");
string paramNames = subtree.GetAttribute("names");
string overloadIncludeAttr = subtree.GetAttribute("include");
bool overloadIncluded = Convert.ToBoolean(string.IsNullOrEmpty(overloadIncludeAttr) ? "true" : overloadIncludeAttr);
// check for existing overload filters that identify the same overload
bool alreadyFiltered = false;
foreach (OverloadFilter overloadFilter in overloadFilters)
{
if (!string.IsNullOrEmpty(paramTypes) && paramTypes == overloadFilter.ParamTypes)
alreadyFiltered = true;
if (alreadyFiltered && (overloadIncluded != overloadFilter.Included))
{
// write warning message about conflicting filters
// ISSUE: how to invoke WriteMessage from here
Console.Write("");
}
}
if (!alreadyFiltered)
{
OverloadFilter overloadFilter = new OverloadFilter(subtree, file);
overloadFilters.Add(overloadFilter);
}
}
}
subtree.Close();
}
//private string name;
private bool included;
public bool Included
{
get
{
return (included);
}
}
private List<OverloadFilter> overloadFilters = new List<OverloadFilter>();
public bool IsIncludedMember(XPathNavigator referenceNode, string key)
{
if (overloadFilters.Count == 0)
return included;
// get the member's paramTypes string
// get the member's paramNames string
XPathNodeIterator parameterNodes = referenceNode.Select(apiParameterNodesExpression);
StringBuilder paramNames = new StringBuilder();
StringBuilder paramTypes = new StringBuilder();
int i = 0;
foreach (XPathNavigator parameterNode in parameterNodes)
{
i++;
paramNames.Append(parameterNode.GetAttribute("name", string.Empty));
if (i < parameterNodes.Count)
paramNames.Append(",");
// BUGBUG: code here and in the psx conversion transform is a quick hack; make it better
string arrayOf = (parameterNode.SelectSingleNode("arrayOf") == null) ? "" : "[]";
string typeName = (string)parameterNode.Evaluate(apiParameterTypeNameExpression);
if (string.IsNullOrEmpty(typeName))
typeName = (string)parameterNode.Evaluate(apiParameterTemplateNameExpression);
int basenameStart = typeName.LastIndexOf(':') + 1;
if (basenameStart > 0 && basenameStart < typeName.Length)
typeName = typeName.Substring(basenameStart);
paramTypes.Append(typeName + arrayOf);
if (i < parameterNodes.Count)
paramTypes.Append(",");
}
foreach (OverloadFilter overloadFilter in overloadFilters)
{
if (paramTypes.ToString() == overloadFilter.ParamTypes)
return overloadFilter.Included;
}
return included;
}
}
public class OverloadFilter : InclusionFilter
{
public OverloadFilter(XmlReader overloadReader, string file)
: base(file)
{
//name = overloadReader.GetAttribute("name");
string includeAttr = overloadReader.GetAttribute("include");
included = Convert.ToBoolean(string.IsNullOrEmpty(includeAttr) ? "true" : includeAttr);
overloadId = overloadReader.GetAttribute("api");
paramTypes = overloadReader.GetAttribute("types");
paramNames = overloadReader.GetAttribute("names");
}
//private string name;
private bool included;
public bool Included
{
get
{
return (included);
}
}
private string paramTypes;
public string ParamTypes
{
get
{
return (paramTypes);
}
}
private string paramNames;
public string ParamNames
{
get
{
return (paramNames);
}
}
private string overloadId;
public string OverloadId
{
get
{
return (overloadId);
}
}
}
}
|