using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace AppConfigVerifier
{
public class InclusionExclusionRuleSetCreator : RuleSetCreator
{
class RuleTokenComparer : IComparer<RuleToken>
{
#region IComparer<RuleToken> Members
public int Compare(RuleToken x, RuleToken y)
{
return new LocationComparer().Compare(x.Location, y.Location);
}
#endregion
}
//enum RuleType
//{
// Include,
// Exclude,
// IncludeAssembly,
// ExcludeAssembly
//}
class RuleToken
{
public Location Location { get; set; }
public Rule Rule { get; set; }
}
class RuleTokensExtension
{
readonly Regex includeExclude = new Regex(@"<!--\s*TypeVerification\s*(?<ExclusionType>Include|Exclude|IncludeAssembly|ExcludeAssembly)\s*(:\s*(?<TypeRegex>.*))?-->",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
//readonly Regex excludes = new Regex(@"<!--\s*Exclude\s*(:\s*(?<TypeRegex>.*))?-->", RegexOptions.Compiled);
readonly GlobalInfo context;
public RuleTokensExtension(GlobalInfo context)
{
this.context = context;
}
Regex ToRegex(string pattern, int index)
{
Regex regex = null;
try
{
regex = new Regex(pattern);
}
catch (ArgumentException ex)
{
context.Log.Send("Cannot parse the regex "
+ pattern + ", the corresponding rule will be ignored : "
+ ex.Message, index, index + pattern.Length - 1, LogType.Warning);
}
return regex;
}
List<RuleToken> tokens;
public IEnumerable<RuleToken> Tokens
{
get
{
if (tokens == null)
{
tokens = ScanContent();
}
return tokens;
}
}
List<RuleToken> ScanContent()
{
string content = context.Content;
var matches = includeExclude.Matches(content).OfType<Match>();
IEnumerable<RuleToken> allTokens = matches.Select(m => CreateToken(content, m));
//var excludesToken = excludes.Matches(content).OfType<Match>()
//.Select(m => CreateToken(RuleType.Exclude, content, m));
List<RuleToken> tokens = allTokens.Where(token => token.Rule != null).ToList();
tokens.Sort(new RuleTokenComparer());
return tokens;
}
RuleToken CreateToken(string content, Match m)
{
var token = new RuleToken();
token.Location = Location.GetLocation(m.Index, content);
//token.Rule = GetRule(m.Groups["ExclusionType"].Value, token.Location);
Group typeRegexGroup = m.Groups["TypeRegex"];
token.Rule = GetRule(m, m.Groups["ExclusionType"], typeRegexGroup, token.Location);
//ToRegex(typeRegexGroup.Value.Trim(), typeRegexGroup.Index);
//if(token.TypeRegex == null)
// token.IsInvalid = true;
return token;
}
Rule GetRule(Match ruleMatch, Group type, Group value, Location ruleLocation)
{
string valueStr = value.Value.Trim();
bool hasValue = value.Success;
Regex regex = null;
if (hasValue)
{
regex = ToRegex(valueStr, value.Index);
if (regex == null)
{
return null;
}
}
if (type.Value.Equals("Include", StringComparison.InvariantCultureIgnoreCase))
{
return new IncludeExcludeRule(regex, ruleLocation, true);
}
if (type.Value.Equals("Exclude", StringComparison.InvariantCultureIgnoreCase))
{
return new IncludeExcludeRule(regex, ruleLocation, false);
}
if (type.Value.Equals("ExcludeAssembly", StringComparison.InvariantCultureIgnoreCase))
{
if (!hasValue)
{
context.Log.Send("ExcludeAssembly should have an assembly specified",
ruleMatch.Index, LogType.Error);
return null;
}
AssemblyName assemblyName = Utility.StrToAssemblyName(valueStr);
if (assemblyName == null)
{
context.Log.Send(valueStr + " is not an assemblyName", value.Index, LogType.Error);
return null;
}
return new IncludeExcludeAssemblyRule(assemblyName, false);
}
if (type.Value.Equals("IncludeAssembly", StringComparison.InvariantCultureIgnoreCase))
{
if (!hasValue)
{
context.Log.Send("IncludeAssembly should have an assembly specified",
ruleMatch.Index, LogType.Error);
return null;
}
AssemblyName assemblyName = Utility.StrToAssemblyName(valueStr);
if (assemblyName == null)
{
context.Log.Send(valueStr + " is not an assemblyName", value.Index, LogType.Error);
return null;
}
return new IncludeExcludeAssemblyRule(assemblyName, true);
}
throw new NotSupportedException(type.Value);
}
}
class InclusionValidationRule : Rule
{
InclusionExclusionRuleSetCreator creator;
public InclusionValidationRule(InclusionExclusionRuleSetCreator creator)
{
this.creator = creator;
}
public override void Apply(LocalContext context)
{
if (context.GetExtension<InclusionExclusionExtension>().IsIncluded)
{
Validate(context);
}
}
void Validate(LocalContext context)
{
using (var loader = new AssemblyLoader())
{
TypeDefinition typeDefinition;
try
{
typeDefinition = Utility.GetTypeDefinition(context.TypeName);
}
catch (FileLoadException)
{
context.Log.Send("Cannot parse assembly name in " + context.TypeName, LogType.Error);
return;
}
if (typeDefinition.AssemblyName == null)
{
SearchTypeInDefaultAssemblies(typeDefinition.TypeName, context, loader);
return;
}
int indexOfAssemblyName = context.Token.TypeName.IndexOf(',') + 1;
while (context.Token.TypeName[indexOfAssemblyName] == ' ')
{
indexOfAssemblyName++;
}
AssemblyName bestMatch = null;
AssemblyName assemblyName = typeDefinition.AssemblyName;
foreach (AssemblyName knownName in creator.KnownNames)
{
if (assemblyName.IsSubsetOf(knownName))
{
bestMatch = knownName;
break;
}
}
if (bestMatch == null)
{
context.Log.Send("Assembly : " + assemblyName.ToString()
+ " not referenced in the project", indexOfAssemblyName,
LogType.Error);
return;
}
bool typeIsFound;
try
{
string assemblyPath = creator.assembliesDictionary[bestMatch];
typeIsFound = loader.HasType(assemblyPath, typeDefinition.TypeName);
}
catch (FileNotFoundException)
{
context.Log.Send(bestMatch
+ " is referenced by the project but not found at "
+ creator.assembliesDictionary[bestMatch],
indexOfAssemblyName, LogType.Error);
return;
}
if (!typeIsFound)
{
context.Log.Send(typeDefinition.TypeName + " was not found inside "
+ assemblyName.ToString(), LogType.Error);
return;
}
}
}
void SearchTypeInDefaultAssemblies(string typeName, LocalContext context, AssemblyLoader loader)
{
bool assemblyFound = false;
foreach (string defaultReference in creator.DefaultReferences)
{
try
{
assemblyFound = loader.HasType(defaultReference, typeName);
if (assemblyFound)
{
break;
}
}
catch (FileNotFoundException)
{
context.Log.Send("Default assembly not found : "
+ defaultReference, LogType.Warning);
}
}
if (!assemblyFound)
{
context.Log.Send("No type " + typeName
+ " found in default assemblies", LogType.Error);
}
}
}
readonly Dictionary<AssemblyName, String> assembliesDictionary = new Dictionary<AssemblyName, string>();
readonly Dictionary<AssemblyName, String> defaultAssembliesDictionary = new Dictionary<AssemblyName, string>();
public InclusionExclusionRuleSetCreator(string[] references, string[] defaultReferences)
{
FillDictionary(references.Union(defaultReferences).ToArray(), assembliesDictionary);
FillDictionary(defaultReferences, defaultAssembliesDictionary);
}
void FillDictionary(string[] references, Dictionary<AssemblyName, String> dictionary)
{
foreach (string reference in references)
{
AssemblyName name = null;
try
{
name = AssemblyName.GetAssemblyName(reference);
}
catch (BadImageFormatException) //Not Managed
{
/* Ignore. */
}
if (name != null)
{
dictionary.Add(name, reference);
}
}
}
public string[] References
{
get
{
return assembliesDictionary.Values.ToArray();
}
}
public string[] DefaultReferences
{
get
{
return defaultAssembliesDictionary.Values.ToArray();
}
}
public AssemblyName[] DefaultKnownNames
{
get
{
return defaultAssembliesDictionary.Keys.ToArray();
}
}
public AssemblyName[] KnownNames
{
get
{
return assembliesDictionary.Keys.ToArray();
}
}
public override IEnumerable<Rule> CreateRules(LocalContext context)
{
var extension = context.GlobalInfo.GetExtension<RuleTokensExtension>();
List<Rule> rules = extension.Tokens.Select(token => token.Rule).ToList();
rules.Add(new InclusionValidationRule(this));
return rules;
}
}
}