Prechádzať zdrojové kódy

+ patch by Bart B to add an fpmasks unit to fpc, part of #40093

florian 2 rokov pred
rodič
commit
1e25375983
1 zmenil súbory, kde vykonal 1423 pridanie a 258 odobranie
  1. 1423 258
      packages/fpindexer/src/fpmasks.pp

+ 1423 - 258
packages/fpindexer/src/fpmasks.pp

@@ -1,99 +1,417 @@
 {
- /***************************************************************************
-                                  fpmasks.pas
-                                  ---------
-
-  Moved here from LCL
- ***************************************************************************/
-
- *****************************************************************************
- *                                                                           *
- *  This file is part of the Lazarus Component Library (LCL)                 *
- *                                                                           *
- *  See the file COPYING.modifiedLGPL.txt, included in this distribution,    *
- *  for details about the copyright.                                         *
- *                                                                           *
- *  This program 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.                     *
- *                                                                           *
- *****************************************************************************
+* ***************************************************************************
+*  This file is an adapted version of the masks unit which is part of part  *
+*  of the LazUtils package of Lazarus                                       *
+*                                                                           *
+*  See the file COPYING.modifiedLGPL.txt, included in this distribution,    *
+*  for details about the license.                                           *
+*                                                                           *
+*  This program 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.                     *
+*****************************************************************************
+
+ Implement TMask and related classes.
+ It is fast and optimized, and fully supports Unicode. Also supports
+  DOS/Windows compatible mask which behaves differently from standard mask.
+
+ Original Author: José Mejuto
+ Adapted for fpc by Bart Broersma
 }
 unit FPMasks;
 
 {$mode objfpc}{$H+}
+{.$define debug_maskcompiled}
+{.$define debug_anycharornone}
 
 interface
 
 uses
-  // For Smart Linking: Do not use the LCL!
-  Classes, SysUtils, Contnrs;
+  Classes, SysUtils, Contnrs
+  {$ifdef unix}, cwstring{$endif unix}  //we need a widestring manager
+  ;
+
+type
+  { EMaskError }
+
+  EMaskError=class(EConvertError)
+  public
+    type
+      TMaskExceptionCode=(mecInternalError,
+                          mecInvalidCharMask,
+                          mecMissingClose,
+                          mecIncompleteMask,
+                          mecInvalidEscapeChar
+                          //mecInvalidUTF8Sequence
+                          );
+  protected
+    FCode: TMaskExceptionCode;
+  public
+    constructor Create(const msg: Utf8String; aCode: TMaskExceptionCode);
+    constructor CreateFmt(const msg: Utf8String; args: array of const; aCode: TMaskExceptionCode);
+    property Code: TMaskExceptionCode read FCode;
+  end;
+
+
+  TMaskOpCode=(mocAnyChar,          //treat ? as a wildcard to match exactly one char
+               mocAnyCharOrNone,    //treat [?] to match any char or the absence of a char
+               mocAnyText,          //treat * as a wildcard to mach zero or any mumber of chars
+               mocRange,            //treat [a-c] to match either 'a', 'b' or 'c'. '-' is always treated as a range indicator.
+                                    //to have a literal '-' in a range, you must escape it with EscapeChar (defaults to '\'): [+-\-] matches '+', ',', or '-'.
+               mocSet,              //treat [a-c] to match either 'a', '-' or 'c'
+               mocNegateGroup,      //treat [!a-c] to not match 'a', 'b', or 'c', but match any other char. Requires mocRange and/or mocSet
+               mocEscapeChar        //treat EscapeChar (defaults to '\') to take the next char as a literal, so '\*' is treated as a literal '*'.
+               );
+  TMaskOpCodes=set of TMaskOpCode;
+
+
+const
+  AllMaskOpCodes=[mocAnyChar,
+                  mocAnyCharOrNone,
+                  mocAnyText,
+                  mocRange,
+                  mocSet,
+                  mocNegateGroup,
+                  mocEscapeChar];
+
 
+  // Match [x] literally, not as a range.
+  // Leave out mocAnyCharOrNone, mocRange and mocSet.
+  MaskOpCodesDisableRange=[mocAnyChar,
+                           mocAnyText,
+                           mocNegateGroup,
+                           mocEscapeChar];
+
+  // Interpret [?] as literal question mark instead of 0..1 chars wildcard.
+  // Disable backslash escaping characters like "\?".
+  // Leave out mocAnyCharOrNone and mocEscapeChar
+  MaskOpCodesNoEscape=[mocAnyChar,
+                       mocAnyText,
+                       mocRange,
+                       mocSet,
+                       mocNegateGroup];
+
+  DefaultMaskOpCodes=MaskOpCodesNoEscape;
+
+
+  (*
+    Windows mask works in a different mode than regular mask, it has too many
+    quirks and corner cases inherited from CP/M, then adapted to DOS (8.3) file
+    names and adapted again for long file names.
+
+    Anyth?ng.abc    = "?" matches exactly 1 char
+    Anyth*ng.abc    = "*" matches 0 or more of chars
+
+    ------- Quirks -------
+   *)
 type
-  TMaskCharType = (mcChar, mcCharSet, mcAnyChar, mcAnyText);
-  
-  TCharSet = set of Char;
-  PCharSet = ^TCharSet;
-  
-  TMaskChar = record
-    case CharType: TMaskCharType of
-      mcChar: (CharValue: Char);
-      mcCharSet: (Negative: Boolean; SetValue: PCharSet);
-      mcAnyChar, mcAnyText: ();
+  TWindowsQuirk=(wqAnyExtension,      // Anything*.*     : ".*" is removed. Also makes "foo.*" match "foo"
+                 wqFilenameEnd,       // Anything??.abc  : "?" matches 1 or 0 chars (except '.')
+                                      // (Not the same as "Anything*.abc", but the same
+                                      // as regex "Anything.{0,2}\.abc")
+                                      // Internally converted to "Anything[??].abc"
+
+                 wqExtension3More,    // Anything.abc    : Matches "Anything.abc" but also "Anything.abc*" (3 char extension)
+                                      // Anything.ab     : Matches "Anything.ab" and never "anything.abcd"
+                                      // *.pas           : Matches "Unit1.pas.bak". Not good.
+                 wqEmptyIsAny,        // ""              : Empty string matches anything "*"
+                 wqAllByExtension,    // .abc            : Runs as "*.abc" (Not in use anymore)
+                 wqNoExtension);      // Anything*.      : Matches "Anything*" without extension
+  TWindowsQuirks=set of TWindowsQuirk;
+
+const
+  AllWindowsQuirks=[wqAnyExtension,
+                    wqFilenameEnd,
+                    wqExtension3More,
+                    wqEmptyIsAny,
+                    wqAllByExtension,
+                    wqNoExtension];
+
+  // Leave out wqExtension3More and wqAllByExtension
+  DefaultWindowsQuirks=[wqAnyExtension,
+                        wqEmptyIsAny,
+                        wqNoExtension];
+
+  //Maybe move to rtlconst unit?
+  rsInvalidCharMaskAt = 'Invalid character "%s" in mask (offset %d).';
+  rsInvalidCharMask = 'Invalid character "%s" in mask.';
+  rsMissingCloseCharMaskAt = 'Missing closing character "%s" in mask (offset %d).';
+  rsMissingCloseCharMask = 'Missing closing character "%s" in mask.';
+  rsIncompleteMask = 'Reached end of mask, but missing close/escape sequence.';
+  rsInvalidEscapeChar = 'Escape character must be ASCII <= 127.';
+  rsInternalError = 'Internal error in %s.';
+
+type
+
+  { TMaskBase }
+
+  TMaskBase = class
+  private
+    procedure SetAutoReverseRange(AValue: Boolean);
+    procedure SetCaseSensitive(AValue: Boolean);
+    procedure SetMaskEscapeChar(AValue: Char);
+    procedure SetMaskOpCodesAllowed(AValue: TMaskOpCodes);
+  protected
+    // Literal = It must match
+    // Range = Match any char in the range
+    // Negate = Negate match in a group
+    // AnyChar = It matches any char, but one must match
+    // AnyCharOrNone = Matches one or none char (only in a group)
+    // AnyCharToNext = Matches any chars amount, if fail, restart in the
+    //                 next position up to finish the mask or the matched string
+    // OptionalChar = Optional char
+    // CharsGroupBegin = Begin optional chars or ranges "["
+    // CharsGroupEnd = End optional chars or ranges "]"
+    type
+    TMaskParsedCode = (
+      Literal=0,
+      Range=1,
+      Negate=2,
+      AnyChar=3,
+      AnyCharOrNone=4,
+      AnyCharToNext=5,
+      OptionalChar=6,
+      CharsGroupBegin=10,
+      CharsGroupEnd=11
+    );
+    TMaskFailCause = (
+      mfcSuccess,
+      mfcMatchStringExhausted,
+      mfcMaskExhausted,
+      mfcMaskNotMatch,
+      mfcUnexpectedEnd
+    );
+    const
+      GROW_BY=100;
+      DefaultSpecialChars=['*', '?', '['];
+    procedure Add(aLength: integer; aData: PBYTE);
+    procedure Add(aValue: integer);
+    procedure Add(aValue: TMaskParsedCode);
+    procedure IncrementLastCounterBy(aOpcode: TMaskParsedCode; aValue: integer);
+  protected
+    fCaseSensitive: Boolean;
+    fAutoReverseRange: Boolean; // If enabled, range [z-a] is interpreted as [a-z]
+    fMaskIsCompiled: Boolean;
+    fMaskCompiled: TBytes;
+    fMaskCompiledIndex: integer;
+    fMaskCompiledAllocated: integer;
+    fMaskCompiledLimit: integer;
+    fMaskLimit: integer;
+    fMatchStringLimit: integer;
+    fMatchMinimumLiteralBytes: SizeInt;
+    fMatchMaximumLiteralBytes: SizeInt;
+    fMaskOpcodesAllowed: TMaskOpCodes;
+    // EscapeChar forces next char to be a literal one, not a wildcard.
+    fMaskEscapeChar: Char;
+    procedure PrepareCompile;
+    class procedure Exception_InvalidCharMask(const aMaskChar: Utf8String; aOffset: integer=-1); static;
+    class procedure Exception_MissingCloseChar(const aMaskChar: Utf8String; aOffset: integer=-1); static;
+    class procedure Exception_IncompleteMask(); static;
+    class procedure Exception_InvalidEscapeChar(); static;
+    procedure Exception_InternalError();
+  public
+    constructor Create; // Sets value of CaseSensistive to System.FileNameCaseSensitive
+    constructor Create(aCaseSensitive: Boolean;
+      aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes);
+  public
+    property CaseSensitive: Boolean read fCaseSensitive write SetCaseSensitive;
+    property AutoReverseRange: Boolean read fAutoReverseRange write SetAutoReverseRange;
+    property EscapeChar: Char read fMaskEscapeChar write SetMaskEscapeChar;  //Must be lower ASCII (#0-#127)
+    property MaskOpCodes: TMaskOpCodes read fMaskOpcodesAllowed write SetMaskOpCodesAllowed;
   end;
-  
-  TMaskString = record
-    MinLength: Integer;
-    MaxLength: Integer;
-    Chars: Array of TMaskChar;
+
+  { TMaskUTF8 }
+
+  TMaskUTF8 = class (TMaskBase)
+  private
+    fMatchString: Utf8String;
+    // Used by Compile.
+    fMaskInd: Integer;
+    fCPLength: integer;    // Size of codepoint.
+    fLastOC: TMaskParsedCode;  // Last OpCode.
+    fMask: Utf8String;
+    procedure AddAnyChar;
+    procedure AddLiteral;
+    procedure AddRange(lFirstRange, lSecondRange: Integer);
+    procedure AddRangeReverse(lFirstRange, lSecondRange: Integer);
+    procedure CompileRange;
+    procedure CompileEscapeCharPlusLiteral;
+    procedure CompileSpecialChars;
+    procedure CompileAnyCharOrNone(QChar: Char; BracketsRequired: Boolean);
+    function GetMask: Utf8String; virtual;
+    procedure SetMask(AValue: Utf8String); virtual;
+  protected
+    fOriginalMask: Utf8String;
+    function IsSpecialChar({%H-}AChar: Char): Boolean; virtual;
+    procedure CompileOtherSpecialChars; virtual;
+    class function CompareUTF8Sequences(const P1,P2: PChar): integer; static;
+    function intfMatches(aMatchOffset: integer; aMaskIndex: integer): TMaskFailCause; //override;
+  public
+
+    {$IFDEF debug_maskcompiled}
+    procedure DumpMaskCompiled;
+    {$ENDIF}
+
+    constructor Create(const aMask: Utf8String); overload;
+    constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean); overload;
+    constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes); virtual; overload;
+
+    procedure Compile; virtual;
+    function Matches(const aStringToMatch: Utf8String): Boolean; virtual;
+  public
+    property Mask: Utf8String read GetMask write SetMask;
   end;
 
-  { TMask }
+  TMask = class(TMaskUTF8);
+
+  { TWindowsMaskUTF8 }
 
-  TMask = class
+  TWindowsMaskUTF8=class(TMask)
   private
-    FMask: TMaskString;
+    procedure SetMask(AValue: Utf8String); override;
+    function GetMask: Utf8String; override;
+    procedure SetWindowsQuirkAllowed(AValue: TWindowsQuirks);
+  protected
+    fWindowsQuirkAllowed: TWindowsQuirks;
+    fWindowsQuirkInUse: TWindowsQuirks;
+    fWindowsMask: Utf8String;
+    procedure CompileOtherSpecialChars; override;
+    function IsSpecialChar(AChar: Char): Boolean; override;
+    class procedure SplitFileNameExtension(const aSourceFileName: Utf8String;
+      out aFileName: Utf8String; out aExtension: Utf8String; aIsMask: Boolean=False); static;
   public
-    constructor Create(const AValue: UTF8string);
-    destructor Destroy; override;
-    
-    function Matches(const AFileName: UTF8string): Boolean;
+    constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes); override;
+    constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks);
+
+    procedure Compile; override;
+    function Matches(const aFileName: Utf8String): Boolean; override;
+  public
+    property Quirks: TWindowsQuirks read fWindowsQuirkAllowed write SetWindowsQuirkAllowed;
   end;
-  
+
+  TWindowsMask = class(TWindowsMaskUTF8);
+
+  TMaskClass = class of TMaskUtf8;
+
   { TParseStringList }
 
   TParseStringList = class(TStringList)
   public
-    constructor Create(const AText, ASeparators: UTF8string);
+    constructor Create(const AText, ASeparators: Utf8String);
   end;
-  
+
   { TMaskList }
 
   TMaskList = class
   private
-    FMasks: TObjectList;
+    fAutoReverseRange: Boolean;
+    fMasks: TObjectList;
+    FMaskClass: TMaskClass;
+    fMask: Utf8String;
+    fSeparator: Char;
+    fCaseSensitive: Boolean;
+    fMaskOpcodes: TMaskOpcodes;
     function GetCount: Integer;
     function GetItem(Index: Integer): TMask;
+    procedure SetAutoReverseRange(AValue: Boolean);
+    procedure SetCaseSensitive(AValue: Boolean);
+    procedure SetMask(AValue: Utf8String); virtual;
+    procedure SetMaskOpCodes(AValue: TMaskOpCodes);
+  protected
+    function GetMaskClass: TMaskClass; virtual;
+    procedure AddMasksToList(const aValue: Utf8String; aSeparator: Char; CaseSensitive: Boolean;
+      aOpcodesAllowed: TMaskOpCodes); virtual;
   public
-    constructor Create(const AValue: UTF8string; ASeparator: Char = ';');
+    constructor Create(const aValue: Utf8String; aSeparator: Char=';'; CaseSensitive: Boolean=False;
+      aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes); virtual;
+
     destructor Destroy; override;
-    
-    function Matches(const AFileName: UTF8string): Boolean;
-    
+
+    function Matches(const AFileName: Utf8String): Boolean;
+
     property Count: Integer read GetCount;
     property Items[Index: Integer]: TMask read GetItem;
+    property Mask: Utf8String read fMask write SetMask;
+    property MaskOpCodes: TMaskOpCodes read fMaskOpCodes write SetMaskOpCodes;
+    property AutoReverseRange: Boolean read fAutoReverseRange write SetAutoReverseRange;
+    property CaseSensitive: Boolean read fCaseSensitive write SetCaseSensitive;
   end;
 
-function MatchesMask(const FileName, Mask: UTF8string): Boolean;
-function MatchesMaskList(const FileName, Mask: UTF8string; Separator: Char = ';'): Boolean;
+
+  { TWindowsMaskList }
+
+  TWindowsMaskList = class(TMaskList)
+  private
+    fWindowsQuirks: TWindowsQuirks;
+    procedure SetQuirks(AValue: TWindowsQuirks);
+  protected
+    function GetMaskClass: TMaskClass; override;
+    procedure AddMasksToList(const aValue: Utf8String; aSeparator: Char; aCaseSensitive: Boolean;
+      aOpcodesAllowed: TMaskOpCodes); override;
+  public
+    constructor Create(const aValue: Utf8String; aSeparator: Char=';'; aCaseSensitive: Boolean=False;
+      aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes); override;
+
+    constructor Create(const aValue: Utf8String; aSeparator: Char; aCaseSensitive: Boolean;
+    aOpcodesAllowed: TMaskOpCodes;
+    aWindowsQuirksAllowed: TWindowsQuirks); overload;
+
+    property Quirks: TWindowsQuirks read fWindowsQuirks write SetQuirks;
+  end;
+
+function MatchesMask(const FileName, Mask: Utf8String): Boolean; // Value of CaseSensitive equals System.FileNameCaseSesnsitive!
+
+function MatchesMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean;
+  aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes): Boolean;
+
+function MatchesWindowsMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean=False;
+  aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes;
+  aWindowsQuirksAllowed: TWindowsQuirks=DefaultWindowsQuirks): Boolean;
+
+function MatchesMaskList(const FileName, Mask: Utf8String): Boolean;  // Value of CaseSensitive equals System.FileNameCaseSesnsitive!
+
+function MatchesMaskList(const FileName, Mask: Utf8String; Separator: Char;
+  CaseSensitive: Boolean=False;
+  aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes): Boolean;
+
+function MatchesWindowsMaskList(const FileName, Mask: Utf8String; Separator: Char=';';
+  CaseSensitive: Boolean=False;
+  aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes;
+  aWindowsQuirksAllowed: TWindowsQuirks=DefaultWindowsQuirks): Boolean;
+
 
 implementation
 
-function MatchesMask(const FileName, Mask: UTF8string): Boolean;
+//copied form LazUtf8
+//does NOT check the validity of a codepoint!
+function UTF8CodepointSizeFast(p: PChar): integer; inline;
+begin
+  case p^ of
+    #0..#191   : Result := 1;
+    #192..#223 : Result := 2;
+    #224..#239 : Result := 3;
+    #240..#247 : Result := 4;
+    else Result := 1; // An optimization + prevents compiler warning about uninitialized Result.
+  end;
+end;
+
+function Utf8LowerCase(const S: Utf8String): Utf8String;
+begin
+  Result := Utf8Encode(WideLowerCase(Utf8Decode(S)));
+end;
+
+
+function MatchesMask(const FileName, Mask: Utf8String): Boolean;
+begin
+  Result := MatchesMask(FileName, Mask, System.FileNameCaseSensitive, DefaultMaskOpCodes);
+end;
+
+function MatchesMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean;
+  aOpcodesAllowed: TMaskOpCodes): Boolean;
 var
   AMask: TMask;
 begin
-  AMask := TMask.Create(Mask);
+  AMask := TMask.Create(Mask, CaseSensitive, aOpcodesAllowed);
   try
     Result := AMask.Matches(FileName);
   finally
@@ -101,11 +419,32 @@ begin
   end;
 end;
 
-function MatchesMaskList(const FileName, Mask: UTF8string; Separator: Char): Boolean;
+
+
+function MatchesWindowsMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean;
+  aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks): Boolean;
+var
+  AMask: TWindowsMask;
+begin
+  AMask := TWindowsMask.Create(Mask, CaseSensitive, aOpcodesAllowed, aWindowsQuirksAllowed);
+  try
+    Result := AMask.Matches(FileName);
+  finally
+    AMask.Free;
+  end;
+end;
+
+function MatchesMaskList(const FileName, Mask: Utf8String): Boolean;
+begin
+  Result := MatchesMaskList(FileName, Mask, ';', System.FileNameCaseSensitive, DefaultMaskOpCodes);
+end;
+
+function MatchesMaskList(const FileName, Mask: Utf8String; Separator: Char;
+  CaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes): Boolean;
 var
   AMaskList: TMaskList;
 begin
-  AMaskList := TMaskList.Create(Mask, Separator);
+  AMaskList := TMaskList.Create(Mask, Separator, CaseSensitive, aOpcodesAllowed);
   try
     Result := AMaskList.Matches(FileName);
   finally
@@ -113,293 +452,1119 @@ begin
   end;
 end;
 
-{ TMask }
 
-constructor TMask.Create(const AValue: UTF8string);
+function MatchesWindowsMaskList(const FileName, Mask: Utf8String; Separator: Char;
+  CaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks): Boolean;
 var
-  I: Integer;
-  SkipAnyText: Boolean;
-  
-  procedure CharSetError;
-  begin
-    raise EConvertError.CreateFmt('Invalid charset %s', [AValue]);
+  AMaskList: TWindowsMaskList;
+begin
+  AMaskList := TWindowsMaskList.Create(Mask, Separator, CaseSensitive, aOpcodesAllowed, aWindowsQuirksAllowed);
+  try
+    Result := AMaskList.Matches(FileName);
+  finally
+    AMaskList.Free;
   end;
-  
-  procedure AddAnyText;
+end;
+
+
+{ TWindowsMaskList }
+
+procedure TWindowsMaskList.SetQuirks(AValue: TWindowsQuirks);
+var
+  i: Integer;
+begin
+  if fWindowsQuirks = AValue then Exit;
+  fWindowsQuirks := AValue;
+  for i := 0 to fMasks.Count - 1 do
   begin
-    if SkipAnyText then
+    TWindowsMask(fMasks.Items[i]).Quirks := FWindowsQuirks;
+    TWindowsMask(fMasks.Items[i]).MaskOpCodes := fMaskOpcodes;
+  end;
+end;
+
+function TWindowsMaskList.GetMaskClass: TMaskClass;
+begin
+  Result := TWindowsMask;
+end;
+
+procedure TWindowsMaskList.AddMasksToList(const aValue: Utf8String;
+  aSeparator: Char; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
+var
+  i: Integer;
+begin
+  inherited AddMasksToList(aValue, aSeparator, aCaseSensitive, aOpcodesAllowed);
+  if (FWindowsQuirks <> DefaultWindowsQuirks) then  //inherited did not pass Quirks to the constructor
     begin
-      Inc(I);
-      Exit;
+    for i := 0 to fMasks.Count - 1 do
+    begin
+      TWindowsMask(fMasks.Items[i]).fWindowsQuirkAllowed := FWindowsQuirks;
     end;
-    
-    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
-    FMask.Chars[High(FMask.Chars)].CharType := mcAnyText;
-
-    FMask.MaxLength := MaxInt;
-    SkipAnyText := True;
-    Inc(I);
   end;
-  
-  procedure AddAnyChar;
-  begin
-    SkipAnyText := False;
+end;
+
+constructor TWindowsMaskList.Create(const aValue: Utf8String; aSeparator: Char;
+  aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
+begin
+  Create(aValue, aSeparator, aCaseSensitive, aOpcodesAllowed, DefaultWindowsQuirks);
+end;
+
+constructor TWindowsMaskList.Create(const aValue: Utf8String; aSeparator: Char;
+  aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes;
+  aWindowsQuirksAllowed: TWindowsQuirks);
+begin
+  fWindowsQuirks := aWindowsQuirksAllowed;
+  //if (wqFilenameEnd in aWindowsQuirksAllowed) then
+  //  Include(aOpcodesAllowed, mocAnyCharOrNone);
+  inherited Create(aValue, aSeparator, aCaseSensitive, aOpcodesAllowed);
+end;
+
+
+{ EMaskError }
+
+constructor EMaskError.Create(const msg: Utf8String; aCode: TMaskExceptionCode);
+begin
+  CreateFmt(msg,[],aCode);
+end;
+
+constructor EMaskError.CreateFmt(const msg: Utf8String; args: array of const;
+  aCode: TMaskExceptionCode);
+begin
+  FCode:=aCode;
+  Inherited CreateFmt(msg,args);
+end;
+
+{ TMaskBase }
 
-    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
-    FMask.Chars[High(FMask.Chars)].CharType := mcAnyChar;
+procedure TMaskBase.SetAutoReverseRange(AValue: Boolean);
+begin
+  if fAutoReverseRange = AValue then Exit;
+  fAutoReverseRange := AValue;
+  fMaskIsCompiled := False;
+end;
 
-    Inc(FMask.MinLength);
-    if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
-    
-    Inc(I);
+procedure TMaskBase.SetCaseSensitive(AValue: Boolean);
+begin
+  if fCaseSensitive = AValue then Exit;
+  fCaseSensitive := AValue;
+  fMaskIsCompiled := False;
+end;
+
+procedure TMaskBase.SetMaskEscapeChar(AValue: Char);
+begin
+  if fMaskEscapeChar=AValue then Exit;
+  if fMaskEscapeChar>#127 then
+    Exception_InvalidEscapeChar();
+  fMaskEscapeChar:=AValue;
+  fMaskIsCompiled:=False;
+end;
+
+procedure TMaskBase.SetMaskOpCodesAllowed(AValue: TMaskOpCodes);
+begin
+  if fMaskOpcodesAllowed = AValue then Exit;
+  fMaskOpcodesAllowed := AValue;
+  fMaskIsCompiled := False;
+end;
+
+procedure TMaskBase.Add(aLength: integer; aData: PBYTE);
+var
+  lCounter: integer;
+begin
+  if fMaskCompiledIndex+aLength>=fMaskCompiledAllocated then begin
+    fMaskCompiledAllocated:=fMaskCompiledAllocated+aLength+GROW_BY;
+    SetLength(fMaskCompiled,fMaskCompiledAllocated);
   end;
-  
-  procedure AddCharSet;
-  var
-    CharSet: TCharSet;
-    Valid: Boolean;
-    C, Last: Char;
-  begin
-    SkipAnyText := False;
-    
-    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
-    FMask.Chars[High(FMask.Chars)].CharType := mcCharSet;
+  for lCounter := 0 to Pred(aLength) do begin
+    fMaskCompiled[fMaskCompiledIndex]:=(aData+lCounter)^;
+    inc(fMaskCompiledIndex);
+  end;
+end;
 
-    Inc(I);
-    if (I <= Length(AValue)) and (AValue[I] = '!') then
-    begin
-      FMask.Chars[High(FMask.Chars)].Negative := True;
-      Inc(I);
-    end
-    else FMask.Chars[High(FMask.Chars)].Negative := False;
-
-    Last := '-';
-    CharSet := [];
-    Valid := False;
-    while I <= Length(AValue) do
+procedure TMaskBase.Add(aValue: integer);
+begin
+  Add(sizeof(aValue),@aValue);
+end;
+
+procedure TMaskBase.Add(aValue: TMaskParsedCode);
+var
+  v: BYTE;
+begin
+  //writeln('Add: ',aValue);
+  v:=BYTE(aValue);
+  Add(1,@v);
+end;
+
+procedure TMaskBase.IncrementLastCounterBy(aOpcode: TMaskParsedCode; aValue: integer);
+var
+  p: PInteger;
+begin
+  //writeln('TMaskBase.IncrementLastCounterBy: aOPcode=',aOpcode,', aValue=',aValue);
+  fMaskCompiledIndex:=fMaskCompiledIndex-sizeof(aValue);
+  if TMaskParsedCode(fMaskCompiled[fMaskCompiledIndex-1])<>aOpcode then
+    Exception_InternalError();
+  P:=@fMaskCompiled[fMaskCompiledIndex];
+  Add(P^+aValue);
+end;
+
+procedure TMaskBase.PrepareCompile;
+begin
+  fMaskCompiled := nil;
+  fMaskCompiledAllocated := 0;
+  fMaskCompiledIndex := 0;
+  fMaskIsCompiled:=False;
+end;
+
+class procedure TMaskBase.Exception_InvalidCharMask(const aMaskChar: Utf8String;
+  aOffset: integer);
+begin
+  if aOffset>=0 then
+    raise EMaskError.CreateFmt(rsInvalidCharMaskAt, [aMaskChar, aOffset], mecInvalidCharMask)
+  else
+    raise EMaskError.CreateFmt(rsInvalidCharMask, [aMaskChar], mecInvalidCharMask);
+end;
+
+class procedure TMaskBase.Exception_MissingCloseChar(const aMaskChar: Utf8String;
+  aOffset: integer);
+begin
+  if aOffset>=0 then
+    raise EMaskError.CreateFmt(rsMissingCloseCharMaskAt, [aMaskChar, aOffset], mecMissingClose)
+  else
+    raise EMaskError.CreateFmt(rsMissingCloseCharMask, [aMaskChar], mecMissingClose);
+end;
+
+class procedure TMaskBase.Exception_IncompleteMask();
+begin
+  raise EMaskError.CreateFmt(rsIncompleteMask, [], mecIncompleteMask);
+end;
+
+class procedure TMaskBase.Exception_InvalidEscapeChar();
+begin
+  raise EMaskError.Create(rsInvalidEscapeChar, mecInvalidEscapeChar);
+end;
+
+procedure TMaskBase.Exception_InternalError();
+begin
+  raise EMaskError.CreateFmt(rsInternalError, [self.ClassName], mecInternalError);
+end;
+
+constructor TMaskBase.Create;
+begin
+  Create(System.FileNameCaseSensitive, DefaultMaskOpCodes);
+end;
+
+constructor TMaskBase.Create(aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
+begin
+  fCaseSensitive:=aCaseSensitive;
+  fMaskOpcodesAllowed:=aOpcodesAllowed;
+  fAutoReverseRange:=True;
+  fMaskEscapeChar:='\';
+end;
+
+
+{ TMask }
+
+procedure TMaskUTF8.AddAnyChar;
+begin
+  Add(TMaskParsedCode.AnyChar);
+  inc(fMatchMinimumLiteralBytes,1);
+  if fMatchMaximumLiteralBytes<High(fMatchMaximumLiteralBytes) then
+    inc(fMatchMaximumLiteralBytes,4);
+  fLastOC:=TMaskParsedCode.AnyChar;
+end;
+
+procedure TMaskUTF8.AddLiteral;
+begin
+  Add(TMaskParsedCode.Literal);
+  Add(fCPLength,@fMask[fMaskInd]);
+  inc(fMatchMinimumLiteralBytes,fCPLength);
+  if fMatchMaximumLiteralBytes<High(fMatchMaximumLiteralBytes) then
+    inc(fMatchMaximumLiteralBytes,fCPLength);
+  fLastOC:=TMaskParsedCode.Literal;
+end;
+
+procedure TMaskUTF8.AddRange(lFirstRange, lSecondRange: Integer);
+begin
+  fCPLength:=UTF8CodepointSizeFast(@fMask[lFirstRange]);
+  Add(fCPLength,@fMask[lFirstRange]);
+  fCPLength:=UTF8CodepointSizeFast(@fMask[lSecondRange]);
+  Add(fCPLength,@fMask[lSecondRange]);
+  fMaskInd:=lSecondRange;
+end;
+
+procedure TMaskUTF8.AddRangeReverse(lFirstRange, lSecondRange: Integer);
+begin
+  fCPLength:=UTF8CodepointSizeFast(@fMask[lSecondRange]);
+  Add(fCPLength,@fMask[lSecondRange]);
+  Add(UTF8CodepointSizeFast(@fMask[lFirstRange]),@fMask[lFirstRange]);
+  fMaskInd:=lSecondRange;
+end;
+
+procedure TMaskUTF8.SetMask(AValue: Utf8String);
+begin
+  if fOriginalMask = AValue then Exit;
+  fOriginalMask := AValue;
+  fMaskIsCompiled := False;
+end;
+
+function TMaskUTF8.IsSpecialChar(AChar: Char): Boolean;
+begin
+  Result := False
+end;
+
+procedure TMaskUTF8.CompileOtherSpecialChars;
+begin
+  //Nothing to do
+end;
+
+procedure TMaskUTF8.CompileRange;
+  function IsARange(aPosition: integer; out aFirstSequence: integer; out aSecondSequence: integer): Boolean;// {$IFDEF USE_INLINE}inline;{$ENDIF}
+    var
+      llCPL: integer;
     begin
-      case AValue[I] of
-        '-':
-          begin
-            if Last = '-' then CharSetError;
-            Inc(I);
-            
-            if (I > Length(AValue)) then CharSetError;
-            //DebugLn('Set:  ' + Last + '-' + UpCase(AValue[I]));
-            for C := Last to UpCase(AValue[I]) do Include(CharSet, C);
-            Inc(I);
+      Result:=false;
+      aFirstSequence:=0;
+      aSecondSequence:=0;
+      if (mocEscapeChar in FMaskOpcodesAllowed) and (fMask[aPosition]=FMaskEscapeChar) then begin
+        llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
+        if aPosition+llCPL>fMaskLimit then exit; // ==>
+        inc(aPosition,llCPL);
+        aFirstSequence:=aPosition;
+      end else begin
+        aFirstSequence:=aPosition;
+      end;
+      llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
+      inc(aPosition,llCPL);
+      if aPosition+llCPL>fMaskLimit then exit; // ==>
+      // It is not a range, return false
+      if fMask[aPosition]<>'-' then exit;
+
+      llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
+      inc(aPosition,llCPL);
+      if (mocEscapeChar in FMaskOpcodesAllowed) and (fMask[aPosition]=FMaskEscapeChar) then begin
+        llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
+        if aPosition+llCPL>fMaskLimit then exit; // ==>
+        inc(aPosition,llCPL);
+        aSecondSequence:=aPosition;
+      end else begin
+        aSecondSequence:=aPosition;
+      end;
+      Result:=true;
+    end;
+
+var
+  lCharsGroupInsertSize, lFirstRange, lSecondRange: integer;
+begin
+  //writeln('CompileRange: fMask[fMaskInd]=',fMask[fMaskInd]);
+  if ([mocSet,mocRange,mocAnyCharOrNone]*fMaskOpCodesAllowed = [mocAnyCharOrNone]) or
+     ((mocAnyCharOrNone in fMaskOpcodesAllowed) and (fMaskInd<fMaskLimit) and (fMask[fMaskInd+1]='?'))
+      then begin
+    CompileAnyCharOrNone('?', True);
+  end else begin//not AnyCharOrNone
+    fLastOC:=TMaskParsedCode.CharsGroupBegin;
+    Add(TMaskParsedCode.CharsGroupBegin);
+    inc(fMatchMinimumLiteralBytes,1);
+    if fMatchMaximumLiteralBytes<High(fMatchMaximumLiteralBytes) then
+      begin inc(fMatchMaximumLiteralBytes,4); end;
+    lCharsGroupInsertSize:=fMaskCompiledIndex;
+    Add(0);
+    inc(fMaskInd); // CP length is 1 because it is "["
+    if fMaskInd<fMaskLimit then begin
+      if (fMask[fMaskInd]='!') and (mocNegateGroup in fMaskOpcodesAllowed) then begin
+        Add(TMaskParsedCode.Negate);
+        inc(fMaskInd); // CP length is 1 because it is "!"
+        fLastOC:=TMaskParsedCode.Negate;
+      end;
+    end;
+    while fMaskInd<=fMaskLimit do begin //while
+      fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
+      if (mocRange in fMaskOpcodesAllowed) and IsARange(fMaskInd,lFirstRange,lSecondRange) then begin //is a range
+        Add(TMaskParsedCode.Range);
+        // Check if reverse range is needed
+        if (not fAutoReverseRange)
+        or (CompareUTF8Sequences(@fMask[lFirstRange],@fMask[lSecondRange])<0) then
+          AddRange(lFirstRange, lSecondRange)
+        else
+          AddRangeReverse(lFirstRange, lSecondRange);
+        fLastOC:=TMaskParsedCode.Range;
+
+      end //is a range
+      else if fMask[fMaskInd]=']' then begin  //end of range or set
+        if (fLastOC=TMaskParsedCode.CharsGroupBegin) or (fLastOC=TMaskParsedCode.Negate) then
+          begin //empty set or range
+            //writeln('CompileRange: empty match');
+            Exception_InvalidCharMask(fMask[fMaskInd],fMaskInd); //Error empty match
+          end; //empty set or range
+        // Insert the new offset in case of a positive match in CharsGroup
+        PInteger(@fMaskCompiled[lCharsGroupInsertSize])^:=fMaskCompiledIndex;
+        Add(TMaskParsedCode.CharsGroupEnd);
+        fLastOC:=TMaskParsedCode.CharsGroupEnd;
+        break;
+      end // end of range or set
+      else begin  //not a range, not AnyCharOrNone, must be a set
+        //Note: why not raise an exception right here if mocSet is NOT enabled?
+
+        // handle escaping if mocSet is enabled, but mocRange not
+        if (fMask[fMaskInd]=FMaskEscapeChar) and (mocEscapeChar in fMaskOpcodesAllowed) then begin //escaped literal in set
+          // next is optional char in set or literal, consume the EscapeChar
+          inc(fMaskInd,fCPLength);
+          if fMaskInd<=fMaskLimit then begin
+            fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
+          end else begin
+            //writeln('CompileRange: incomplete mask');
+            Exception_IncompleteMask();
           end;
-        ']':
-          begin
-            Valid := True;
-            Break;
+        end;//escaped literal in set
+
+        if (mocSet in fMaskOpcodesAllowed) then begin //add to set
+          Add(TMaskParsedCode.OptionalChar);
+          Add(fCPLength,@fMask[fMaskInd]);
+          fLastOC:=TMaskParsedCode.OptionalChar;
+        end //add to set
+        else begin // mocRange enabled but IsRange=False, mocSet disabled
+           Exception_InvalidCharMask(fMask[fMaskInd],fMaskInd);
+        end;
+      end; //not a range, not AnyCharOrNone, must be a set
+      inc(fMaskInd,fCPLength);
+    end;//while
+    if fMaskInd>fMaskLimit then
+      Exception_MissingCloseChar(']',fMaskLimit);
+  end;//not AnyCharOrNone
+  //writeln('CompileRange end: fMask[fMaskInd]=',fMask[fMaskInd]);
+end;
+
+function TMaskUTF8.GetMask: Utf8String;
+begin
+  Result := fOriginalMask;
+end;
+
+
+procedure TMaskUtf8.CompileEscapeCharPlusLiteral;
+begin
+  inc(fMaskInd,fCPLength);
+  if fMaskInd<=fMaskLimit then begin
+    fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
+    AddLiteral;
+    inc(fMaskInd,fCPLength);
+  end
+  else
+    Exception_IncompleteMask();
+end;
+
+procedure TMaskUtf8.CompileSpecialChars;
+begin
+  case fMask[fMaskInd] of
+    '*':
+      begin
+        if mocAnyText in fMaskOpcodesAllowed then begin
+          if fLastOC<>TMaskParsedCode.AnyCharToNext then begin
+            Add(TMaskParsedCode.AnyCharToNext);
+            fLastOC:=TMaskParsedCode.AnyCharToNext;
+            // * = No limit
+            fMatchMaximumLiteralBytes:=High(fMatchMaximumLiteralBytes);
           end;
+        end
         else
-        begin
-          Last := UpCase(AValue[I]);
-          Include(CharSet, Last);
-          Inc(I);
+          AddLiteral;
+      end;
+    '?':
+      begin
+        if mocAnyChar in fMaskOpcodesAllowed then
+          AddAnyChar
+        else
+          AddLiteral;
+      end;
+    '[':
+      begin
+        if ([mocSet,mocRange,
+            mocAnyCharOrNone] * fMaskOpcodesAllowed <> [])
+        //in this case the '[' always mus be the start of a Range, a Set or AnyCharOrNone
+        then
+          begin //it's just a bit easier later on if we handle this before we call CompileRange
+            //if ([mocSet,mocRange]*fMaskOpCodesAllowed = []) {only mocAnyCharOrNone enabled}
+            //   and (fMaskInd<fMaskLimit) and (fMask[fMaskInd+1]<>'?') {next char is not '?'} then
+            //     Exception_InvalidCharMask(fMask[fMaskInd+1], fMaskInd+1);
+            CompileRange;
+          end
+        else begin //mocSet,MocRange and mocAnyCharOrNone are all disabled
+          {
+          if (fMask[fMaskInd]=FMaskEscapeChar) and (mocEscapeChar in FMaskOpcodesAllowed) then begin  //DEAD CODE?
+            //This codepath could only be chosen if, at this point '[' is the escapechar
+            //but, if that is the case, it should already have been handled in Compile
+            //and CompileRange should not have been called.
+            //Maybe I'm wrong, so I just comment this section out instead of
+            //completely removing it
+
+            // next is Literal
+            inc(fMaskInd,fCPLength);
+            if fMaskInd<=fMaskLimit then begin
+              fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
+            end else begin
+              Exception_IncompleteMask();
+            end;
+          end; //DEAD CODE??
+          }
+          AddLiteral;
         end;
       end;
+    otherwise
+    begin
+      CompileOtherSpecialChars;
     end;
-    
-    if (not Valid) or (CharSet = []) then CharSetError;
-
-    New(FMask.Chars[High(FMask.Chars)].SetValue);
-    FMask.Chars[High(FMask.Chars)].SetValue^ := CharSet;
-    
-    Inc(FMask.MinLength);
-    if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
-
-    Inc(I);
   end;
-  
-  procedure AddChar;
-  begin
-    SkipAnyText := False;
+end;
+
+procedure TMaskUTF8.CompileAnyCharOrNone(QChar: Char; BracketsRequired: Boolean);
+var
+  QCount, lCharsGroupInsertSize: Integer;
+begin
+    //if any of the 2 conditions is true, this procedure should not have been called.
+    {$IFDEF debug_anycharornone}
+    writeln('CompileAnyCharOrNone: QChar=#',Ord(QChar),', BracketsRequired=',BracketsRequired,', fMask[fMaskInd]=',fMask[fMaskInd]);
+    if (BracketsRequired and (fMask[fMaskInd]<>'[')) or ((not BracketsRequired) and (fMask[fMaskInd]<>QChar)) then
+        Exception_InternalError();
+    {$ENDIF}
 
-    SetLength(FMask.Chars, Length(FMask.Chars) + 1);
-    with FMask.Chars[High(FMask.Chars)] do
+    if BracketsRequired then
     begin
-      CharType := mcChar;
-      CharValue := UpCase(AValue[I]);
+      Inc(fMaskInd); //consume the '['
+      //the following happens when mocSet amd mocRange are disabled and the first char after the bracket is not a '?'
+      if fMask[fMaskInd]<>QChar then
+        Exception_InvalidCharMask(fMask[fMaskInd], fMaskInd);
     end;
 
-    Inc(FMask.MinLength);
-    if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
+    QCount:=1;
+    while (fMaskInd+QCount<=fMaskLimit) and (fMask[fMaskInd+QCount]=QChar) do Inc(QCount);
+    {$IFDEF debug_anycharornone}
+    writeln('CompileAnyCharOrNone: Nr of AnyCharOrNone-tokens: ',QCount);
+    if (fMaskInd+QCount>fMaskLimit) then writeln('(fMaskInd+QCount>fMaskLimit): ',fMaskInd+QCount,'>',fMaskLimit);
+    {$ENDIF}
+
+    //is Last found QChar also last character of the mask, while we expect a closing ']'?
+    if BracketsRequired and (fMaskInd+QCount>fMaskLimit) then
+      Exception_MissingCloseChar(']',fMaskInd+QCount-1);
 
-    Inc(I);
+    {$IFDEF debug_anycharornone}
+    if not (fMask[fMaskInd+QCount]=']') then writeln('fMask[fMaskInd+QCount]: expected ], found: ',fMask[fMaskInd+QCount]);
+    {$ENDIF}
+
+    if BracketsRequired and not (fMask[fMaskInd+QCount]=']') then
+      Exception_InvalidCharMask(fMask[fMaskInd+QCount],fMaskInd+QCount);
+
+    Add(TMaskParsedCode.CharsGroupBegin);
+    lCharsGroupInsertSize:=fMaskCompiledIndex;
+    Add(0);
+    Add(TMaskParsedCode.AnyCharOrNone);
+    Add(1);
+    if QCount>1 then
+      IncrementLastCounterBy(TMaskParsedCode.AnyCharOrNone,QCount-1);
+    PInteger(@fMaskCompiled[lCharsGroupInsertSize])^:=fMaskCompiledIndex;
+    Add(TMaskParsedCode.CharsGroupEnd);
+    fLastOC:=TMaskParsedCode.CharsGroupEnd;
+    Inc(fMatchMaximumLiteralBytes,QCount*4);
+    if BracketsRequired then
+      Inc(fMaskInd,QCount) //go to ending ']'
+    else
+      Inc(fMaskInd,QCount-1); //go to last found QChar
+
+    {$IFDEF debug_anycharornone}
+    write('fMaskInd=',fMaskInd,', fMaskLimit=',fMaskLimit,' fMask[fMaskInd]=');if fMaskInd<=fMaskLimit then writeln('#',Ord(fMask[fMaskInd]),': ',fMask[fMaskInd])else writeln('>>');
+    writeln('CompileAnyCharOrNone end.');
+    {$ENDIF}
+end;
+
+procedure TMaskUTF8.Compile;
+begin
+  PrepareCompile;
+  if fCaseSensitive then
+    fMask:=fOriginalMask
+  else
+    fMask:=UTF8LowerCase(fOriginalMask);
+  fMaskLimit:=Length(fMask);
+  fLastOC:=TMaskParsedCode.Literal;
+  SetLength(fMaskCompiled,0);
+  fMaskInd:=1;
+
+  while fMaskInd<=fMaskLimit do begin //while
+    fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
+    if (mocEscapeChar in fMaskOpcodesAllowed) and (fMask[fMaskInd]=fMaskEscapeChar) then
+      CompileEscapeCharPlusLiteral
+    else begin // not an escaped literal
+      //writeln('Compile while: fMask[',fMaskInd,']=',fMask[fMaskInd]);
+      if (fMask[fMaskInd] in DefaultSpecialChars) or IsSpecialChar(fMask[fMaskInd]) then begin // handle special chars
+        CompileSpecialChars;
+        //writeln('After CompileSpecialChar');
+        //write('fMaskInd=',fMaskInd,', fMaskLimit=',fMaskLimit,' fMask[fMaskInd]=');if fMaskInd<=fMaskLimit then writeln('#',Ord(fMask[fMaskInd]),': ',fMask[fMaskInd])else writeln('>>');
+      end  //handle special chars
+      else
+      begin
+        //writeln('Compile: AddLiteral: fMask[',fMaskInd,']=',fMask[fMaskInd]);
+        AddLiteral;
+      end;
+      inc(fMaskInd,fCPLength);
+    end; //not an escaped literal
+  end;  //while
+
+  SetLength(fMaskCompiled,fMaskCompiledIndex);
+  fMaskCompiledLimit:=fMaskCompiledIndex-1;
+  {
+   fMaskIsCompiled Indicates that Compile was succesfull
+   If you override this method make sure that it is only set to True if
+   the overridden method also succeeds!
+  }
+  fMaskIsCompiled:=True;
+
+  //writeln('Compile end.');
+  {$IFDEF debug_maskcompiled}
+  writeln('fMaskInd=',fMaskInd,', fMaskLimit=',fMaskLimit);
+  writeln('fMaskCompiled:');
+  writeln('fMaskCompiledLimit=',fMaskCompiledLimit);
+  writeln('fMaskCompiledIndex=',fMaskCompiledIndex);
+  DumpMaskCompiled;
+  {$ENDIF}
+end;
+
+class function TMaskUTF8.CompareUTF8Sequences(const P1, P2: PChar): integer;
+var
+  l1,l2: integer;
+  l: integer;
+begin
+  l1:=UTF8CodepointSizeFast(p1);
+  l2:=UTF8CodepointSizeFast(p2);
+  Result:=0;
+  l:=0;
+  while (l<l1) and (l<l2) do begin
+    Result:=Integer((P1+l)^)-integer((P2+l)^);
+    if Result<>0 then exit;
+    inc(l);
   end;
-  
-begin
-  SetLength(FMask.Chars, 0);
-  FMask.MinLength := 0;
-  FMask.MaxLength := 0;
-  SkipAnyText := False;
-  
-  I := 1;
-  while I <= Length(AValue) do
-  begin
-    case AValue[I] of
-      '*': AddAnyText;
-      '?': AddAnyChar;
-      '[': AddCharSet;
-      else AddChar;
+  Result:=l1-l2;
+end;
+
+function TMaskUTF8.intfMatches(aMatchOffset: integer; aMaskIndex: integer): TMaskFailCause;
+var
+  c1,c2: PChar;
+  lFailCause: TMaskFailCause;
+  lNegateCharGroup: Boolean;
+  lSkipOnSuccessGroup: integer;
+  t1: Boolean;
+  j: integer;
+  lTryCounter: integer;
+begin
+  lSkipOnSuccessGroup:=0;
+  Result:=mfcUnexpectedEnd;
+  lNegateCharGroup:=false;
+  while aMaskIndex<=fMaskCompiledLimit do begin
+    case TMaskParsedCode(fMaskCompiled[aMaskIndex]) of
+      TMaskParsedCode.Literal:
+        begin
+          if aMatchOffset>fMatchStringLimit then
+            exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
+          inc(aMaskIndex);
+          if CompareUTF8Sequences(@fMaskCompiled[aMaskIndex],@fMatchString[aMatchOffset])<>0 then
+            exit(TMaskFailCause.mfcMaskNotMatch);
+          inc(aMaskIndex,UTF8CodepointSizeFast(@fMaskCompiled[aMaskIndex]));
+          inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+        end;
+      TMaskParsedCode.AnyChar:
+        begin
+          inc(aMaskIndex);
+          if aMatchOffset>fMatchStringLimit then
+            exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
+          inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+        end;
+      TMaskParsedCode.Negate:
+        begin
+          lNegateCharGroup:=true;
+          inc(aMaskIndex);
+        end;
+      TMaskParsedCode.CharsGroupBegin:
+        begin
+          lNegateCharGroup:=false;
+          inc(aMaskIndex);
+          lSkipOnSuccessGroup:=PInteger(@fMaskCompiled[aMaskIndex])^;
+          inc(aMaskIndex,sizeof(integer));
+        end;
+      TMaskParsedCode.CharsGroupEnd:
+        begin
+          if lNegateCharGroup then begin
+            aMaskIndex:=lSkipOnSuccessGroup+1;
+            inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+          end
+          else
+            exit(TMaskFailCause.mfcMaskNotMatch);
+        end;
+      TMaskParsedCode.OptionalChar:
+        begin
+          inc(aMaskIndex);
+          if aMatchOffset>fMatchStringLimit then
+            exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
+          if CompareUTF8Sequences(@fMaskCompiled[aMaskIndex],@fMatchString[aMatchOffset])=0 then begin
+            if lNegateCharGroup then
+              exit(TMaskFailCause.mfcMaskNotMatch);
+            aMaskIndex:=lSkipOnSuccessGroup+1;
+            inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+          end
+          else
+            inc(aMaskIndex,UTF8CodepointSizeFast(@fMaskCompiled[aMaskIndex]));
+        end;
+      TMaskParsedCode.Range:
+        begin
+          if aMatchOffset>fMatchStringLimit then
+            exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
+          inc(aMaskIndex);
+          c1:=@fMaskCompiled[aMaskIndex];
+          inc(aMaskIndex,UTF8CodepointSizeFast(C1));
+          c2:=@fMaskCompiled[aMaskIndex];
+          inc(aMaskIndex,UTF8CodepointSizeFast(C2));
+          t1:=(CompareUTF8Sequences(@fMatchString[aMatchOffset],c1)>=0) and
+              (CompareUTF8Sequences(@fMatchString[aMatchOffset],c2)<=0);
+          if t1 then begin
+            if not lNegateCharGroup then begin
+              //Jump to CharsGroupEnd+1 because if CharsGroupEnd is reached
+              //it means that all optional chars and ranges have not matched the Utf8String.
+              aMaskIndex:=lSkipOnSuccessGroup+1;
+              inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+            end
+            else
+              exit(TMaskFailCause.mfcMaskNotMatch);
+          end
+        end;
+      TMaskParsedCode.AnyCharToNext:
+        begin
+          // if last is "*", everything in remain data matches
+          if aMaskIndex=fMaskCompiledLimit then
+            exit(TMaskFailCause.mfcSuccess);
+          if aMatchOffset>fMatchStringLimit then begin
+            if aMaskIndex=fMaskCompiledLimit then
+              exit(TMaskFailCause.mfcSuccess);
+            exit(TMaskFailCause.mfcMatchStringExhausted);
+          end;
+          inc(aMaskIndex);
+          while aMatchOffset<=fMatchStringLimit do begin
+            lFailCause:=intfMatches(aMatchOffset,aMaskIndex);
+            if lFailCause=TMaskFailCause.mfcSuccess then
+              exit(TMaskFailCause.mfcSuccess)
+            else if lFailCause=TMaskFailCause.mfcMatchStringExhausted then
+              exit(TMaskFailCause.mfcMatchStringExhausted);
+            inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+          end;
+          exit(TMaskFailCause.mfcMatchStringExhausted);
+        end;
+      TMaskParsedCode.AnyCharOrNone:
+        begin
+          inc(aMaskIndex);
+          lTryCounter:=PInteger(@fMaskCompiled[aMaskIndex])^;
+          inc(aMaskIndex,sizeof(integer));
+          if TMaskParsedCode(fMaskCompiled[aMaskIndex])<>TMaskParsedCode.CharsGroupEnd then
+            begin //writeln('TMaskUtf8.infMatches: error parsing AnyCharOrNone, missing CharsGroupEnd, fMaskCompiled[',aMaskIndex,']=',fMaskCompiled[aMaskIndex]);
+            Exception_InternalError() end
+          else
+            aMaskIndex:=lSkipOnSuccessGroup+1;
+
+          // Try to match remain mask eating, 0,1,2,...,lTryCounter chars.
+          for j := 0 to lTryCounter do begin
+            if aMatchOffset>fMatchStringLimit then begin
+              if aMaskIndex=fMaskCompiledLimit+1 then
+                exit(TMaskFailCause.mfcSuccess);
+              exit(TMaskFailCause.mfcMatchStringExhausted);
+            end;
+            lFailCause:=intfMatches(aMatchOffset,aMaskIndex);
+            if lFailCause=TMaskFailCause.mfcSuccess then
+              exit(TMaskFailCause.mfcSuccess)
+            else if lFailCause=TMaskFailCause.mfcMatchStringExhausted then
+              exit(TMaskFailCause.mfcMatchStringExhausted);
+            inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
+          end;
+          exit(TMaskFailCause.mfcMatchStringExhausted);
+        end;
+      else  // case
+        begin
+          writeln('TMaskUtf8.infMatches: XXXX InternalError');
+          Exception_InternalError();
+        end;
     end;
   end;
+  if (aMaskIndex>fMaskCompiledLimit) and (aMatchOffset>fMatchStringLimit) then
+    Result:=TMaskFailCause.mfcSuccess
+  else if aMaskIndex>fMaskCompiledLimit then
+    Result:=TMaskFailCause.mfcMaskExhausted
+  else
+    Result:=TMaskFailCause.mfcMatchStringExhausted;
 end;
 
-destructor TMask.Destroy;
+{$IFDEF debug_maskcompiled}
+procedure TMaskUTF8.DumpMaskCompiled;
 var
-  I: Integer;
+  i: Integer;
+  b: Byte;
 begin
-  for I := 0 to High(FMask.Chars) do
-    if FMask.Chars[I].CharType = mcCharSet then
-      Dispose(FMask.Chars[I].SetValue);
+  for i := low(fMaskCompiled) to fMaskCompiledIndex-1 do
+  begin
+    b := fMaskCompiled[i];
+    writeln(i:2,': ',b:3);
+  end;
+end;
+{$ENDIF}
 
-  inherited Destroy;
+constructor TMaskUTF8.Create(const aMask: Utf8String);
+begin
+  Create(aMask, System.FileNameCaseSensitive, DefaultMaskOpCodes);
 end;
 
-function TMask.Matches(const AFileName: UTF8string): Boolean;
+constructor TMaskUTF8.Create(const aMask: Utf8String; aCaseSensitive: Boolean);
+begin
+  Create(aMask, aCaseSensitive, DefaultMaskOpCodes);
+end;
+
+constructor TMaskUTF8.Create(const aMask: Utf8String;
+  aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
+begin
+  inherited Create(aCaseSensitive,aOpcodesAllowed);
+  fOriginalMask:=aMask;
+end;
+
+
+function TMaskUTF8.Matches(const aStringToMatch: Utf8String): Boolean;
+begin
+  if not fMaskIsCompiled then Compile;
+  if fCaseSensitive then
+    fMatchString:=aStringToMatch
+  else
+    fMatchString:=UTF8LowerCase(aStringToMatch);
+  fMatchStringLimit:=length(fMatchString);
+  if (fMatchStringLimit>=fMatchMinimumLiteralBytes)
+  and (fMatchStringLimit<=fMatchMaximumLiteralBytes) then
+    Result:=intfMatches(1,0)=TMaskFailCause.mfcSuccess
+  else
+    Result:=false; // There are too many or not enough bytes to match the string
+end;
+
+
+{ TWindowsMask }
+
+procedure TWindowsMaskUTF8.SetMask(AValue: Utf8String);
+begin
+  if (AValue = fWindowsMask) then Exit;
+  inherited SetMask(AValue);
+  fWindowsMask := AValue;
+end;
+
+function TWindowsMaskUTF8.GetMask: Utf8String;
+begin
+  Result := fWindowsMask;
+end;
+
+procedure TWindowsMaskUTF8.SetWindowsQuirkAllowed(AValue: TWindowsQuirks);
+begin
+  if fWindowsQuirkAllowed = AValue then Exit;
+  fWindowsQuirkAllowed := AValue;
+  //if (wqFilenameEnd in fWindowsQuirkAllowed) then
+  //  Include(fMaskOpcodesAllowed, mocAnyCharOrNone);
+  fMaskIsCompiled := False;
+end;
+
+procedure TWindowsMaskUTF8.CompileOtherSpecialChars;
+begin
+  inherited CompileOtherSpecialChars;
+  if (fMask[fMaskInd]=#0) and not (wqFileNameEnd in self.fWindowsQuirkInUse) then
+    Exception_InternalError;
+  CompileAnyCharOrNone(#0, False);
+end;
+
+function TWindowsMaskUTF8.IsSpecialChar(AChar: Char): Boolean;
+begin
+  Result := (AChar = #0);
+end;
+
+class procedure TWindowsMaskUTF8.SplitFileNameExtension(
+  const aSourceFileName: Utf8String; out aFileName: Utf8String;
+  out aExtension: Utf8String; aIsMask: Boolean);
 var
-  L: Integer;
-  S: UTF8string;
-  
-  function MatchToEnd(MaskIndex, CharIndex: Integer): Boolean;
+  j: Integer;
+  lLowLimit: integer;
+begin
+  // Default values
+  aFileName:=aSourceFileName;
+  aExtension:='';
+
+  // This is because .foo is considered a file name ".foo" as one.
+  if aIsMask then
+    lLowLimit:=0
+  else
+    lLowLimit:=1;
+
+  j:=Length(aSourceFileName);
+  while j>lLowLimit do begin
+    if aSourceFileName[j]='.' then begin
+      aFileName:=copy(aSourceFileName,1,j-1);
+      aExtension:=copy(aSourceFileName,j);
+      break;
+    end;
+    dec(j);
+  end;
+end;
+
+constructor TWindowsMaskUTF8.Create(const aMask: Utf8String;
+  aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
+begin
+  Create(aMask, aCaseSensitive, aOpcodesAllowed, DefaultWindowsQuirks);
+end;
+
+constructor TWindowsMaskUTF8.Create(const aMask: Utf8String; aCaseSensitive: Boolean;
+  aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks);
+begin
+  fWindowsQuirkAllowed:=aWindowsQuirksAllowed;
+  fWindowsMask:=aMask;
+  //if (wqFilenameEnd in fWindowsQuirkAllowed) then
+  //  Include(aOpcodesAllowed, mocAnyCharOrNone);
+  inherited Create(aMask,aCaseSensitive,aOpcodesAllowed);
+end;
+
+procedure TWindowsMaskUTF8.Compile;
+
+  function OptionalQMarksAtEnd(aMask: Utf8String): Utf8String;
   var
-    I, J: Integer;
+    //lCounter: integer;
+    i: integer;
   begin
-    Result := False;
-    
-    for I := MaskIndex to High(FMask.Chars) do
-    begin
-      case FMask.Chars[I].CharType of
-        mcChar:
-          begin
-            if CharIndex > L then Exit;
-            //DebugLn('Match ' + S[CharIndex] + '<?>' + FMask.Chars[I].CharValue);
-            if S[CharIndex] <> FMask.Chars[I].CharValue then Exit;
-            Inc(CharIndex);
-          end;
-        mcCharSet:
-          begin
-            if CharIndex > L then Exit;
-            if FMask.Chars[I].Negative xor
-               (S[CharIndex] in FMask.Chars[I].SetValue^) then Inc(CharIndex)
-            else Exit;
-          end;
-        mcAnyChar:
-          begin
-            if CharIndex > L then Exit;
-            Inc(CharIndex);
-          end;
-        mcAnyText:
-          begin
-            if I = High(FMask.Chars) then
-            begin
-              Result := True;
-              Exit;
-            end;
-            
-            for J := CharIndex to L do
-              if MatchToEnd(I + 1, J) then
-              begin
-                Result := True;
-                Exit;
-              end;
-          end;
-      end;
+    //Change ending ? in #0
+    Result := aMask;
+    for i := Length(aMask) downto 1 do begin
+      if Result[i]='?' then
+        Result[i]:=#0
+      else
+        Exit;
+    end;
+    {
+    lCounter:=0;
+    for i := Length(aMask) downto 1 do begin
+      if aMask[i]='?' then
+        inc(lCounter)
+      else
+        break;
     end;
-    
-    Result := CharIndex > L;
+    if lCounter>0 then
+      aMask:=copy(aMask,1,Length(aMask)-lCounter)+'['+StringOfChar('?',lCounter)+']';
+    Result:=aMask;
+    }
   end;
-  
+
+var
+  lFileNameMask: Utf8String;
+  lExtensionMask: Utf8String;
+  lModifiedMask: Utf8String;
+  ZeroPos: SizeInt;
+
 begin
-  Result := False;
-  L := Length(AFileName);
-  if L = 0 then
-  begin
-    if FMask.MinLength = 0 then Result := True;
-    Exit;
+  lModifiedMask:=fWindowsMask;
+
+  //We cannot have #0 in the mask,since we use it internally
+  ZeroPos:=Pos(#0, fWindowsMask);
+  if ZeroPos>0 then
+    Exception_InvalidCharMask('NullCharacter', ZeroPos);
+
+  // Quirk "blah.*" = "blah*"
+  if wqAnyExtension in fWindowsQuirkAllowed then begin
+    if (RightStr(lModifiedMask,2)='.*') and (Length(lModifiedMask)>2) then begin
+    //if RightStr(lModifiedMask,3)='*.*' then begin
+      lModifiedMask:=copy(lModifiedMask,1,Length(lModifiedMask)-2);
+      fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqAnyExtension];
+    end;
+  end;
+
+  SplitFileNameExtension(lModifiedMask,lFileNameMask,lExtensionMask,true);
+
+  // Quirk "blah.abc" = "blah.abc*"
+  if wqExtension3More in fWindowsQuirkAllowed then begin
+    if (Length(lExtensionMask)=4) and (Length(lFileNameMask)>0) then begin
+      lExtensionMask:=lExtensionMask+'*';
+      fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqExtension3More];
+    end;
+  end;
+
+  // Quirk "" = "*"
+  if (Length(lFileNameMask)=0) and (Length(lExtensionMask)=0) then begin
+    if wqEmptyIsAny in fWindowsQuirkAllowed then begin
+      lFileNameMask:='*';
+      fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqEmptyIsAny];
+    end;
+  end else begin
+  // Quirk ".abc"
+    if wqAllByExtension in fWindowsQuirkAllowed then begin
+      if (Length(lFileNameMask)=0) and (length(lExtensionMask)>0) then begin
+        if lExtensionMask[1]='.' then begin
+          lFileNameMask:='*';
+          fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqAllByExtension];
+        end;
+      end;
+    end;
   end;
-  
-  if (L < FMask.MinLength) or (L > FMask.MaxLength) then Exit;
 
-  S := UpperCase(AFileName);
-  Result := MatchToEnd(0, 1);
+
+  // Quirk "file???.ab?" matches "file1.ab1" and "file123.ab"
+  if wqFilenameEnd in fWindowsQuirkAllowed then begin
+    lFileNameMask:=OptionalQMarksAtEnd(lFileNameMask);
+    lExtensionMask:=OptionalQMarksAtEnd(lExtensionMask);
+    if (Pos(#0, lFileNameMask)>0) or (Pos(#0, lExtensionMask)>0) then
+      fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqFilenameEnd];
+  end;
+
+  if wqNoExtension in fWindowsQuirkAllowed then begin
+    if Length(lExtensionMask)=1 then begin
+      fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqNoExtension];
+      lExtensionMask:='';
+    end;
+  end;
+
+  inherited Mask:=lFileNameMask+lExtensionMask;
+  inherited Compile;
+end;
+
+function TWindowsMaskUTF8.Matches(const aFileName: Utf8String): Boolean;
+var
+  lFileName, lExtension: Utf8String;
+begin
+  if not fMaskIsCompiled then
+    Compile;
+  if wqNoExtension in fWindowsQuirkInUse then begin
+    SplitFileNameExtension(aFileName,lFileName,lExtension,false);
+    // wqNoExtension = Empty extension
+    //if lExtension<>'' then exit(false);
+    // Its not clear if a file "file." should match an "*." mask because
+    // there is no way in Windows that a file ends with a dot.
+    if (lExtension<>'') and (lExtension<>'.') then
+      exit(false)
+    end else if wqAnyExtension in fWindowsQuirkInUse then begin
+      SplitFileNameExtension(aFileName,lFileName,lExtension,false);
+      Result:=inherited Matches(lFileName);
+      exit;
+  end;
+  Result:=Inherited Matches(aFileName);
 end;
 
+
 { TParseStringList }
 
-constructor TParseStringList.Create(const AText, ASeparators: UTF8string);
+constructor TParseStringList.Create(const AText, ASeparators: Utf8String);
 var
   I, S: Integer;
 begin
   inherited Create;
-
   S := 1;
   for I := 1 to Length(AText) do
   begin
     if Pos(AText[I], ASeparators) > 0 then
     begin
-      if I > S then Add(Copy(AText, S, I - S));
+      if I > S then
+        Add(Copy(AText, S, I - S));
       S := I + 1;
     end;
   end;
-  
-  if Length(AText) >= S then Add(Copy(AText, S, Length(AText) - S + 1));
+  if Length(AText) >= S then
+    Add(Copy(AText, S, Length(AText) - S + 1));
 end;
 
 { TMaskList }
 
+constructor TMaskList.Create(const aValue: Utf8String; aSeparator: Char;
+  CaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
+begin
+  fMask := aValue;
+  fSeparator := aSeparator;
+  fCaseSensitive := CaseSensitive;
+  fMaskOpcodes := aOpcodesAllowed;
+  fAutoReverseRange := True;
+
+  fMasks := TObjectList.Create(True);
+  FMaskClass := GetMaskClass;
+  AddMasksToList(aValue, aSeparator, CaseSensitive, aOpcodesAllowed);
+end;
+
+
+destructor TMaskList.Destroy;
+begin
+  fMasks.Free;
+  inherited Destroy;
+end;
+
 function TMaskList.GetItem(Index: Integer): TMask;
 begin
-  Result := TMask(FMasks.Items[Index]);
+  Result := TMask(fMasks.Items[Index]);
 end;
 
-function TMaskList.GetCount: Integer;
+procedure TMaskList.SetAutoReverseRange(AValue: Boolean);
+var
+  i: Integer;
 begin
-  Result := FMasks.Count;
+  if fAutoReverseRange = AValue then Exit;
+  fAutoReverseRange := AValue;
+  for i := 0 to fMasks.Count - 1 do
+    TMask(fMasks.Items[i]).AutoReverseRange := fAutoReverseRange;
 end;
 
-constructor TMaskList.Create(const AValue: UTF8string; ASeparator: Char);
+procedure TMaskList.SetCaseSensitive(AValue: Boolean);
+var
+  i: Integer;
+begin
+  if fCaseSensitive = AValue then Exit;
+  fCaseSensitive := AValue;
+  for i := 0 to fMasks.Count - 1 do
+    TMask(fMasks.Items[i]).CaseSensitive := fCaseSensitive;
+end;
+
+procedure TMaskList.SetMask(AValue: Utf8String);
+begin
+  if fMask = AValue then Exit;
+  fMask := AValue;
+  fMasks.Clear;
+  AddMasksToList(fMask, fSeparator, fCaseSensitive, fMaskOpCodes);
+end;
+
+procedure TMaskList.SetMaskOpCodes(AValue: TMaskOpCodes);
+var
+  i: Integer;
+begin
+  if FMaskOpCodes = AValue then Exit;
+  fMaskOpCodes := AValue;
+  for i := 0 to fMasks.Count - 1 do
+    TMask(fMasks.Items[i]).MaskOpCodes := fMaskOpcodes;
+end;
+
+function TMaskList.GetMaskClass: TMaskClass;
+begin
+  Result := TMask;
+end;
+
+procedure TMaskList.AddMasksToList(const aValue: Utf8String; aSeparator: Char; CaseSensitive: Boolean;
+      aOpcodesAllowed: TMaskOpCodes);
 var
   S: TParseStringList;
-  I: Integer;
+  i: Integer;
 begin
-  FMasks := TObjectList.Create(True);
-  
-  S := TParseStringList.Create(AValue, ASeparator + ' ');
+  S := TParseStringList.Create(aValue, aSeparator);
   try
-    for I := 0 to S.Count - 1 do
-      FMasks.Add(TMask.Create(S[I]));
+    for i := 0 to S.Count-1 do begin
+      fMasks.Add(FMaskClass.Create(S[i], CaseSensitive, aOpcodesAllowed));
+    end;
   finally
     S.Free;
   end;
 end;
 
-destructor TMaskList.Destroy;
+function TMaskList.GetCount: Integer;
 begin
-  FMasks.Free;
-  
-  inherited Destroy;
+  Result := fMasks.Count;
 end;
 
-function TMaskList.Matches(const AFileName: UTF8string): Boolean;
+function TMaskList.Matches(const AFileName: Utf8String): Boolean;
 var
   I: Integer;
 begin
   Result := False;
-  
-  for I := 0 to FMasks.Count - 1 do
-  begin
-    if TMask(FMasks.Items[I]).Matches(AFileName) then
-    begin
-      Result := True;
-      Exit;
-    end;
-  end;
+  for I := 0 to fMasks.Count-1 do
+    if ((fMasks.Items[I]) as FMaskClass).Matches(AFileName) then
+      Exit(True);
 end;
 
+
 end.