1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2006 Alexander Olk
24 // Alexander Olk alex.olk@googlemail.com
29 using System.Collections;
30 using System.Collections.Specialized;
31 using System.Text.RegularExpressions;
36 // string mimeType = Mime.GetMimeTypeForFile( string filename );
38 // string mimeType = Mime.GetMimeTypeForData( byte[] data );
39 // - for string (maybe an email):
40 // string mimeType = Mime.GetMimeTypeForString( string input );
42 // - get alias for mime type:
43 // string alias = Mime.GetMimeAlias( string mimeType );
44 // - get subclass for mime type:
45 // string subtype = Mime.GetMimeSubClass( string mimeType );
46 // - get all available mime types:
47 // string[] available = Mime.AvailableMimeTypes;
50 // - optimize even more :)
51 // - async callback ?!?
52 // - freedesktop org file extensions can have regular expressions also, resolve them too
53 // - sort match collections by magic priority ( higher = first ) ?
56 // looking up the mime types 20 times for 2757 files in /usr/lib without caching (mime_file_cache)
57 // old version: Time: 00:00:32.3791220
58 // new version: Time: 00:00:16.9991810
60 namespace System.Windows.Forms
64 public static Mime Instance = new Mime();
66 private string current_file_name;
67 private string global_result = octet_stream;
69 private FileStream file_stream;
71 private byte[] buffer = null;
73 private const string octet_stream = "application/octet-stream";
74 private const string text_plain = "text/plain";
75 private const string zero_file = "application/x-zerosize";
77 private StringDictionary mime_file_cache = new StringDictionary();
79 private const int mime_file_cache_max_size = 3000;
81 private string search_string;
83 private static object lock_object = new Object();
85 // private int platform = (int) Environment.OSVersion.Platform;
87 private bool is_zero_file = false;
89 private int bytes_read = 0;
91 private bool mime_available = false;
93 public static NameValueCollection Aliases;
94 public static NameValueCollection SubClasses;
96 public static NameValueCollection GlobalPatternsShort;
97 public static NameValueCollection GlobalPatternsLong;
98 public static NameValueCollection GlobalLiterals;
99 public static NameValueCollection GlobalSufPref;
101 public static ArrayList Matches80Plus;
102 public static ArrayList MatchesBelow80;
107 Aliases = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
108 SubClasses = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
109 GlobalPatternsShort = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
110 GlobalPatternsLong = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
111 GlobalLiterals = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
112 GlobalSufPref = new NameValueCollection (StringComparer.CurrentCultureIgnoreCase);
114 Aliases = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
115 SubClasses = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
116 GlobalPatternsShort = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
117 GlobalPatternsLong = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
118 GlobalLiterals = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
119 GlobalSufPref = new NameValueCollection (new CaseInsensitiveHashCodeProvider (), new Comparer (System.Globalization.CultureInfo.CurrentUICulture));
122 Matches80Plus = new ArrayList ();
123 MatchesBelow80 = new ArrayList ();
125 FDOMimeConfigReader fmcr = new FDOMimeConfigReader ();
126 int buffer_length = fmcr.Init ();
128 if (buffer_length != -1) {
129 buffer = new byte[ buffer_length ];
130 mime_available = true;
134 public static bool MimeAvailable
137 return Instance.mime_available;
141 public static string GetMimeTypeForFile( string filename )
145 Instance.StartByFileName( filename );
148 return Instance.global_result;
152 public static string GetMimeTypeForData( byte[] data )
156 Instance.StartDataLookup( data );
159 return Instance.global_result;
162 public static string GetMimeTypeForString( string input )
166 Instance.StartStringLookup( input );
169 return Instance.global_result;
172 public static string GetMimeAlias( string mimetype )
174 return Aliases[ mimetype ];
177 public static string GetMimeSubClass( string mimetype )
179 return SubClasses[ mimetype ];
182 private void StartByFileName( string filename )
184 if ( mime_file_cache.ContainsKey( filename ) )
186 global_result = mime_file_cache[ filename ];
190 current_file_name = filename;
191 is_zero_file = false;
193 // if ( !CheckForInode( ) )
195 global_result = octet_stream;
200 mime_file_cache.Add( current_file_name, global_result );
202 if (mime_file_cache.Count > mime_file_cache_max_size) {
203 IEnumerator enumerator = mime_file_cache.GetEnumerator ();
205 int counter = mime_file_cache_max_size - 1000;
207 while (enumerator.MoveNext ()) {
208 mime_file_cache.Remove (enumerator.Current.ToString ());
217 private void StartDataLookup( byte[] data )
219 global_result = octet_stream;
221 System.Array.Clear( buffer, 0, buffer.Length );
223 if ( data.Length > buffer.Length )
225 System.Array.Copy( data, buffer, buffer.Length );
229 System.Array.Copy( data, buffer, data.Length );
232 if ( CheckMatch80Plus( ) )
235 if ( CheckMatchBelow80( ) )
238 CheckForBinaryOrText( );
241 private void StartStringLookup( string input )
243 global_result = text_plain;
245 search_string = input;
247 if ( CheckForContentTypeString( ) )
251 // private bool CheckForInode( )
253 // if ( ( platform == 4 ) || ( platform == 128 ) )
259 // Mono.Unix.UnixFileInfo ufi = new Mono.Unix.UnixFileInfo( current_file_name );
266 // if ( ufi.IsDirectory )
268 // global_result = "inode/directory";
272 // if ( ufi.IsBlockDevice )
274 // global_result = "inode/blockdevice";
278 // if ( ufi.IsSocket )
280 // global_result = "inode/socket";
284 // if ( ufi.IsSymbolicLink )
286 // global_result = "inode/symlink";
290 // if ( ufi.IsCharacterDevice )
292 // global_result = "inode/chardevice";
298 // global_result = "inode/fifo";
301 // } catch( Exception e )
310 // // windows platform
316 private void GoByFileName( )
318 // check if we can open the file
321 // couldn't open the file, check globals only
322 CheckGlobalPatterns( );
329 // check for matches with a priority >= 80
330 if ( CheckMatch80Plus( ) )
334 // check global patterns, aka file extensions...
335 // this should be done for zero size files also,
336 // for example zero size file trash.ccc~ should return
337 // application/x-trash instead of application/x-zerosize
338 if ( CheckGlobalPatterns( ) )
341 // if file size is zero, no other checks are needed
345 // ok, still nothing matches then try matches with a priority < 80
346 if ( CheckMatchBelow80( ) )
349 // wow, still nothing... return application/octet-stream for binary data, or text/plain for textual data
350 CheckForBinaryOrText( );
353 private bool CheckMatch80Plus( )
355 foreach ( Match match in Matches80Plus )
357 if ( TestMatch( match ) )
359 global_result = match.MimeType;
368 // this little helper method gives us a real speed improvement
369 private bool FastEndsWidth(string input, string value)
371 if (value.Length > input.Length)
374 int z = input.Length - 1;
376 for (int i = value.Length - 1; i > -1; i--) {
377 if (value[i] != input[z])
386 private bool FastStartsWith(string input, string value)
388 if (value.Length > input.Length)
391 for (int i = 0; i < value.Length; i++)
392 if (value[i] != input[i])
398 // start always with index = 0
399 private int FastIndexOf(string input, char value)
401 if (input.Length == 0)
404 for (int i = 0; i < input.Length; i++)
405 if (input[i] == value)
411 private int FastIndexOf(string input, string value)
413 if (input.Length == 0)
416 for (int i = 0; i < input.Length - value.Length; i++) {
417 if (input[i] == value[0]) {
419 for (int z = 1; z < value.Length; z++) {
420 if (input[i+z] != value[z])
425 if (counter == value.Length -1) {
434 private void CheckGlobalResult( )
436 int comma_index = FastIndexOf(global_result, ',');
438 if ( comma_index != -1 )
440 global_result = global_result.Substring( 0, comma_index );
444 private bool CheckGlobalPatterns( )
446 string filename = Path.GetFileName( current_file_name );
448 // first check for literals
449 for ( int i = 0; i < GlobalLiterals.Count; i++ )
451 string key = GlobalLiterals.GetKey(i);
454 if ( FastIndexOf(key, '[' ) == -1 )
456 if (FastIndexOf(filename, key) != -1)
458 global_result = GlobalLiterals[i];
459 CheckGlobalResult( );
465 if ( Regex.IsMatch( filename, key ) )
467 global_result = GlobalLiterals[ i ];
468 CheckGlobalResult( );
474 if ( FastIndexOf(filename, '.' ) != -1 )
476 // check for double extension like .tar.gz
477 for ( int i = 0; i < GlobalPatternsLong.Count; i++ )
479 string key = GlobalPatternsLong.GetKey( i );
481 if (FastEndsWidth (filename, key))
483 global_result = GlobalPatternsLong[ i ];
484 CheckGlobalResult( );
489 if ( FastEndsWidth (filename.ToLower( ), key ) )
491 global_result = GlobalPatternsLong[ i ];
492 CheckGlobalResult( );
498 // check normal extensions...
499 string extension = Path.GetExtension( current_file_name );
501 if ( extension.Length != 0 )
503 string global_result_tmp = GlobalPatternsShort[ extension ];
505 if ( global_result_tmp != null )
507 global_result = global_result_tmp;
508 CheckGlobalResult( );
512 global_result_tmp = GlobalPatternsShort[ extension.ToLower( ) ];
514 if ( global_result_tmp != null )
516 global_result = global_result_tmp;
517 CheckGlobalResult( );
523 // finally check if a prefix or suffix matches
524 for ( int i = 0; i < GlobalSufPref.Count; i++ )
526 string key = GlobalSufPref.GetKey( i );
530 if (FastEndsWidth(filename, key.Replace( "*", String.Empty )))
532 global_result = GlobalSufPref[ i ];
533 CheckGlobalResult( );
539 if ( FastStartsWith(filename, key.Replace( "*", String.Empty ) ) )
541 global_result = GlobalSufPref[ i ];
542 CheckGlobalResult( );
551 private bool CheckMatchBelow80( )
553 foreach ( Match match in MatchesBelow80 )
555 if ( TestMatch( match ) )
557 global_result = match.MimeType;
566 private void CheckForBinaryOrText( )
568 // check the first 32 bytes
570 for ( int i = 0; i < 32; i++ )
572 char c = System.Convert.ToChar( buffer[ i ] );
574 if ( c != '\t' && c != '\n' && c != '\r' && c != 12 && c < 32 )
576 global_result = octet_stream;
581 global_result = text_plain;
584 private bool TestMatch (Match match)
586 foreach (Matchlet matchlet in match.Matchlets)
587 if (TestMatchlet (matchlet))
593 private bool TestMatchlet( Matchlet matchlet )
595 // using a simple brute force search algorithm
596 // compare each (masked) value from the buffer with the (masked) value from the matchlet
598 // no need to check if the offset + the bytevalue length exceed the # bytes read
599 if (matchlet.Offset + matchlet.ByteValue.Length > bytes_read)
602 for ( int offset_counter = 0; offset_counter < matchlet.OffsetLength; offset_counter++ )
604 if (matchlet.Offset + offset_counter + matchlet.ByteValue.Length > bytes_read)
607 if ( matchlet.Mask == null )
609 if ( buffer[ matchlet.Offset + offset_counter ] == matchlet.ByteValue[ 0 ] )
611 if ( matchlet.ByteValue.Length == 1 )
613 if ( matchlet.Matchlets.Count > 0 )
615 foreach ( Matchlet sub_matchlet in matchlet.Matchlets )
617 if ( TestMatchlet( sub_matchlet ) )
626 // check if the last matchlet byte value is the same as the byte value in the buffer...
627 if (matchlet.ByteValue.Length > 2) {
628 if (buffer[ matchlet.Offset + offset_counter + matchlet.ByteValue.Length - 1 ] != matchlet.ByteValue[ matchlet.ByteValue.Length - 1 ])
634 for ( int i = 1; i < matchlet.ByteValue.Length - minus; i++ )
636 if ( buffer[ matchlet.Offset + offset_counter + i ] != matchlet.ByteValue[ i ] )
640 if ( matchlet.Matchlets.Count > 0 )
642 foreach ( Matchlet sub_matchlets in matchlet.Matchlets )
644 if ( TestMatchlet( sub_matchlets ) )
652 else // with mask ( it's the same as above, only AND the byte with the corresponding mask byte
654 if ( ( buffer[ matchlet.Offset + offset_counter ] & matchlet.Mask[ 0 ] ) ==
655 ( matchlet.ByteValue[ 0 ] & matchlet.Mask[ 0 ] ) )
657 if ( matchlet.ByteValue.Length == 1 )
659 if ( matchlet.Matchlets.Count > 0 )
661 foreach ( Matchlet sub_matchlets in matchlet.Matchlets )
663 if ( TestMatchlet( sub_matchlets ) )
672 // check if the last matchlet byte value is the same as the byte value in the buffer...
673 if (matchlet.ByteValue.Length > 2) {
675 if ((buffer[ matchlet.Offset + offset_counter + matchlet.ByteValue.Length - 1 ] & matchlet.Mask[ matchlet.ByteValue.Length - 1 ])
676 != (matchlet.ByteValue[ matchlet.ByteValue.Length - 1 ] & matchlet.Mask[ matchlet.ByteValue.Length - 1 ]))
682 for ( int i = 1; i < matchlet.ByteValue.Length - minus; i++ )
684 if ( ( buffer[ matchlet.Offset + offset_counter + i ] & matchlet.Mask[ i ] ) !=
685 ( matchlet.ByteValue[ i ] & matchlet.Mask[ i ] ) )
689 if ( matchlet.Matchlets.Count > 0 )
691 foreach ( Matchlet sub_matchlets in matchlet.Matchlets )
693 if ( TestMatchlet( sub_matchlets ) )
706 private bool OpenFile( )
710 file_stream = new FileStream( current_file_name, FileMode.Open, FileAccess.Read ); // FileShare ??? use BinaryReader ???
712 if ( file_stream.Length == 0 )
714 global_result = zero_file;
719 bytes_read = file_stream.Read( buffer, 0, buffer.Length );
721 // do not clear the whole buffer everytime; clear only what's needed
722 if (bytes_read < buffer.Length) {
723 System.Array.Clear( buffer, bytes_read, buffer.Length - bytes_read );
727 file_stream.Close( );
737 private bool CheckForContentTypeString( )
739 int index = search_string.IndexOf( "Content-type:" );
743 index += 13; // Length of string "Content-type:"
745 global_result = String.Empty;
747 while ( search_string[ index ] != ';' )
749 global_result += search_string[ index++ ];
752 global_result.Trim( );
757 // convert string to byte array
758 byte[] string_byte = ( new ASCIIEncoding( ) ).GetBytes( search_string );
760 System.Array.Clear( buffer, 0, buffer.Length );
762 if ( string_byte.Length > buffer.Length )
764 System.Array.Copy( string_byte, buffer, buffer.Length );
768 System.Array.Copy( string_byte, buffer, string_byte.Length );
771 if ( CheckMatch80Plus( ) )
774 if ( CheckMatchBelow80( ) )
781 internal class FDOMimeConfigReader {
782 bool fdo_mime_available = false;
783 StringCollection shared_mime_paths = new StringCollection ();
786 int max_offset_and_range = 0;
790 CheckFDOMimePaths ();
792 if (!fdo_mime_available)
803 shared_mime_paths = null;
806 return max_offset_and_range;
809 private void CheckFDOMimePaths ()
811 if (Directory.Exists ("/usr/share/mime"))
812 shared_mime_paths.Add ("/usr/share/mime/");
814 if (Directory.Exists ("/usr/local/share/mime"))
815 shared_mime_paths.Add ("/usr/local/share/mime/");
817 if (Directory.Exists (System.Environment.GetFolderPath (Environment.SpecialFolder.Personal) + "/.local/share/mime"))
818 shared_mime_paths.Add (System.Environment.GetFolderPath (Environment.SpecialFolder.Personal) + "/.local/share/mime/");
820 if (shared_mime_paths.Count == 0)
823 fdo_mime_available = true;
826 private void ReadMagicData ()
828 foreach (string path in shared_mime_paths) {
829 if (!File.Exists (path + "/magic"))
833 FileStream fs = File.OpenRead (path + "/magic");
834 br = new BinaryReader (fs);
836 if (CheckMagicHeader ()) {
842 } catch (Exception ) {
847 private void MakeMatches ()
849 Matchlet[] matchlets = new Matchlet [30];
851 while (br.PeekChar () != -1) {
853 string mime_type = ReadPriorityAndMimeType (ref priority);
855 if (mime_type != null) {
856 Match match = new Match ();
857 match.Priority = priority;
858 match.MimeType = mime_type;
864 if (br.PeekChar () != '>') {
865 string indent_string = String.Empty;
867 if (br.PeekChar () == '>')
873 indent = Convert.ToInt32 (indent_string);
879 if (br.PeekChar () == '>') {
881 offset = ReadValue ();
884 int value_length = 0;
886 // value length and value
887 if (br.PeekChar () == '=') {
890 // read 2 bytes value length (always big endian)
891 byte first = br.ReadByte ();
892 byte second = br.ReadByte ();
894 value_length = first * 256 + second;
896 value = br.ReadBytes (value_length);
902 if (br.PeekChar () == '&') {
905 mask = br.ReadBytes (value_length);
910 if (br.PeekChar () == '~') {
915 word_size = Convert.ToInt32 (c - 0x30);
917 // data is stored in big endian format.
918 if (word_size > 1 && System.BitConverter.IsLittleEndian) {
919 //convert the value and, if available, the mask data to little endian
920 if (word_size == 2) {
922 for (int i = 0; i < value.Length; i += 2) {
923 byte one = value [i];
924 byte two = value [i + 1];
930 for (int i = 0; i < mask.Length; i += 2) {
932 byte two = mask [i + 1];
937 } else if (word_size == 4) {
939 for (int i = 0; i < value.Length; i += 4) {
940 byte one = value [i];
941 byte two = value [i + 1];
942 byte three = value [i + 2];
943 byte four = value [i + 3];
945 value [i + 1] = three;
951 for (int i = 0; i < mask.Length; i += 4) {
953 byte two = mask [i + 1];
954 byte three = mask [i + 2];
955 byte four = mask [i + 3];
957 mask [i + 1] = three;
968 int range_length = 1;
969 if (br.PeekChar () == '+') {
971 range_length = ReadValue ();
977 // create the matchlet
978 matchlets [indent] = new Matchlet ();
979 matchlets [indent].Offset = offset;
980 matchlets [indent].OffsetLength = range_length;
981 matchlets [indent].ByteValue = value;
983 matchlets [indent].Mask = mask;
986 match.Matchlets.Add (matchlets [indent]);
988 matchlets [indent - 1].Matchlets.Add (matchlets [indent]);
991 if (max_offset_and_range < matchlets [indent].Offset + matchlets [indent].OffsetLength + matchlets [indent].ByteValue.Length + 1)
992 max_offset_and_range = matchlets [indent].Offset + matchlets [indent].OffsetLength + matchlets [indent].ByteValue.Length + 1;
994 // if '[' move to next mime type
995 if (br.PeekChar () == '[')
1000 Mime.MatchesBelow80.Add (match);
1002 Mime.Matches80Plus.Add (match);
1007 private void ReadGlobsData ()
1009 foreach (string path in shared_mime_paths) {
1010 if (!File.Exists (path + "/globs"))
1014 StreamReader sr = new StreamReader (path + "/globs");
1016 while (sr.Peek () != -1) {
1017 string line = sr.ReadLine ().Trim ();
1019 if (line.StartsWith ("#"))
1022 string[] split = line.Split (new char [] {':'});
1024 if (split [1].IndexOf ('*') > -1 && split [1].IndexOf ('.') == -1) {
1025 Mime.GlobalSufPref.Add (split [1], split [0]);
1026 } else if (split [1]. IndexOf ('*') == -1) {
1027 Mime.GlobalLiterals.Add (split [1], split [0]);
1029 string[] split2 = split [1].Split (new char [] {'.'});
1031 if (split2.Length > 2) {
1032 // more than one dot
1033 Mime.GlobalPatternsLong.Add (split [1].Remove(0, 1), split [0]);
1036 Mime.GlobalPatternsShort.Add (split [1].Remove(0, 1), split [0]);
1042 } catch (Exception ) {
1047 private void ReadSubclasses ()
1049 foreach (string path in shared_mime_paths) {
1050 if (!File.Exists (path + "/subclasses"))
1054 StreamReader sr = new StreamReader (path + "/subclasses");
1056 while (sr.Peek () != -1) {
1057 string line = sr.ReadLine ().Trim ();
1059 if (line.StartsWith ("#"))
1062 string[] split = line.Split (new char [] {' '});
1064 Mime.SubClasses.Add (split [0], split [1]);
1068 } catch (Exception ) {
1073 private void ReadAliases ()
1075 foreach (string path in shared_mime_paths) {
1076 if (!File.Exists (path + "/aliases"))
1080 StreamReader sr = new StreamReader (path + "/aliases");
1082 while (sr.Peek () != -1) {
1083 string line = sr.ReadLine ().Trim ();
1085 if (line.StartsWith ("#"))
1088 string[] split = line.Split (new char [] {' '});
1090 Mime.Aliases.Add (split [0], split [1]);
1094 } catch (Exception ) {
1099 private int ReadValue ()
1101 string result_string = String.Empty;
1106 if (br.PeekChar () == '=' || br.PeekChar () == '\n')
1113 result = Convert.ToInt32 (result_string);
1118 private string ReadPriorityAndMimeType (ref int priority)
1120 if (br.ReadChar () == '[') {
1121 string priority_string = String.Empty;
1123 char c = br.ReadChar ();
1126 priority_string += c;
1129 priority = System.Convert.ToInt32 (priority_string);
1131 string mime_type_result = String.Empty;
1133 char c = br.ReadChar ();
1137 mime_type_result += c;
1140 if (br.ReadChar () == '\n')
1141 return mime_type_result;
1146 private bool CheckMagicHeader ()
1148 char[] chars = br.ReadChars (10);
1149 string magic_header = new String (chars);
1151 if (magic_header != "MIME-Magic")
1154 if (br.ReadByte () != 0)
1156 if (br.ReadChar () != '\n')
1163 internal class Match {
1166 ArrayList matchlets = new ArrayList();
1168 public string MimeType {
1178 public int Priority {
1188 public ArrayList Matchlets {
1195 internal class Matchlet {
1203 ArrayList matchlets = new ArrayList ();
1205 public byte[] ByteValue {
1215 public byte[] Mask {
1235 public int OffsetLength {
1237 offsetLength = value;
1241 return offsetLength;
1245 public int WordSize {
1255 public ArrayList Matchlets {