/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Globalization;
using System.Text.RegularExpressions;
using dnlib.DotNet;
namespace dnSpy.Contracts.Search {
///
/// factory
///
static class SearchComparerFactory {
///
/// Creates a
///
/// Search text
/// true if case sensitive
/// true to match whole words
/// true to match any word
///
public static ISearchComparer Create(string searchText, bool caseSensitive, bool matchWholeWords, bool matchAnyWords) {
var regex = TryCreateRegEx(searchText, caseSensitive);
if (regex is not null)
return new RegExSearchComparer(regex);
var searchTerms = searchText.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (matchAnyWords)
return new OrSearchComparer(searchTerms, caseSensitive, matchWholeWords);
return new AndSearchComparer(searchTerms, caseSensitive, matchWholeWords);
}
///
/// Creates a that compares literals
///
/// Search text
/// true if case sensitive
/// true to match whole words
/// true to match any word
///
public static ISearchComparer CreateLiteral(string searchText, bool caseSensitive, bool matchWholeWords, bool matchAnyWords) {
var s = searchText.Trim();
var val64 = TryParseInt64(s);
if (val64 is not null)
return new IntegerLiteralSearchComparer(val64.Value);
var uval64 = TryParseUInt64(s);
if (uval64 is not null)
return new IntegerLiteralSearchComparer(unchecked((long)uval64.Value));
if (double.TryParse(s, out double dbl))
return new DoubleLiteralSearchComparer(dbl);
if (s.Length >= 2 && s[0] == '"' && s[s.Length - 1] == '"')
s = s.Substring(1, s.Length - 2);
else {
var regex = TryCreateRegEx(s, caseSensitive);
if (regex is not null)
return new RegExStringLiteralSearchComparer(regex);
}
return new StringLiteralSearchComparer(s, caseSensitive, matchWholeWords);
}
static Regex? TryCreateRegEx(string s, bool caseSensitive) {
s = s.Trim();
if (s.Length > 2 && s[0] == '/' && s[s.Length - 1] == '/') {
var regexOpts = RegexOptions.Compiled;
if (!caseSensitive)
regexOpts |= RegexOptions.IgnoreCase;
try {
return new Regex(s.Substring(1, s.Length - 2), regexOpts);
}
catch (ArgumentException) {
}
}
return null;
}
static long? TryParseInt64(string s) {
bool isSigned = s.StartsWith("-", StringComparison.OrdinalIgnoreCase);
if (isSigned)
s = s.Substring(1);
if (s != s.Trim())
return null;
ulong val;
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) {
s = s.Substring(2);
if (s != s.Trim())
return null;
if (!ulong.TryParse(s, NumberStyles.HexNumber, null, out val))
return null;
}
else {
if (!ulong.TryParse(s, out val))
return null;
}
if (isSigned) {
if (val > (ulong)long.MaxValue + 1)
return null;
return unchecked(-(long)val);
}
else {
if (val > long.MaxValue)
return null;
return (long)val;
}
}
static ulong? TryParseUInt64(string s) {
ulong val;
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) {
s = s.Substring(2);
if (s != s.Trim())
return null;
if (!ulong.TryParse(s, NumberStyles.HexNumber, null, out val))
return null;
}
else {
if (!ulong.TryParse(s, out val))
return null;
}
return val;
}
}
sealed class RegExStringLiteralSearchComparer : ISearchComparer {
readonly Regex regex;
public RegExStringLiteralSearchComparer(Regex regex) => this.regex = regex ?? throw new ArgumentNullException(nameof(regex));
public bool IsMatch(string? text, object? obj) {
if (obj is IHasConstant hc && hc.Constant is not null)
obj = hc.Constant.Value;
return obj is string s && regex.IsMatch(s);
}
}
sealed class StringLiteralSearchComparer : ISearchComparer {
readonly string str;
readonly StringComparison stringComparison;
readonly bool matchWholeString;
public StringLiteralSearchComparer(string s, bool caseSensitive = false, bool matchWholeString = false) {
str = s ?? throw new ArgumentNullException(nameof(s));
stringComparison = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
this.matchWholeString = matchWholeString;
}
public bool IsMatch(string? text, object? obj) {
if (obj is IHasConstant hc && hc.Constant is not null)
obj = hc.Constant.Value;
if (!(obj is string s))
return false;
if (matchWholeString)
return s.Equals(str, stringComparison);
return s.IndexOf(str, stringComparison) >= 0;
}
}
sealed class IntegerLiteralSearchComparer : ISearchComparer {
readonly long searchValue;
public IntegerLiteralSearchComparer(long value) => searchValue = value;
public bool IsMatch(string? text, object? obj) {
if (obj is IHasConstant hc && hc.Constant is not null)
obj = hc.Constant.Value;
if (obj is null)
return false;
switch (Type.GetTypeCode(obj.GetType())) {
case TypeCode.Char: return searchValue == (char)obj;
case TypeCode.SByte: return searchValue == (sbyte)obj;
case TypeCode.Byte: return searchValue == (byte)obj;
case TypeCode.Int16: return searchValue == (short)obj;
case TypeCode.UInt16: return searchValue == (ushort)obj;
case TypeCode.Int32: return searchValue == (int)obj;
case TypeCode.UInt32: return searchValue == (uint)obj;
case TypeCode.Int64: return searchValue == (long)obj;
case TypeCode.UInt64: return searchValue == unchecked((long)(ulong)obj);
case TypeCode.Single: return searchValue == (float)obj;
case TypeCode.Double: return searchValue == (double)obj;
case TypeCode.Decimal: return searchValue == (decimal)obj;
case TypeCode.DateTime: return new DateTime(searchValue) == (DateTime)obj;
}
return false;
}
}
sealed class DoubleLiteralSearchComparer : ISearchComparer {
readonly double searchValue;
public DoubleLiteralSearchComparer(double value) => searchValue = value;
public bool IsMatch(string? text, object? obj) {
if (obj is IHasConstant hc && hc.Constant is not null)
obj = hc.Constant.Value;
if (obj is null)
return false;
switch (Type.GetTypeCode(obj.GetType())) {
case TypeCode.Char: return searchValue == (char)obj;
case TypeCode.SByte: return searchValue == (sbyte)obj;
case TypeCode.Byte: return searchValue == (byte)obj;
case TypeCode.Int16: return searchValue == (short)obj;
case TypeCode.UInt16: return searchValue == (ushort)obj;
case TypeCode.Int32: return searchValue == (int)obj;
case TypeCode.UInt32: return searchValue == (uint)obj;
case TypeCode.Int64: return searchValue == (long)obj;
case TypeCode.UInt64: return searchValue == (ulong)obj;
case TypeCode.Single: return searchValue == (float)obj;
case TypeCode.Double: return searchValue == (double)obj;
}
return false;
}
}
sealed class RegExSearchComparer : ISearchComparer {
readonly Regex regex;
public RegExSearchComparer(Regex regex) => this.regex = regex ?? throw new ArgumentNullException(nameof(regex));
public bool IsMatch(string? text, object? obj) {
if (text is null)
return false;
return regex.IsMatch(text);
}
}
sealed class AndSearchComparer : ISearchComparer {
readonly string[] searchTerms;
readonly StringComparison stringComparison;
readonly bool matchWholeWords;
public AndSearchComparer(string[] searchTerms, bool caseSensitive = false, bool matchWholeWords = false)
: this(searchTerms, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase, matchWholeWords) {
}
public AndSearchComparer(string[] searchTerms, StringComparison stringComparison, bool matchWholeWords = false) {
this.searchTerms = searchTerms;
this.stringComparison = stringComparison;
this.matchWholeWords = matchWholeWords;
}
public bool IsMatch(string? text, object? obj) {
if (text is null)
return false;
foreach (var searchTerm in searchTerms) {
if (matchWholeWords) {
if (!text.Equals(searchTerm, stringComparison))
return false;
}
else {
if (text.IndexOf(searchTerm, stringComparison) < 0)
return false;
}
}
return true;
}
}
sealed class OrSearchComparer : ISearchComparer {
readonly string[] searchTerms;
readonly StringComparison stringComparison;
readonly bool matchWholeWords;
public OrSearchComparer(string[] searchTerms, bool caseSensitive = false, bool matchWholeWords = false)
: this(searchTerms, caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase, matchWholeWords) {
}
public OrSearchComparer(string[] searchTerms, StringComparison stringComparison, bool matchWholeWords = false) {
this.searchTerms = searchTerms;
this.stringComparison = stringComparison;
this.matchWholeWords = matchWholeWords;
}
public bool IsMatch(string? text, object? obj) {
if (text is null)
return false;
foreach (var searchTerm in searchTerms) {
if (matchWholeWords) {
if (text.Equals(searchTerm, stringComparison))
return true;
}
else {
if (text.IndexOf(searchTerm, stringComparison) >= 0)
return true;
}
}
return false;
}
}
}